[jackson-dataformat-yaml] 01/04: Imported Upstream version 2.2.3
Wolodja Wentland
babilen-guest at alioth.debian.org
Fri Sep 27 12:19:15 UTC 2013
This is an automated email from the git hooks/post-receive script.
babilen-guest pushed a commit to annotated tag debian/2.2.3-1
in repository jackson-dataformat-yaml.
commit c33011ba22580b8e20d02d0279e8f02f1e4a4150
Author: Wolodja Wentland <babilen at gmail.com>
Date: Thu Sep 26 11:48:27 2013 +0100
Imported Upstream version 2.2.3
---
.gitignore | 21 +
README.md | 57 ++
contributor-agreement.pdf | Bin 0 -> 45328 bytes
pom.xml | 130 ++++
profile-perf.sh | 6 +
release-notes/CREDITS | 12 +
release-notes/VERSION | 66 ++
.../jackson/dataformat/yaml/PackageVersion.java.in | 20 +
.../jackson/dataformat/yaml/UTF8Reader.java | 521 ++++++++++++++
.../jackson/dataformat/yaml/UTF8Writer.java | 412 +++++++++++
.../jackson/dataformat/yaml/YAMLFactory.java | 681 ++++++++++++++++++
.../jackson/dataformat/yaml/YAMLGenerator.java | 545 ++++++++++++++
.../jackson/dataformat/yaml/YAMLParser.java | 743 ++++++++++++++++++++
src/main/resources/META-INF/LICENSE | 8 +
src/main/resources/META-INF/NOTICE | 20 +
.../com.fasterxml.jackson.core.JsonFactory | 1 +
.../jackson/dataformat/yaml/EventsTest.java | 52 ++
.../dataformat/yaml/FormatDetectionTest.java | 52 ++
.../jackson/dataformat/yaml/ModuleTestBase.java | 159 +++++
.../dataformat/yaml/SimpleDatabindTest.java | 81 +++
.../dataformat/yaml/SimpleGenerationTest.java | 76 ++
.../jackson/dataformat/yaml/SimpleParseTest.java | 281 ++++++++
.../jackson/dataformat/yaml/TestVersions.java | 30 +
src/test/java/perf/DeserPerf.java | 142 ++++
src/test/java/perf/MediaItem.java | 396 +++++++++++
src/test/java/perf/SerPerf.java | 146 ++++
26 files changed, 4658 insertions(+)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..84914ec
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,21 @@
+# use glob syntax.
+syntax: glob
+*.class
+*~
+*.bak
+*.off
+*.old
+.DS_Store
+
+# building
+target
+
+# Eclipse
+.classpath
+.project
+.settings
+
+# IDEA
+*.iml
+*.ipr
+*.iws
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..477dd76
--- /dev/null
+++ b/README.md
@@ -0,0 +1,57 @@
+# Overview
+
+This project contains [Jackson](http://http://wiki.fasterxml.com/JacksonHome) extension component for reading and writing [YAML](http://en.wikipedia.org/wiki/YAML) encoded data.
+[SnakeYAML](http://code.google.com/p/snakeyaml/) library is used for low-level YAML parsing.
+This project adds necessary abstractions on top to make things work with other Jackson functionality.
+
+Project is licensed under [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt).
+
+# Status
+
+Project is in its prototype phase, so:
+
+* Basic parsing seems to work, as per basic unit tests
+* Basic generation: not configurable, produces visually ok block format
+* Even format auto-detection works! (can create `ObjectMapper` with multiple `JsonFactory` instances, give an `InputStream`, and it'll figure out what format content is in!)
+
+Missing are:
+
+* Not much configurability: might make sense to esp. allow configuration of generation details
+* Support for YAML tags (which theoretically could help with typing), aliases and anchors (which would be good for Object Id, refs): ideally these would be supported. And it is possible in principle, no fundamental problems.
+
+## Maven dependency
+
+To use this extension on Maven-based projects, use following dependency:
+
+```xml
+<dependency>
+ <groupId>com.fasterxml.jackson.dataformat</groupId>
+ <artifactId>jackson-dataformat-yaml</artifactId>
+ <version>2.1.3</version>
+</dependency>
+```
+
+# Usage
+
+## Simple usage
+
+Usage is as with basic `JsonFactory`; most commonly you will just construct a standard `ObjectMapper` with `com.fasterxml.jackson.dataformat.yaml.YAMLFactory`, like so:
+
+```java
+ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
+User user = mapper.readValue(yamlSource, User.class);
+```
+
+but you can also just use underlying `YAMLFactory` and parser it produces, for event-based processing:
+
+```java
+YAMLFactory factory = new YAMLFactory();
+JsonParser parser = factory.createJsonParser(yamlString); // don't be fooled by method name...
+while (parser.nextToken() != null) {
+ // do something!
+}
+```
+
+# Documentation
+
+* [Documentation](jackson-dataformat-yaml/wiki/Documentation) IS TO BE WRITTEN
diff --git a/contributor-agreement.pdf b/contributor-agreement.pdf
new file mode 100644
index 0000000..fe36036
Binary files /dev/null and b/contributor-agreement.pdf differ
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..b85531d
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,130 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>com.fasterxml</groupId>
+ <artifactId>oss-parent</artifactId>
+ <version>11</version>
+ </parent>
+
+ <groupId>com.fasterxml.jackson.dataformat</groupId>
+ <artifactId>jackson-dataformat-yaml</artifactId>
+ <version>2.2.3</version>
+
+ <name>Jackson-dataformat-YAML</name>
+ <description>Support for reading and writing YAML-encoded data via Jackson
+abstractions.
+ </description>
+ <url>http://wiki.fasterxml.com/JacksonExtensionYAML</url>
+
+ <scm>
+ <connection>scm:git:git at github.com:FasterXML/jackson-dataformat-yaml.git</connection>
+ <developerConnection>scm:git:git at github.com:FasterXML/jackson-dataformat-yaml.git</developerConnection>
+ <url>http://github.com/FasterXML/jackson-dataformat-yaml</url>
+ <tag>jackson-dataformat-yaml-2.2.3</tag>
+ </scm>
+
+ <properties>
+ <jackson.core.version>2.2.3</jackson.core.version>
+ <jackson.annotations.version>2.2.3</jackson.annotations.version>
+ <packageVersion.dir>com/fasterxml/jackson/dataformat/yaml</packageVersion.dir>
+ <packageVersion.package>${project.groupId}.yaml</packageVersion.package>
+
+ <osgi.export>${project.groupId}.yaml;version=${project.version}</osgi.export>
+ <osgi.import>
+org.yaml.snakeyaml,
+org.yaml.snakeyaml.emitter,
+org.yaml.snakeyaml.error,
+org.yaml.snakeyaml.events,
+org.yaml.snakeyaml.parser,
+org.yaml.snakeyaml.reader,
+com.fasterxml.jackson.core,
+com.fasterxml.jackson.core.base,
+com.fasterxml.jackson.core.format,
+com.fasterxml.jackson.core.io,
+com.fasterxml.jackson.core.json,
+com.fasterxml.jackson.core.type,
+com.fasterxml.jackson.core.util,
+</osgi.import>
+ </properties>
+
+ <dependencies>
+ <!-- Extends Jackson core; uses SnakeYAML for parsing, generation -->
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-core</artifactId>
+ <version>${jackson.core.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.yaml</groupId>
+ <artifactId>snakeyaml</artifactId>
+ <version>1.10</version>
+ </dependency>
+
+ <!-- and for testing, JUnit (or TestNG?) is needed; as well as databinder, annotatons -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.10</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-annotations</artifactId>
+ <version>${jackson.annotations.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ <version>${jackson.core.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <!-- Inherited from oss-base. Generate PackageVersion.java.-->
+ <groupId>com.google.code.maven-replacer-plugin</groupId>
+ <artifactId>replacer</artifactId>
+ <executions>
+ <execution>
+ <id>process-packageVersion</id>
+ <phase>generate-sources</phase>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <!-- We will shade SnakeYAML, to simplify deployment, avoid version conflicts -->
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-shade-plugin</artifactId>
+ <version>1.4</version>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>shade</goal>
+ </goals>
+ <configuration>
+ <artifactSet>
+ <includes>
+ <include>org.yaml:snakeyaml</include>
+ </includes>
+ </artifactSet>
+ <relocations>
+ <relocation>
+ <pattern>org.yaml.snakeyaml</pattern>
+ <shadedPattern>com.fasterxml.jackson.dataformat.yaml.snakeyaml</shadedPattern>
+ </relocation>
+ </relocations>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/profile-perf.sh b/profile-perf.sh
new file mode 100755
index 0000000..d66b6ec
--- /dev/null
+++ b/profile-perf.sh
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+java -Xmx64m -server \
+ -cp target/classes:target/test-classes:lib/\* \
+ -Xrunhprof:cpu=samples,depth=10,verbose=n,interval=2 \
+$*
diff --git a/release-notes/CREDITS b/release-notes/CREDITS
new file mode 100644
index 0000000..f250251
--- /dev/null
+++ b/release-notes/CREDITS
@@ -0,0 +1,12 @@
+Here are people who have contributed to development Jackson JSON process
+databind core component, version 2.x
+(version numbers in brackets indicate release in which the problem was fixed)
+
+(note: for older credits, check out release notes for 1.x versions)
+
+Tatu Saloranta, tatu.saloranta at iki.fi: author
+
+Shawn Smith:
+
+- Contibuted [Issue-14]: Fix parsing of longs near MIN/MAX_INT, parsing of BigIntegers.
+ (2.1.4)
diff --git a/release-notes/VERSION b/release-notes/VERSION
new file mode 100644
index 0000000..b2c745c
--- /dev/null
+++ b/release-notes/VERSION
@@ -0,0 +1,66 @@
+Project: jackson-dataformat-yaml
+Version: 2.2.3 (22-Aug-2013)
+
+No functional changes.
+
+------------------------------------------------------------------------
+=== History: ===
+------------------------------------------------------------------------
+
+2.2.2 (27-May-2013)
+
+No functional changes.
+
+2.2.1 (03-May-2013)
+
+- Fixed problem with YamlFactory.copy()
+
+2.2.0 (22-Apr-2013)
+
+New minor version; no functional changes.
+
+2.1.3 (19-Jan-2013)
+
+- [Issue-10]: Avoid parsing scalars in non-plain flow styles
+ (contributed by nkvoll at github)
+- [Issue-12]: Problems with 'YAMLFactory.createJsonGenerator(File)'
+
+2.1.2 (04-Dec-2012)
+
+No functional changes.
+
+2.1.1 (12-Nov-2012)
+
+No functional changes.
+
+2.1.0 (08-Oct-2012)
+
+Another minor update on Jackson YAML format module
+
+Improvements:
+
+- [Issue-5]: Expose Aliases as JsonToken.STRING_VALUE tokens
+- [Issue-6]: Add a method in YAMLParser to detect Anchors: `getCurrentAnchor()`
+- [Issue-7]: Possible parsing problems with unquoted values with colons
+
+2.0.5 (27-Jul-2012)
+
+- [Issue-4]: Regexp for detecting floating point numbers too permissive with dots
+ (reported by Dan Sheehan)
+
+2.0.4: skipped release (no changes)
+
+2.0.3: skipped release (no changes)
+
+2.0.2 (14-May-2012)
+
+Improvements:
+
+- 10-20% performance improvements based on profiling (most overhead
+ in SnakeYaml, so limited possibilities)
+
+2.0.0 (03-May-2012)
+
+Fixes:
+
+* [Issue-2] UUIDs not properly deserialized.
diff --git a/src/main/java/com/fasterxml/jackson/dataformat/yaml/PackageVersion.java.in b/src/main/java/com/fasterxml/jackson/dataformat/yaml/PackageVersion.java.in
new file mode 100644
index 0000000..7860aa1
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/dataformat/yaml/PackageVersion.java.in
@@ -0,0 +1,20 @@
+package @package@;
+
+import com.fasterxml.jackson.core.Version;
+import com.fasterxml.jackson.core.Versioned;
+import com.fasterxml.jackson.core.util.VersionUtil;
+
+/**
+ * Automatically generated from PackageVersion.java.in during
+ * packageVersion-generate execution of maven-replacer-plugin in
+ * pom.xml.
+ */
+public final class PackageVersion implements Versioned {
+ public final static Version VERSION = VersionUtil.parseVersion(
+ "@projectversion@", "@projectgroupid@", "@projectartifactid@");
+
+ @Override
+ public Version version() {
+ return VERSION;
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/dataformat/yaml/UTF8Reader.java b/src/main/java/com/fasterxml/jackson/dataformat/yaml/UTF8Reader.java
new file mode 100644
index 0000000..e924270
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/dataformat/yaml/UTF8Reader.java
@@ -0,0 +1,521 @@
+package com.fasterxml.jackson.dataformat.yaml;
+
+import java.io.*;
+import java.lang.ref.SoftReference;
+
+/**
+ * Optimized Reader that reads UTF-8 encoded content from an input stream.
+ * In addition to doing (hopefully) optimal conversion, it can also take
+ * array of "pre-read" (leftover) bytes; this is necessary when preliminary
+ * stream/reader is trying to figure out underlying character encoding.
+ */
+public final class UTF8Reader
+ extends Reader
+{
+ private final static int DEFAULT_BUFFER_SIZE = 8000;
+
+ /**
+ * This <code>ThreadLocal</code> contains a {@link java.lang.ref.SoftRerefence}
+ * to a byte array used for holding content to decode
+ */
+ final protected static ThreadLocal<SoftReference<byte[][]>> _bufferRecycler
+ = new ThreadLocal<SoftReference<byte[][]>>();
+
+ protected final byte[][] _bufferHolder;
+
+ private InputStream _inputSource;
+
+ private final boolean _autoClose;
+
+ protected byte[] _inputBuffer;
+
+ /**
+ * Pointer to the next available byte (if any), iff less than
+ * <code>mByteBufferEnd</code>
+ */
+ protected int _inputPtr;
+
+ /**
+ * Pointed to the end marker, that is, position one after the last
+ * valid available byte.
+ */
+ protected int _inputEnd;
+
+ /**
+ * Decoded first character of a surrogate pair, if one needs to be buffered
+ */
+ protected int _surrogate = -1;
+
+ /**
+ * Total read character count; used for error reporting purposes
+ */
+ int _charCount = 0;
+
+ /**
+ * Total read byte count; used for error reporting purposes
+ */
+ int _byteCount = 0;
+
+ /*
+ /**********************************************************************
+ /* Life-cycle
+ /**********************************************************************
+ */
+
+ public UTF8Reader(InputStream in, boolean autoClose)
+ {
+ super(in);
+ _inputSource = in;
+ _bufferHolder = _findBufferHolder();
+ byte[] buffer = _bufferHolder[0];
+ if (buffer == null) {
+ _bufferHolder[0] = buffer = new byte[DEFAULT_BUFFER_SIZE];
+ }
+ _inputBuffer = buffer;
+ _inputPtr = 0;
+ _inputEnd = 0;
+ _autoClose = autoClose;
+ }
+
+ public UTF8Reader(byte[] buf, int ptr, int len, boolean autoClose)
+ {
+ super(bogusStream());
+ _inputSource = null;
+ _inputBuffer = buf;
+ _inputPtr = ptr;
+ _inputEnd = ptr+len;
+ _autoClose = autoClose;
+ _bufferHolder = null;
+ }
+
+ private static InputStream bogusStream() {
+ return new InputStream() {
+ @Override
+ public int read() throws IOException {
+ return -1;
+ }
+ };
+ }
+
+ private static byte[][] _findBufferHolder()
+ {
+ byte[][] bufs = null;
+ SoftReference<byte[][]> ref = _bufferRecycler.get();
+ if (ref != null) {
+ bufs = ref.get();
+ }
+ if (bufs == null) {
+ bufs = new byte[1][];
+ _bufferRecycler.set(new SoftReference<byte[][]>(bufs));
+ }
+ return bufs;
+ }
+
+
+ /*
+ /**********************************************************************
+ /* Reader API
+ /**********************************************************************
+ */
+
+ @Override
+ public void close()
+ throws IOException
+ {
+ InputStream in = _inputSource;
+
+ if (in != null) {
+ _inputSource = null;
+ if (_autoClose) {
+ in.close();
+ }
+ }
+ freeBuffers();
+ }
+
+ private char[] _tmpBuffer = null;
+
+ /**
+ * Although this method is implemented by the base class, AND it should
+ * never be called by Woodstox code, let's still implement it bit more
+ * efficiently just in case
+ */
+ @Override
+ public int read()
+ throws IOException
+ {
+ if (_tmpBuffer == null) {
+ _tmpBuffer = new char[1];
+ }
+ if (read(_tmpBuffer, 0, 1) < 1) {
+ return -1;
+ }
+ return _tmpBuffer[0];
+ }
+
+ @Override
+ public int read(char[] cbuf) throws IOException {
+ return read(cbuf, 0, cbuf.length);
+ }
+
+ @Override
+ public int read(char[] cbuf, int start, int len)
+ throws IOException
+ {
+ // Already EOF?
+ if (_inputBuffer == null) {
+ return -1;
+ }
+ len += start;
+ int outPtr = start;
+
+ // Ok, first; do we have a surrogate from last round?
+ if (_surrogate >= 0) {
+ cbuf[outPtr++] = (char) _surrogate;
+ _surrogate = -1;
+ // No need to load more, already got one char
+ } else {
+ /* To prevent unnecessary blocking (esp. with network streams),
+ * we'll only require decoding of a single char
+ */
+ int left = (_inputEnd - _inputPtr);
+
+ /* So; only need to load more if we can't provide at least
+ * one more character. We need not do thorough check here,
+ * but let's check the common cases here: either completely
+ * empty buffer (left == 0), or one with less than max. byte
+ * count for a single char, and starting of a multi-byte
+ * encoding (this leaves possibility of a 2/3-byte char
+ * that is still fully accessible... but that can be checked
+ * by the load method)
+ */
+
+ if (left < 4) {
+ // Need to load more?
+ if (left < 1 || _inputBuffer[_inputPtr] < 0) {
+ if (!loadMore(left)) { // (legal) EOF?
+ return -1;
+ }
+ }
+ }
+ }
+ final byte[] buf = _inputBuffer;
+ int inPtr = _inputPtr;
+ final int inBufLen = _inputEnd;
+
+ main_loop:
+ while (outPtr < len) {
+ // At this point we have at least one byte available
+ int c = (int) buf[inPtr++];
+
+ // Let's first do the quickie loop for common case; 7-bit ASCII
+ if (c >= 0) { // ASCII? can probably loop, then
+ cbuf[outPtr++] = (char) c; // ok since MSB is never on
+
+ // Ok, how many such chars could we safely process without overruns?
+ // (will combine 2 in-loop comparisons into just one)
+ int outMax = (len - outPtr); // max output
+ int inMax = (inBufLen - inPtr); // max input
+ int inEnd = inPtr + ((inMax < outMax) ? inMax : outMax);
+
+ ascii_loop:
+ while (true) {
+ if (inPtr >= inEnd) {
+ break main_loop;
+ }
+ c = buf[inPtr++];
+ if (c < 0) { // or multi-byte
+ break ascii_loop;
+ }
+ cbuf[outPtr++] = (char) c;
+ }
+ }
+
+ int needed;
+
+ // Ok; if we end here, we got multi-byte combination
+ if ((c & 0xE0) == 0xC0) { // 2 bytes (0x0080 - 0x07FF)
+ c = (c & 0x1F);
+ needed = 1;
+ } else if ((c & 0xF0) == 0xE0) { // 3 bytes (0x0800 - 0xFFFF)
+ c = (c & 0x0F);
+ needed = 2;
+ } else if ((c & 0xF8) == 0xF0) {
+ // 4 bytes; double-char BS, with surrogates and all...
+ c = (c & 0x0F);
+ needed = 3;
+ } else {
+ reportInvalidInitial(c & 0xFF, outPtr-start);
+ // never gets here...
+ needed = 1;
+ }
+ /* Do we have enough bytes? If not, let's just push back the
+ * byte and leave, since we have already gotten at least one
+ * char decoded. This way we will only block (with read from
+ * input stream) when absolutely necessary.
+ */
+ if ((inBufLen - inPtr) < needed) {
+ --inPtr;
+ break main_loop;
+ }
+
+ int d = (int) buf[inPtr++];
+ if ((d & 0xC0) != 0x080) {
+ reportInvalidOther(d & 0xFF, outPtr-start);
+ }
+ c = (c << 6) | (d & 0x3F);
+
+ if (needed > 1) { // needed == 1 means 2 bytes total
+ d = buf[inPtr++]; // 3rd byte
+ if ((d & 0xC0) != 0x080) {
+ reportInvalidOther(d & 0xFF, outPtr-start);
+ }
+ c = (c << 6) | (d & 0x3F);
+ if (needed > 2) { // 4 bytes? (need surrogates)
+ d = buf[inPtr++];
+ if ((d & 0xC0) != 0x080) {
+ reportInvalidOther(d & 0xFF, outPtr-start);
+ }
+ c = (c << 6) | (d & 0x3F);
+ /* Ugh. Need to mess with surrogates. Ok; let's inline them
+ * there, then, if there's room: if only room for one,
+ * need to save the surrogate for the rainy day...
+ */
+ c -= 0x10000; // to normalize it starting with 0x0
+ cbuf[outPtr++] = (char) (0xD800 + (c >> 10));
+ // hmmh. can this ever be 0? (not legal, at least?)
+ c = (0xDC00 | (c & 0x03FF));
+
+ // Room for second part?
+ if (outPtr >= len) { // nope
+ _surrogate = c;
+ break main_loop;
+ }
+ // sure, let's fall back to normal processing:
+ }
+ // Otherwise, should we check that 3-byte chars are
+ // legal ones (should not expand to surrogates?
+ // For now, let's not...
+ /*
+ else {
+ if (c >= 0xD800 && c < 0xE000) {
+ reportInvalid(c, outPtr-start, "(a surrogate character) ");
+ }
+ }
+ */
+ }
+ cbuf[outPtr++] = (char) c;
+ if (inPtr >= inBufLen) {
+ break main_loop;
+ }
+ }
+
+ _inputPtr = inPtr;
+ len = outPtr - start;
+ _charCount += len;
+ return len;
+ }
+
+ /*
+ /**********************************************************************
+ /* Internal/package methods:
+ /**********************************************************************
+ */
+
+ protected final InputStream getStream() { return _inputSource; }
+
+ /**
+ * Method for reading as many bytes from the underlying stream as possible
+ * (that fit in the buffer), to the beginning of the buffer.
+ */
+ protected final int readBytes()
+ throws IOException
+ {
+ _inputPtr = 0;
+ _inputEnd = 0;
+ if (_inputSource != null) {
+ int count = _inputSource.read(_inputBuffer, 0, _inputBuffer.length);
+ if (count > 0) {
+ _inputEnd = count;
+ }
+ return count;
+ }
+ return -1;
+ }
+
+ /**
+ * Method for reading as many bytes from the underlying stream as possible
+ * (that fit in the buffer considering offset), to the specified offset.
+ *
+ * @return Number of bytes read, if any; -1 to indicate none available
+ * (that is, end of input)
+ */
+ protected final int readBytesAt(int offset)
+ throws IOException
+ {
+ // shouldn't modify mBytePtr, assumed to be 'offset'
+ if (_inputSource != null) {
+ int count = _inputSource.read(_inputBuffer, offset, _inputBuffer.length - offset);
+ if (count > 0) {
+ _inputEnd += count;
+ }
+ return count;
+ }
+ return -1;
+ }
+
+ /**
+ * This method should be called along with (or instead of) normal
+ * close. After calling this method, no further reads should be tried.
+ * Method will try to recycle read buffers (if any).
+ */
+ public final void freeBuffers()
+ {
+ if (_bufferHolder != null) {
+ byte[] buf = _inputBuffer;
+ if (buf != null) {
+ _inputBuffer = null;
+ _bufferHolder[0] = buf;
+ }
+ }
+ }
+
+ /*
+ /**********************************************************************
+ /* Internal methods
+ /**********************************************************************
+ */
+
+ private void reportInvalidInitial(int mask, int offset)
+ throws IOException
+ {
+ // input (byte) ptr has been advanced by one, by now:
+ int bytePos = _byteCount + _inputPtr - 1;
+ int charPos = _charCount + offset + 1;
+
+ throw new CharConversionException("Invalid UTF-8 start byte 0x"+Integer.toHexString(mask)
+ +" (at char #"+charPos+", byte #"+bytePos+")");
+ }
+
+ private void reportInvalidOther(int mask, int offset)
+ throws IOException
+ {
+ int bytePos = _byteCount + _inputPtr - 1;
+ int charPos = _charCount + offset;
+
+ throw new CharConversionException("Invalid UTF-8 middle byte 0x"+Integer.toHexString(mask)
+ +" (at char #"+charPos+", byte #"+bytePos+")");
+ }
+
+ private void reportUnexpectedEOF(int gotBytes, int needed)
+ throws IOException
+ {
+ int bytePos = _byteCount + gotBytes;
+ int charPos = _charCount;
+
+ throw new CharConversionException("Unexpected EOF in the middle of a multi-byte char: got "
+ +gotBytes+", needed "+needed +", at char #"+charPos+", byte #"+bytePos+")");
+ }
+
+ /*
+ private void reportInvalid(int value, int offset, String msg) throws IOException
+ {
+ int bytePos = _byteCount + _inputPtr - 1;
+ int charPos = _charCount + offset;
+
+ throw new CharConversionException("Invalid UTF-8 character 0x"+Integer.toHexString(value)+msg
+ +" at char #"+charPos+", byte #"+bytePos+")");
+ }
+ */
+
+ /**
+ * @param available Number of "unused" bytes in the input buffer
+ *
+ * @return True, if enough bytes were read to allow decoding of at least
+ * one full character; false if EOF was encountered instead.
+ */
+ private boolean loadMore(int available)
+ throws IOException
+ {
+ _byteCount += (_inputEnd - available);
+
+ // Bytes that need to be moved to the beginning of buffer?
+ if (available > 0) {
+ if (_inputPtr > 0) {
+ // sanity check: can only move if we "own" buffers
+ if (_bufferHolder == null) {
+ throw new IllegalStateException("Internal error: need to move partially decoded character; buffer not modifiable");
+ }
+
+ for (int i = 0; i < available; ++i) {
+ _inputBuffer[i] = _inputBuffer[_inputPtr+i];
+ }
+ _inputPtr = 0;
+ _inputEnd = available;
+ }
+ } else {
+ /* Ok; here we can actually reasonably expect an EOF,
+ * so let's do a separate read right away:
+ */
+ int count = readBytes();
+ if (count < 1) {
+ freeBuffers(); // to help GC?
+ if (count < 0) { // -1
+ return false;
+ }
+ // 0 count is no good; let's err out
+ reportStrangeStream();
+ }
+ }
+
+ /* We now have at least one byte... and that allows us to
+ * calculate exactly how many bytes we need!
+ */
+ @SuppressWarnings("cast")
+ int c = (int) _inputBuffer[_inputPtr];
+ if (c >= 0) { // single byte (ascii) char... cool, can return
+ return true;
+ }
+
+ // Ok, a multi-byte char, let's check how many bytes we'll need:
+ int needed;
+ if ((c & 0xE0) == 0xC0) { // 2 bytes (0x0080 - 0x07FF)
+ needed = 2;
+ } else if ((c & 0xF0) == 0xE0) { // 3 bytes (0x0800 - 0xFFFF)
+ needed = 3;
+ } else if ((c & 0xF8) == 0xF0) {
+ // 4 bytes; double-char BS, with surrogates and all...
+ needed = 4;
+ } else {
+ reportInvalidInitial(c & 0xFF, 0);
+ // never gets here... but compiler whines without this:
+ needed = 1;
+ }
+
+ /* And then we'll just need to load up to that many bytes;
+ * if an EOF is hit, that'll be an error. But we need not do
+ * actual decoding here, just load enough bytes.
+ */
+ while ((_inputPtr + needed) > _inputEnd) {
+ int count = readBytesAt(_inputEnd);
+ if (count < 1) {
+ if (count < 0) { // -1, EOF... no good!
+ freeBuffers();
+ reportUnexpectedEOF(_inputEnd, needed);
+ }
+ // 0 count is no good; let's err out
+ reportStrangeStream();
+ }
+ }
+ return true;
+ }
+
+ protected void reportBounds(char[] cbuf, int start, int len) throws IOException {
+ throw new ArrayIndexOutOfBoundsException("read(buf,"+start+","+len+"), cbuf["+cbuf.length+"]");
+ }
+
+ protected void reportStrangeStream() throws IOException {
+ throw new IOException("Strange I/O stream, returned 0 bytes on read");
+ }
+}
+
diff --git a/src/main/java/com/fasterxml/jackson/dataformat/yaml/UTF8Writer.java b/src/main/java/com/fasterxml/jackson/dataformat/yaml/UTF8Writer.java
new file mode 100644
index 0000000..8193177
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/dataformat/yaml/UTF8Writer.java
@@ -0,0 +1,412 @@
+package com.fasterxml.jackson.dataformat.yaml;
+
+import java.io.*;
+import java.lang.ref.SoftReference;
+
+public final class UTF8Writer
+ extends Writer
+{
+ final static int SURR1_FIRST = 0xD800;
+ final static int SURR1_LAST = 0xDBFF;
+ final static int SURR2_FIRST = 0xDC00;
+ final static int SURR2_LAST = 0xDFFF;
+
+ private final static int DEFAULT_BUFFER_SIZE = 8000;
+
+ /**
+ * This <code>ThreadLocal</code> contains a {@link java.lang.ref.SoftRerefence}
+ * to a byte array used for holding content to decode
+ */
+ final protected static ThreadLocal<SoftReference<byte[][]>> _bufferRecycler
+ = new ThreadLocal<SoftReference<byte[][]>>();
+
+ protected final byte[][] _bufferHolder;
+
+ private OutputStream _out;
+
+ private byte[] _outBuffer;
+
+ private final int _outBufferEnd;
+
+ private int _outPtr;
+
+ /**
+ * When outputting chars from BMP, surrogate pairs need to be coalesced.
+ * To do this, both pairs must be known first; and since it is possible
+ * pairs may be split, we need temporary storage for the first half
+ */
+ int _surrogate = 0;
+
+ public UTF8Writer(OutputStream out)
+ {
+ _out = out;
+ _bufferHolder = _findBufferHolder();
+ byte[] buffer = _bufferHolder[0];
+ if (buffer == null) {
+ _bufferHolder[0] = buffer = new byte[DEFAULT_BUFFER_SIZE];
+ }
+ _outBuffer = buffer;
+ /* Max. expansion for a single char (in unmodified UTF-8) is
+ * 4 bytes (or 3 depending on how you view it -- 4 when recombining
+ * surrogate pairs)
+ */
+ _outBufferEnd = _outBuffer.length - 4;
+ _outPtr = 0;
+ }
+
+ private static byte[][] _findBufferHolder()
+ {
+ byte[][] bufs = null;
+ SoftReference<byte[][]> ref = _bufferRecycler.get();
+ if (ref != null) {
+ bufs = ref.get();
+ }
+ if (bufs == null) {
+ bufs = new byte[1][];
+ _bufferRecycler.set(new SoftReference<byte[][]>(bufs));
+ }
+ return bufs;
+ }
+
+ @Override
+ public Writer append(char c)
+ throws IOException
+ {
+ write(c);
+ return this;
+ }
+
+ @Override
+ public void close()
+ throws IOException
+ {
+ if (_out != null) {
+ if (_outPtr > 0) {
+ _out.write(_outBuffer, 0, _outPtr);
+ _outPtr = 0;
+ }
+ OutputStream out = _out;
+ _out = null;
+
+ byte[] buf = _outBuffer;
+ if (buf != null) {
+ _outBuffer = null;
+ _bufferHolder[0] = buf;
+ }
+ out.close();
+
+ /* Let's 'flush' orphan surrogate, no matter what; but only
+ * after cleanly closing everything else.
+ */
+ int code = _surrogate;
+ _surrogate = 0;
+ if (code > 0) {
+ throwIllegal(code);
+ }
+ }
+ }
+
+ @Override
+ public void flush()
+ throws IOException
+ {
+ if (_out != null) {
+ if (_outPtr > 0) {
+ _out.write(_outBuffer, 0, _outPtr);
+ _outPtr = 0;
+ }
+ _out.flush();
+ }
+ }
+
+ @Override
+ public void write(char[] cbuf)
+ throws IOException
+ {
+ write(cbuf, 0, cbuf.length);
+ }
+
+ @Override
+ public void write(char[] cbuf, int off, int len)
+ throws IOException
+ {
+ if (len < 2) {
+ if (len == 1) {
+ write(cbuf[off]);
+ }
+ return;
+ }
+
+ // First: do we have a leftover surrogate to deal with?
+ if (_surrogate > 0) {
+ char second = cbuf[off++];
+ --len;
+ write(convertSurrogate(second));
+ // will have at least one more char
+ }
+
+ int outPtr = _outPtr;
+ byte[] outBuf = _outBuffer;
+ int outBufLast = _outBufferEnd; // has 4 'spare' bytes
+
+ // All right; can just loop it nice and easy now:
+ len += off; // len will now be the end of input buffer
+
+ output_loop:
+ for (; off < len; ) {
+ /* First, let's ensure we can output at least 4 bytes
+ * (longest UTF-8 encoded codepoint):
+ */
+ if (outPtr >= outBufLast) {
+ _out.write(outBuf, 0, outPtr);
+ outPtr = 0;
+ }
+
+ int c = cbuf[off++];
+ // And then see if we have an Ascii char:
+ if (c < 0x80) { // If so, can do a tight inner loop:
+ outBuf[outPtr++] = (byte)c;
+ // Let's calc how many ascii chars we can copy at most:
+ int maxInCount = (len - off);
+ int maxOutCount = (outBufLast - outPtr);
+
+ if (maxInCount > maxOutCount) {
+ maxInCount = maxOutCount;
+ }
+ maxInCount += off;
+ ascii_loop:
+ while (true) {
+ if (off >= maxInCount) { // done with max. ascii seq
+ continue output_loop;
+ }
+ c = cbuf[off++];
+ if (c >= 0x80) {
+ break ascii_loop;
+ }
+ outBuf[outPtr++] = (byte) c;
+ }
+ }
+
+ // Nope, multi-byte:
+ if (c < 0x800) { // 2-byte
+ outBuf[outPtr++] = (byte) (0xc0 | (c >> 6));
+ outBuf[outPtr++] = (byte) (0x80 | (c & 0x3f));
+ } else { // 3 or 4 bytes
+ // Surrogates?
+ if (c < SURR1_FIRST || c > SURR2_LAST) {
+ outBuf[outPtr++] = (byte) (0xe0 | (c >> 12));
+ outBuf[outPtr++] = (byte) (0x80 | ((c >> 6) & 0x3f));
+ outBuf[outPtr++] = (byte) (0x80 | (c & 0x3f));
+ continue;
+ }
+ // Yup, a surrogate:
+ if (c > SURR1_LAST) { // must be from first range
+ _outPtr = outPtr;
+ throwIllegal(c);
+ }
+ _surrogate = c;
+ // and if so, followed by another from next range
+ if (off >= len) { // unless we hit the end?
+ break;
+ }
+ c = convertSurrogate(cbuf[off++]);
+ if (c > 0x10FFFF) { // illegal in JSON as well as in XML
+ _outPtr = outPtr;
+ throwIllegal(c);
+ }
+ outBuf[outPtr++] = (byte) (0xf0 | (c >> 18));
+ outBuf[outPtr++] = (byte) (0x80 | ((c >> 12) & 0x3f));
+ outBuf[outPtr++] = (byte) (0x80 | ((c >> 6) & 0x3f));
+ outBuf[outPtr++] = (byte) (0x80 | (c & 0x3f));
+ }
+ }
+ _outPtr = outPtr;
+ }
+
+ @Override
+ public void write(int c) throws IOException
+ {
+ // First; do we have a left over surrogate?
+ if (_surrogate > 0) {
+ c = convertSurrogate(c);
+ // If not, do we start with a surrogate?
+ } else if (c >= SURR1_FIRST && c <= SURR2_LAST) {
+ // Illegal to get second part without first:
+ if (c > SURR1_LAST) {
+ throwIllegal(c);
+ }
+ // First part just needs to be held for now
+ _surrogate = c;
+ return;
+ }
+
+ if (_outPtr >= _outBufferEnd) { // let's require enough room, first
+ _out.write(_outBuffer, 0, _outPtr);
+ _outPtr = 0;
+ }
+
+ if (c < 0x80) { // ascii
+ _outBuffer[_outPtr++] = (byte) c;
+ } else {
+ int ptr = _outPtr;
+ if (c < 0x800) { // 2-byte
+ _outBuffer[ptr++] = (byte) (0xc0 | (c >> 6));
+ _outBuffer[ptr++] = (byte) (0x80 | (c & 0x3f));
+ } else if (c <= 0xFFFF) { // 3 bytes
+ _outBuffer[ptr++] = (byte) (0xe0 | (c >> 12));
+ _outBuffer[ptr++] = (byte) (0x80 | ((c >> 6) & 0x3f));
+ _outBuffer[ptr++] = (byte) (0x80 | (c & 0x3f));
+ } else { // 4 bytes
+ if (c > 0x10FFFF) { // illegal
+ throwIllegal(c);
+ }
+ _outBuffer[ptr++] = (byte) (0xf0 | (c >> 18));
+ _outBuffer[ptr++] = (byte) (0x80 | ((c >> 12) & 0x3f));
+ _outBuffer[ptr++] = (byte) (0x80 | ((c >> 6) & 0x3f));
+ _outBuffer[ptr++] = (byte) (0x80 | (c & 0x3f));
+ }
+ _outPtr = ptr;
+ }
+ }
+
+ @Override
+ public void write(String str) throws IOException
+ {
+ write(str, 0, str.length());
+ }
+
+ @Override
+ public void write(String str, int off, int len) throws IOException
+ {
+ if (len < 2) {
+ if (len == 1) {
+ write(str.charAt(off));
+ }
+ return;
+ }
+
+ // First: do we have a leftover surrogate to deal with?
+ if (_surrogate > 0) {
+ char second = str.charAt(off++);
+ --len;
+ write(convertSurrogate(second));
+ // will have at least one more char (case of 1 char was checked earlier on)
+ }
+
+ int outPtr = _outPtr;
+ byte[] outBuf = _outBuffer;
+ int outBufLast = _outBufferEnd; // has 4 'spare' bytes
+
+ // All right; can just loop it nice and easy now:
+ len += off; // len will now be the end of input buffer
+
+ output_loop:
+ for (; off < len; ) {
+ /* First, let's ensure we can output at least 4 bytes
+ * (longest UTF-8 encoded codepoint):
+ */
+ if (outPtr >= outBufLast) {
+ _out.write(outBuf, 0, outPtr);
+ outPtr = 0;
+ }
+
+ int c = str.charAt(off++);
+ // And then see if we have an Ascii char:
+ if (c < 0x80) { // If so, can do a tight inner loop:
+ outBuf[outPtr++] = (byte)c;
+ // Let's calc how many ascii chars we can copy at most:
+ int maxInCount = (len - off);
+ int maxOutCount = (outBufLast - outPtr);
+
+ if (maxInCount > maxOutCount) {
+ maxInCount = maxOutCount;
+ }
+ maxInCount += off;
+ ascii_loop:
+ while (true) {
+ if (off >= maxInCount) { // done with max. ascii seq
+ continue output_loop;
+ }
+ c = str.charAt(off++);
+ if (c >= 0x80) {
+ break ascii_loop;
+ }
+ outBuf[outPtr++] = (byte) c;
+ }
+ }
+
+ // Nope, multi-byte:
+ if (c < 0x800) { // 2-byte
+ outBuf[outPtr++] = (byte) (0xc0 | (c >> 6));
+ outBuf[outPtr++] = (byte) (0x80 | (c & 0x3f));
+ } else { // 3 or 4 bytes
+ // Surrogates?
+ if (c < SURR1_FIRST || c > SURR2_LAST) {
+ outBuf[outPtr++] = (byte) (0xe0 | (c >> 12));
+ outBuf[outPtr++] = (byte) (0x80 | ((c >> 6) & 0x3f));
+ outBuf[outPtr++] = (byte) (0x80 | (c & 0x3f));
+ continue;
+ }
+ // Yup, a surrogate:
+ if (c > SURR1_LAST) { // must be from first range
+ _outPtr = outPtr;
+ throwIllegal(c);
+ }
+ _surrogate = c;
+ // and if so, followed by another from next range
+ if (off >= len) { // unless we hit the end?
+ break;
+ }
+ c = convertSurrogate(str.charAt(off++));
+ if (c > 0x10FFFF) { // illegal, as per RFC 4627
+ _outPtr = outPtr;
+ throwIllegal(c);
+ }
+ outBuf[outPtr++] = (byte) (0xf0 | (c >> 18));
+ outBuf[outPtr++] = (byte) (0x80 | ((c >> 12) & 0x3f));
+ outBuf[outPtr++] = (byte) (0x80 | ((c >> 6) & 0x3f));
+ outBuf[outPtr++] = (byte) (0x80 | (c & 0x3f));
+ }
+ }
+ _outPtr = outPtr;
+ }
+
+ /*
+ /**********************************************************
+ /* Internal methods
+ /**********************************************************
+ */
+
+ /**
+ * Method called to calculate UTF codepoint, from a surrogate pair.
+ */
+ private int convertSurrogate(int secondPart)
+ throws IOException
+ {
+ int firstPart = _surrogate;
+ _surrogate = 0;
+
+ // Ok, then, is the second part valid?
+ if (secondPart < SURR2_FIRST || secondPart > SURR2_LAST) {
+ throw new IOException("Broken surrogate pair: first char 0x"+Integer.toHexString(firstPart)+", second 0x"+Integer.toHexString(secondPart)+"; illegal combination");
+ }
+ return 0x10000 + ((firstPart - SURR1_FIRST) << 10) + (secondPart - SURR2_FIRST);
+ }
+
+ private void throwIllegal(int code)
+ throws IOException
+ {
+ if (code > 0x10FFFF) { // over max?
+ throw new IOException("Illegal character point (0x"+Integer.toHexString(code)+") to output; max is 0x10FFFF as per RFC 4627");
+ }
+ if (code >= SURR1_FIRST) {
+ if (code <= SURR1_LAST) { // Unmatched first part (closing without second part?)
+ throw new IOException("Unmatched first part of surrogate pair (0x"+Integer.toHexString(code)+")");
+ }
+ throw new IOException("Unmatched second part of surrogate pair (0x"+Integer.toHexString(code)+")");
+ }
+
+ // should we ever get this?
+ throw new IOException("Illegal character point (0x"+Integer.toHexString(code)+") to output");
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLFactory.java b/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLFactory.java
new file mode 100644
index 0000000..73d2d25
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLFactory.java
@@ -0,0 +1,681 @@
+package com.fasterxml.jackson.dataformat.yaml;
+
+import java.io.*;
+import java.net.URL;
+import java.nio.charset.Charset;
+
+import org.yaml.snakeyaml.DumperOptions;
+import org.yaml.snakeyaml.DumperOptions.FlowStyle;
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.format.InputAccessor;
+import com.fasterxml.jackson.core.format.MatchStrength;
+import com.fasterxml.jackson.core.io.IOContext;
+
+public class YAMLFactory extends JsonFactory
+{
+ private static final long serialVersionUID = 1171663157274350349L;
+
+ /**
+ * Name used to identify YAML format.
+ * (and returned by {@link #getFormatName()}
+ */
+ public final static String FORMAT_NAME_YAML = "YAML";
+
+ /**
+ * Bitfield (set of flags) of all parser features that are enabled
+ * by default.
+ */
+ protected final static int DEFAULT_YAML_PARSER_FEATURE_FLAGS = YAMLParser.Feature.collectDefaults();
+
+ /**
+ * Bitfield (set of flags) of all generator features that are enabled
+ * by default.
+ */
+ protected final static int DEFAULT_YAML_GENERATOR_FEATURE_FLAGS = YAMLGenerator.Feature.collectDefaults();
+
+ private final static byte UTF8_BOM_1 = (byte) 0xEF;
+ private final static byte UTF8_BOM_2 = (byte) 0xBB;
+ private final static byte UTF8_BOM_3 = (byte) 0xBF;
+
+ /*
+ /**********************************************************************
+ /* Configuration
+ /**********************************************************************
+ */
+
+ protected int _yamlParserFeatures = DEFAULT_YAML_PARSER_FEATURE_FLAGS;
+
+ protected int _yamlGeneratorFeatures = DEFAULT_YAML_GENERATOR_FEATURE_FLAGS;
+
+ /*
+ /**********************************************************************
+ /* Factory construction, configuration
+ /**********************************************************************
+ */
+
+ protected transient DumperOptions _outputOptions;
+
+ protected Integer[] _version;
+
+ /**
+ * Default constructor used to create factory instances.
+ * Creation of a factory instance is a light-weight operation,
+ * but it is still a good idea to reuse limited number of
+ * factory instances (and quite often just a single instance):
+ * factories are used as context for storing some reused
+ * processing objects (such as symbol tables parsers use)
+ * and this reuse only works within context of a single
+ * factory instance.
+ */
+ public YAMLFactory() { this(null); }
+
+ public YAMLFactory(ObjectCodec oc)
+ {
+ super(oc);
+ _yamlParserFeatures = DEFAULT_YAML_PARSER_FEATURE_FLAGS;
+ _yamlGeneratorFeatures = DEFAULT_YAML_GENERATOR_FEATURE_FLAGS;
+ _outputOptions = _defaultOptions();
+ DumperOptions.Version version = _outputOptions.getVersion();
+ _version = (version == null) ? null : version.getArray();
+ }
+
+ /**
+ * @since 2.2.1
+ */
+ public YAMLFactory(YAMLFactory src, ObjectCodec oc)
+ {
+ super(src, oc);
+ _outputOptions = src._outputOptions;
+ _version = src._version;
+ _yamlParserFeatures = src._yamlParserFeatures;
+ _yamlGeneratorFeatures = src._yamlGeneratorFeatures;
+ }
+
+ private static DumperOptions _defaultOptions()
+ {
+ DumperOptions opt = new DumperOptions();
+ // would we want canonical?
+ opt.setCanonical(false);
+ // if not, MUST specify flow styles
+ opt.setDefaultFlowStyle(FlowStyle.BLOCK);
+ return opt;
+
+ }
+
+ @Override
+ public YAMLFactory copy()
+ {
+ _checkInvalidCopy(YAMLFactory.class);
+ return new YAMLFactory(this, null);
+ }
+
+ /*
+ /**********************************************************
+ /* Serializable overrides
+ /**********************************************************
+ */
+
+ /**
+ * Method that we need to override to actually make restoration go
+ * through constructors etc.
+ * Also: must be overridden by sub-classes as well.
+ */
+ @Override
+ protected Object readResolve() {
+ // this is transient, need to explicitly recreate:
+ _outputOptions = _defaultOptions();
+ return new YAMLFactory(this, _objectCodec);
+ }
+
+ /*
+ /**********************************************************
+ /* Versioned
+ /**********************************************************
+ */
+
+ @Override
+ public Version version() {
+ return PackageVersion.VERSION;
+ }
+
+ /*
+ /**********************************************************
+ /* Format detection functionality (since 1.8)
+ /**********************************************************
+ */
+
+ @Override
+ public String getFormatName() {
+ return FORMAT_NAME_YAML;
+ }
+
+ /**
+ * Sub-classes need to override this method (as of 1.8)
+ */
+ @Override
+ public MatchStrength hasFormat(InputAccessor acc) throws IOException
+ {
+ /* Actually quite possible to do, thanks to (optional) "---"
+ * indicator we may be getting...
+ */
+ if (!acc.hasMoreBytes()) {
+ return MatchStrength.INCONCLUSIVE;
+ }
+ byte b = acc.nextByte();
+ // Very first thing, a UTF-8 BOM?
+ if (b == UTF8_BOM_1) { // yes, looks like UTF-8 BOM
+ if (!acc.hasMoreBytes()) {
+ return MatchStrength.INCONCLUSIVE;
+ }
+ if (acc.nextByte() != UTF8_BOM_2) {
+ return MatchStrength.NO_MATCH;
+ }
+ if (!acc.hasMoreBytes()) {
+ return MatchStrength.INCONCLUSIVE;
+ }
+ if (acc.nextByte() != UTF8_BOM_3) {
+ return MatchStrength.NO_MATCH;
+ }
+ if (!acc.hasMoreBytes()) {
+ return MatchStrength.INCONCLUSIVE;
+ }
+ b = acc.nextByte();
+ }
+ // as far as I know, leading space is NOT allowed before "---" marker?
+ if (b == '-' && (acc.hasMoreBytes() && acc.nextByte() == '-')
+ && (acc.hasMoreBytes() && acc.nextByte() == '-')) {
+ return MatchStrength.FULL_MATCH;
+ }
+ return MatchStrength.INCONCLUSIVE;
+ }
+
+ /*
+ /**********************************************************
+ /* Configuration, parser settings
+ /**********************************************************
+ */
+
+ /**
+ * Method for enabling or disabling specified parser feature
+ * (check {@link YAMLParser.Feature} for list of features)
+ */
+ public final YAMLFactory configure(YAMLParser.Feature f, boolean state)
+ {
+ if (state) {
+ enable(f);
+ } else {
+ disable(f);
+ }
+ return this;
+ }
+
+ /**
+ * Method for enabling specified parser feature
+ * (check {@link YAMLParser.Feature} for list of features)
+ */
+ public YAMLFactory enable(YAMLParser.Feature f) {
+ _yamlParserFeatures |= f.getMask();
+ return this;
+ }
+
+ /**
+ * Method for disabling specified parser features
+ * (check {@link YAMLParser.Feature} for list of features)
+ */
+ public YAMLFactory disable(YAMLParser.Feature f) {
+ _yamlParserFeatures &= ~f.getMask();
+ return this;
+ }
+
+ /**
+ * Checked whether specified parser feature is enabled.
+ */
+ public final boolean isEnabled(YAMLParser.Feature f) {
+ return (_yamlParserFeatures & f.getMask()) != 0;
+ }
+
+ /*
+ /**********************************************************
+ /* Configuration, generator settings
+ /**********************************************************
+ */
+
+ /**
+ * Method for enabling or disabling specified generator feature
+ * (check {@link YAMLGenerator.Feature} for list of features)
+ */
+ public final YAMLFactory configure(YAMLGenerator.Feature f, boolean state) {
+ if (state) {
+ enable(f);
+ } else {
+ disable(f);
+ }
+ return this;
+ }
+
+
+ /**
+ * Method for enabling specified generator features
+ * (check {@link YAMLGenerator.Feature} for list of features)
+ */
+ public YAMLFactory enable(YAMLGenerator.Feature f) {
+ _yamlGeneratorFeatures |= f.getMask();
+ return this;
+ }
+
+ /**
+ * Method for disabling specified generator feature
+ * (check {@link YAMLGenerator.Feature} for list of features)
+ */
+ public YAMLFactory disable(YAMLGenerator.Feature f) {
+ _yamlGeneratorFeatures &= ~f.getMask();
+ return this;
+ }
+
+ /**
+ * Check whether specified generator feature is enabled.
+ */
+ public final boolean isEnabled(YAMLGenerator.Feature f) {
+ return (_yamlGeneratorFeatures & f.getMask()) != 0;
+ }
+
+ /*
+ /**********************************************************
+ /* Overridden parser factory methods (for 2.1)
+ /**********************************************************
+ */
+
+ @Override
+ public YAMLParser createParser(String content)
+ throws IOException, JsonParseException
+ {
+ Reader r = new StringReader(content);
+ IOContext ctxt = _createContext(r, true); // true->own, can close
+ // [JACKSON-512]: allow wrapping with InputDecorator
+ if (_inputDecorator != null) {
+ r = _inputDecorator.decorate(ctxt, r);
+ }
+ return _createParser(r, ctxt);
+ }
+
+ @SuppressWarnings("resource")
+ @Override
+ public YAMLParser createParser(File f)
+ throws IOException, JsonParseException
+ {
+ IOContext ctxt = _createContext(f, true);
+ InputStream in = new FileInputStream(f);
+ // [JACKSON-512]: allow wrapping with InputDecorator
+ if (_inputDecorator != null) {
+ in = _inputDecorator.decorate(ctxt, in);
+ }
+ return _createParser(in, ctxt);
+ }
+
+ @Override
+ public YAMLParser createParser(URL url)
+ throws IOException, JsonParseException
+ {
+ IOContext ctxt = _createContext(url, true);
+ InputStream in = _optimizedStreamFromURL(url);
+ // [JACKSON-512]: allow wrapping with InputDecorator
+ if (_inputDecorator != null) {
+ in = _inputDecorator.decorate(ctxt, in);
+ }
+ return _createParser(in, ctxt);
+ }
+
+ @Override
+ public YAMLParser createParser(InputStream in)
+ throws IOException, JsonParseException
+ {
+ IOContext ctxt = _createContext(in, false);
+ // [JACKSON-512]: allow wrapping with InputDecorator
+ if (_inputDecorator != null) {
+ in = _inputDecorator.decorate(ctxt, in);
+ }
+ return _createParser(in, ctxt);
+ }
+
+ @Override
+ public JsonParser createParser(Reader r)
+ throws IOException, JsonParseException
+ {
+ IOContext ctxt = _createContext(r, false);
+ if (_inputDecorator != null) {
+ r = _inputDecorator.decorate(ctxt, r);
+ }
+ return _createParser(r, ctxt);
+ }
+
+ @Override
+ public YAMLParser createParser(byte[] data)
+ throws IOException, JsonParseException
+ {
+ IOContext ctxt = _createContext(data, true);
+ // [JACKSON-512]: allow wrapping with InputDecorator
+ if (_inputDecorator != null) {
+ InputStream in = _inputDecorator.decorate(ctxt, data, 0, data.length);
+ if (in != null) {
+ return _createParser(in, ctxt);
+ }
+ }
+ return _createParser(data, 0, data.length, ctxt);
+ }
+
+ @Override
+ public YAMLParser createParser(byte[] data, int offset, int len)
+ throws IOException, JsonParseException
+ {
+ IOContext ctxt = _createContext(data, true);
+ // [JACKSON-512]: allow wrapping with InputDecorator
+ if (_inputDecorator != null) {
+ InputStream in = _inputDecorator.decorate(ctxt, data, offset, len);
+ if (in != null) {
+ return _createParser(in, ctxt);
+ }
+ }
+ return _createParser(data, offset, len, ctxt);
+ }
+
+ /*
+ /**********************************************************
+ /* Overridden parser factory methods (2.0 and prior)
+ /**********************************************************
+ */
+
+ @Deprecated
+ @Override
+ public YAMLParser createJsonParser(String content)
+ throws IOException, JsonParseException
+ {
+ Reader r = new StringReader(content);
+ IOContext ctxt = _createContext(r, true); // true->own, can close
+ // [JACKSON-512]: allow wrapping with InputDecorator
+ if (_inputDecorator != null) {
+ r = _inputDecorator.decorate(ctxt, r);
+ }
+ return _createParser(r, ctxt);
+ }
+
+ @SuppressWarnings("resource")
+ @Deprecated
+ @Override
+ public YAMLParser createJsonParser(File f)
+ throws IOException, JsonParseException
+ {
+ IOContext ctxt = _createContext(f, true);
+ InputStream in = new FileInputStream(f);
+ // [JACKSON-512]: allow wrapping with InputDecorator
+ if (_inputDecorator != null) {
+ in = _inputDecorator.decorate(ctxt, in);
+ }
+ return _createParser(in, ctxt);
+ }
+
+ @Deprecated
+ @Override
+ public YAMLParser createJsonParser(URL url)
+ throws IOException, JsonParseException
+ {
+ IOContext ctxt = _createContext(url, true);
+ InputStream in = _optimizedStreamFromURL(url);
+ // [JACKSON-512]: allow wrapping with InputDecorator
+ if (_inputDecorator != null) {
+ in = _inputDecorator.decorate(ctxt, in);
+ }
+ return _createParser(in, ctxt);
+ }
+
+ @Deprecated
+ @Override
+ public YAMLParser createJsonParser(InputStream in)
+ throws IOException, JsonParseException
+ {
+ IOContext ctxt = _createContext(in, false);
+ // [JACKSON-512]: allow wrapping with InputDecorator
+ if (_inputDecorator != null) {
+ in = _inputDecorator.decorate(ctxt, in);
+ }
+ return _createParser(in, ctxt);
+ }
+
+ @Deprecated
+ @Override
+ public JsonParser createJsonParser(Reader r)
+ throws IOException, JsonParseException
+ {
+ IOContext ctxt = _createContext(r, false);
+ if (_inputDecorator != null) {
+ r = _inputDecorator.decorate(ctxt, r);
+ }
+ return _createParser(r, ctxt);
+ }
+
+ @Deprecated
+ @Override
+ public YAMLParser createJsonParser(byte[] data)
+ throws IOException, JsonParseException
+ {
+ IOContext ctxt = _createContext(data, true);
+ // [JACKSON-512]: allow wrapping with InputDecorator
+ if (_inputDecorator != null) {
+ InputStream in = _inputDecorator.decorate(ctxt, data, 0, data.length);
+ if (in != null) {
+ return _createParser(in, ctxt);
+ }
+ }
+ return _createParser(data, 0, data.length, ctxt);
+ }
+
+ @Deprecated
+ @Override
+ public YAMLParser createJsonParser(byte[] data, int offset, int len)
+ throws IOException, JsonParseException
+ {
+ IOContext ctxt = _createContext(data, true);
+ // [JACKSON-512]: allow wrapping with InputDecorator
+ if (_inputDecorator != null) {
+ InputStream in = _inputDecorator.decorate(ctxt, data, offset, len);
+ if (in != null) {
+ return _createParser(in, ctxt);
+ }
+ }
+ return _createParser(data, offset, len, ctxt);
+ }
+
+ /*
+ /**********************************************************
+ /* Overridden generator factory methods (2.1)
+ /**********************************************************
+ */
+
+ @Override
+ public YAMLGenerator createGenerator(OutputStream out, JsonEncoding enc) throws IOException
+ {
+ // false -> we won't manage the stream unless explicitly directed to
+ IOContext ctxt = _createContext(out, false);
+ // [JACKSON-512]: allow wrapping with _outputDecorator
+ if (_outputDecorator != null) {
+ out = _outputDecorator.decorate(ctxt, out);
+ }
+ return _createGenerator(_createWriter(out, JsonEncoding.UTF8, ctxt), ctxt);
+ }
+
+ @Override
+ public YAMLGenerator createGenerator(OutputStream out) throws IOException
+ {
+ // false -> we won't manage the stream unless explicitly directed to
+ IOContext ctxt = _createContext(out, false);
+ // [JACKSON-512]: allow wrapping with _outputDecorator
+ if (_outputDecorator != null) {
+ out = _outputDecorator.decorate(ctxt, out);
+ }
+ return _createGenerator(_createWriter(out, JsonEncoding.UTF8, ctxt), ctxt);
+ }
+
+ @Override
+ public YAMLGenerator createGenerator(Writer out) throws IOException
+ {
+ IOContext ctxt = _createContext(out, false);
+ // [JACKSON-512]: allow wrapping with _outputDecorator
+ if (_outputDecorator != null) {
+ out = _outputDecorator.decorate(ctxt, out);
+ }
+ return _createGenerator(out, ctxt);
+ }
+
+ /*
+ /**********************************************************
+ /* Overridden generator factory methods (2.0 and before)
+ /**********************************************************
+ */
+
+ @Deprecated
+ @Override
+ public YAMLGenerator createJsonGenerator(OutputStream out, JsonEncoding enc) throws IOException {
+ return createGenerator(out, enc);
+ }
+
+ @Deprecated
+ @Override
+ public YAMLGenerator createJsonGenerator(OutputStream out) throws IOException {
+ return createGenerator(out);
+ }
+
+ @Deprecated
+ @Override
+ public YAMLGenerator createJsonGenerator(Writer out) throws IOException {
+ return createGenerator(out);
+ }
+
+ /*
+ /******************************************************
+ /* Overridden internal factory methods
+ /******************************************************
+ */
+
+ //protected IOContext _createContext(Object srcRef, boolean resourceManaged)
+
+ @Override
+ protected YAMLParser _createParser(InputStream in, IOContext ctxt)
+ throws IOException, JsonParseException
+ {
+ Reader r = _createReader(in, null, ctxt);
+ return new YAMLParser(ctxt, _getBufferRecycler(), _parserFeatures, _yamlParserFeatures,
+ _objectCodec, r);
+ }
+
+ @Override
+ protected YAMLParser _createParser(Reader r, IOContext ctxt)
+ throws IOException, JsonParseException
+ {
+ return new YAMLParser(ctxt, _getBufferRecycler(), _parserFeatures, _yamlParserFeatures,
+ _objectCodec, r);
+ }
+
+ @Override
+ protected YAMLParser _createParser(byte[] data, int offset, int len, IOContext ctxt)
+ throws IOException, JsonParseException
+ {
+ Reader r = _createReader(data, offset, len, null, ctxt);
+ return new YAMLParser(ctxt, _getBufferRecycler(), _parserFeatures, _yamlParserFeatures,
+ _objectCodec, r);
+ }
+
+ @Override
+ protected YAMLParser _createJsonParser(InputStream in, IOContext ctxt)
+ throws IOException, JsonParseException
+ {
+ return _createParser(in, ctxt);
+ }
+
+ @Override
+ protected JsonParser _createJsonParser(Reader r, IOContext ctxt)
+ throws IOException, JsonParseException
+ {
+ return _createParser(r, ctxt);
+ }
+
+ @Override
+ protected YAMLParser _createJsonParser(byte[] data, int offset, int len, IOContext ctxt)
+ throws IOException, JsonParseException
+ {
+ return _createParser(data, offset, len, ctxt);
+ }
+
+ @Override
+ protected YAMLGenerator _createGenerator(Writer out, IOContext ctxt)
+ throws IOException
+ {
+ int feats = _yamlGeneratorFeatures;
+ YAMLGenerator gen = new YAMLGenerator(ctxt, _generatorFeatures, feats,
+ _objectCodec, out, _outputOptions, _version);
+ // any other initializations? No?
+ return gen;
+ }
+
+ @Override
+ protected YAMLGenerator _createJsonGenerator(Writer out, IOContext ctxt)
+ throws IOException
+ {
+ return _createGenerator(out, ctxt);
+ }
+
+ @Override
+ protected YAMLGenerator _createUTF8Generator(OutputStream out, IOContext ctxt) throws IOException {
+ return _createGenerator(new UTF8Writer(out), ctxt);
+ }
+
+ @Override
+ @Deprecated
+ protected YAMLGenerator _createUTF8JsonGenerator(OutputStream out, IOContext ctxt) throws IOException {
+ return _createUTF8Generator(out, ctxt);
+ }
+
+ @Override
+ protected Writer _createWriter(OutputStream out, JsonEncoding enc, IOContext ctxt) throws IOException
+ {
+ if (enc == JsonEncoding.UTF8) {
+ return new UTF8Writer(out);
+ }
+ return new OutputStreamWriter(out, enc.getJavaName());
+ }
+
+ /*
+ /**********************************************************
+ /* Internal methods
+ /**********************************************************
+ */
+
+ protected final Charset UTF8 = Charset.forName("UTF-8");
+
+ protected Reader _createReader(InputStream in, JsonEncoding enc, IOContext ctxt) throws IOException
+ {
+ if (enc == null) {
+ enc = JsonEncoding.UTF8;
+ }
+ // default to UTF-8 if encoding missing
+ if (enc == JsonEncoding.UTF8) {
+ boolean autoClose = ctxt.isResourceManaged() || this.isEnabled(JsonParser.Feature.AUTO_CLOSE_SOURCE);
+ return new UTF8Reader(in, autoClose);
+// return new InputStreamReader(in, UTF8);
+ }
+ return new InputStreamReader(in, enc.getJavaName());
+ }
+
+ protected Reader _createReader(byte[] data, int offset, int len,
+ JsonEncoding enc, IOContext ctxt) throws IOException
+ {
+ if (enc == null) {
+ enc = JsonEncoding.UTF8;
+ }
+ // default to UTF-8 if encoding missing
+ if (enc == null || enc == JsonEncoding.UTF8) {
+ return new UTF8Reader(data, offset, len, true);
+ }
+ ByteArrayInputStream in = new ByteArrayInputStream(data, offset, len);
+ return new InputStreamReader(in, enc.getJavaName());
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLGenerator.java b/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLGenerator.java
new file mode 100644
index 0000000..5687331
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLGenerator.java
@@ -0,0 +1,545 @@
+package com.fasterxml.jackson.dataformat.yaml;
+
+import java.io.*;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Arrays;
+import java.util.Collections;
+
+import org.yaml.snakeyaml.DumperOptions;
+import org.yaml.snakeyaml.emitter.Emitter;
+import org.yaml.snakeyaml.events.*;
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.base.GeneratorBase;
+import com.fasterxml.jackson.core.json.JsonWriteContext;
+import com.fasterxml.jackson.core.io.IOContext;
+
+public class YAMLGenerator extends GeneratorBase
+{
+ /**
+ * Enumeration that defines all togglable features for YAML generators
+ */
+ public enum Feature {
+ BOGUS(false) // placeholder
+ ;
+
+ protected final boolean _defaultState;
+ protected final int _mask;
+
+ /**
+ * Method that calculates bit set (flags) of all features that
+ * are enabled by default.
+ */
+ public static int collectDefaults()
+ {
+ int flags = 0;
+ for (Feature f : values()) {
+ if (f.enabledByDefault()) {
+ flags |= f.getMask();
+ }
+ }
+ return flags;
+ }
+
+ private Feature(boolean defaultState) {
+ _defaultState = defaultState;
+ _mask = (1 << ordinal());
+ }
+
+ public boolean enabledByDefault() { return _defaultState; }
+ public int getMask() { return _mask; }
+ };
+
+ protected final static long MIN_INT_AS_LONG = (long) Integer.MIN_VALUE;
+ protected final static long MAX_INT_AS_LONG = (long) Integer.MAX_VALUE;
+
+ /*
+ /**********************************************************
+ /* Configuration
+ /**********************************************************
+ */
+
+ final protected IOContext _ioContext;
+
+ /**
+ * Bit flag composed of bits that indicate which
+ * {@link YAMLGenerator.Feature}s
+ * are enabled.
+ */
+ protected int _yamlFeatures;
+
+ protected Writer _writer;
+
+ protected DumperOptions _outputOptions;
+
+ // for field names, leave out quotes
+ private final static Character STYLE_NAME = null;
+
+ // numbers, booleans, should use implicit
+ private final static Character STYLE_SCALAR = null;
+ // Strings quoted for fun
+ private final static Character STYLE_STRING = Character.valueOf('"');
+
+ // Which flow style to use for Base64? Maybe basic quoted?
+ private final static Character STYLE_BASE64 = Character.valueOf('"');
+
+ /*
+ /**********************************************************
+ /* Output state
+ /**********************************************************
+ */
+
+ protected Emitter _emitter;
+
+ /*
+ /**********************************************************
+ /* Life-cycle
+ /**********************************************************
+ */
+
+ public YAMLGenerator(IOContext ctxt, int jsonFeatures, int yamlFeatures,
+ ObjectCodec codec, Writer out,
+ DumperOptions outputOptions, Integer[] version
+ ) throws IOException
+ {
+ super(jsonFeatures, codec);
+ _ioContext = ctxt;
+ _yamlFeatures = yamlFeatures;
+ _writer = out;
+ _emitter = new Emitter(_writer, outputOptions);
+ _outputOptions = outputOptions;
+ // should we start output now, or try to defer?
+ _emitter.emit(new StreamStartEvent(null, null));
+ _emitter.emit(new DocumentStartEvent(null, null, /*explicit start*/ false,
+ version, /*tags*/ Collections.<String,String>emptyMap()));
+ }
+
+ /*
+ /**********************************************************
+ /* Versioned
+ /**********************************************************
+ */
+
+ @Override
+ public Version version() {
+ return PackageVersion.VERSION;
+ }
+
+ /*
+ /**********************************************************
+ /* Overridden methods, configuration
+ /**********************************************************
+ */
+
+ /**
+ * Not sure what to do here; could reset indentation to some value maybe?
+ */
+ @Override
+ public YAMLGenerator useDefaultPrettyPrinter()
+ {
+ return this;
+ }
+
+ /**
+ * Not sure what to do here; will always indent, but uses
+ * YAML-specific settings etc.
+ */
+ @Override
+ public YAMLGenerator setPrettyPrinter(PrettyPrinter pp) {
+ return this;
+ }
+
+ @Override
+ public Object getOutputTarget() {
+ return _writer;
+ }
+
+ @Override
+ public boolean canUseSchema(FormatSchema schema) {
+ return false;
+ }
+
+ //@Override public void setSchema(FormatSchema schema)
+
+ /*
+ /**********************************************************************
+ /* Overridden methods; writing field names
+ /**********************************************************************
+ */
+
+ /* And then methods overridden to make final, streamline some
+ * aspects...
+ */
+
+ @Override
+ public final void writeFieldName(String name) throws IOException, JsonGenerationException
+ {
+ if (_writeContext.writeFieldName(name) == JsonWriteContext.STATUS_EXPECT_VALUE) {
+ _reportError("Can not write a field name, expecting a value");
+ }
+ _writeFieldName(name);
+ }
+
+ @Override
+ public final void writeFieldName(SerializableString name)
+ throws IOException, JsonGenerationException
+ {
+ // Object is a value, need to verify it's allowed
+ if (_writeContext.writeFieldName(name.getValue()) == JsonWriteContext.STATUS_EXPECT_VALUE) {
+ _reportError("Can not write a field name, expecting a value");
+ }
+ _writeFieldName(name.getValue());
+ }
+
+ @Override
+ public final void writeStringField(String fieldName, String value)
+ throws IOException, JsonGenerationException
+ {
+ if (_writeContext.writeFieldName(fieldName) == JsonWriteContext.STATUS_EXPECT_VALUE) {
+ _reportError("Can not write a field name, expecting a value");
+ }
+ _writeFieldName(fieldName);
+ writeString(value);
+ }
+
+ private final void _writeFieldName(String name)
+ throws IOException, JsonGenerationException
+ {
+ _writeScalar(name, "string", STYLE_NAME);
+ }
+
+ /*
+ /**********************************************************
+ /* Extended API, configuration
+ /**********************************************************
+ */
+
+ public YAMLGenerator enable(Feature f) {
+ _yamlFeatures |= f.getMask();
+ return this;
+ }
+
+ public YAMLGenerator disable(Feature f) {
+ _yamlFeatures &= ~f.getMask();
+ return this;
+ }
+
+ public final boolean isEnabled(Feature f) {
+ return (_yamlFeatures & f.getMask()) != 0;
+ }
+
+ public YAMLGenerator configure(Feature f, boolean state) {
+ if (state) {
+ enable(f);
+ } else {
+ disable(f);
+ }
+ return this;
+ }
+
+ /*
+ /**********************************************************
+ /* Public API: low-level I/O
+ /**********************************************************
+ */
+
+ @Override
+ public final void flush() throws IOException
+ {
+ _writer.flush();
+ }
+
+ @Override
+ public void close() throws IOException
+ {
+ _emitter.emit(new DocumentEndEvent(null, null, false));
+ _emitter.emit(new StreamEndEvent(null, null));
+ super.close();
+ _writer.close();
+ }
+
+ /*
+ /**********************************************************
+ /* Public API: structural output
+ /**********************************************************
+ */
+
+ @Override
+ public final void writeStartArray() throws IOException, JsonGenerationException
+ {
+ _verifyValueWrite("start an array");
+ _writeContext = _writeContext.createChildArrayContext();
+ Boolean style = _outputOptions.getDefaultFlowStyle().getStyleBoolean();
+ // note: can NOT be implicit, to avoid having to specify tag
+ _emitter.emit(new SequenceStartEvent(/*anchor*/null, /*tag*/null,
+ /*implicit*/ true, null, null, style));
+ }
+
+ @Override
+ public final void writeEndArray() throws IOException, JsonGenerationException
+ {
+ if (!_writeContext.inArray()) {
+ _reportError("Current context not an ARRAY but "+_writeContext.getTypeDesc());
+ }
+ _writeContext = _writeContext.getParent();
+ _emitter.emit(new SequenceEndEvent(null, null));
+ }
+
+ @Override
+ public final void writeStartObject() throws IOException, JsonGenerationException
+ {
+ _verifyValueWrite("start an object");
+ _writeContext = _writeContext.createChildObjectContext();
+ Boolean style = _outputOptions.getDefaultFlowStyle().getStyleBoolean();
+ // note: can NOT be implicit, to avoid having to specify tag
+ _emitter.emit(new MappingStartEvent(/* anchor */null, null, //TAG_OBJECT,
+ /*implicit*/true, null, null, style));
+ }
+
+ @Override
+ public final void writeEndObject() throws IOException, JsonGenerationException
+ {
+ if (!_writeContext.inObject()) {
+ _reportError("Current context not an object but "+_writeContext.getTypeDesc());
+ }
+ _writeContext = _writeContext.getParent();
+ _emitter.emit(new MappingEndEvent(null, null));
+ }
+
+ /*
+ /**********************************************************
+ /* Output method implementations, textual
+ /**********************************************************
+ */
+
+ @Override
+ public void writeString(String text) throws IOException,JsonGenerationException
+ {
+ if (text == null) {
+ writeNull();
+ return;
+ }
+ _verifyValueWrite("write String value");
+ _writeScalar(text, "string", STYLE_STRING);
+ }
+
+ @Override
+ public void writeString(char[] text, int offset, int len) throws IOException, JsonGenerationException
+ {
+ writeString(new String(text, offset, len));
+ }
+
+ @Override
+ public final void writeString(SerializableString sstr)
+ throws IOException, JsonGenerationException
+ {
+ writeString(sstr.toString());
+ }
+
+ @Override
+ public void writeRawUTF8String(byte[] text, int offset, int len)
+ throws IOException, JsonGenerationException
+ {
+ _reportUnsupportedOperation();
+ }
+
+ @Override
+ public final void writeUTF8String(byte[] text, int offset, int len)
+ throws IOException, JsonGenerationException
+ {
+ writeString(new String(text, offset, len, "UTF-8"));
+ }
+
+ /*
+ /**********************************************************
+ /* Output method implementations, unprocessed ("raw")
+ /**********************************************************
+ */
+
+ @Override
+ public void writeRaw(String text) throws IOException, JsonGenerationException {
+ _reportUnsupportedOperation();
+ }
+
+ @Override
+ public void writeRaw(String text, int offset, int len) throws IOException, JsonGenerationException {
+ _reportUnsupportedOperation();
+ }
+
+ @Override
+ public void writeRaw(char[] text, int offset, int len) throws IOException, JsonGenerationException {
+ _reportUnsupportedOperation();
+ }
+
+ @Override
+ public void writeRaw(char c) throws IOException, JsonGenerationException {
+ _reportUnsupportedOperation();
+ }
+
+ @Override
+ public void writeRawValue(String text) throws IOException, JsonGenerationException {
+ _reportUnsupportedOperation();
+ }
+
+ @Override
+ public void writeRawValue(String text, int offset, int len) throws IOException, JsonGenerationException {
+ _reportUnsupportedOperation();
+ }
+
+ @Override
+ public void writeRawValue(char[] text, int offset, int len) throws IOException, JsonGenerationException {
+ _reportUnsupportedOperation();
+ }
+
+ /*
+ /**********************************************************
+ /* Output method implementations, base64-encoded binary
+ /**********************************************************
+ */
+
+ @Override
+ public void writeBinary(Base64Variant b64variant, byte[] data, int offset, int len) throws IOException, JsonGenerationException
+ {
+ if (data == null) {
+ writeNull();
+ return;
+ }
+ _verifyValueWrite("write Binary value");
+ // ok, better just Base64 encode as a String...
+ if (offset > 0 || (offset+len) != data.length) {
+ data = Arrays.copyOfRange(data, offset, offset+len);
+ }
+ String encoded = b64variant.encode(data);
+ _writeScalar(encoded, "byte[]", STYLE_BASE64);
+ }
+
+ /*
+ /**********************************************************
+ /* Output method implementations, primitive
+ /**********************************************************
+ */
+
+ @Override
+ public void writeBoolean(boolean state) throws IOException, JsonGenerationException
+ {
+ _verifyValueWrite("write boolean value");
+ _writeScalar(state ? "true" : "false", "bool", STYLE_SCALAR);
+ }
+
+ @Override
+ public void writeNull() throws IOException, JsonGenerationException
+ {
+ _verifyValueWrite("write null value");
+ // no real type for this, is there?
+ _writeScalar("null", "object", STYLE_SCALAR);
+ }
+
+ @Override
+ public void writeNumber(int i) throws IOException, JsonGenerationException
+ {
+ _verifyValueWrite("write number");
+ _writeScalar(String.valueOf(i), "int", STYLE_SCALAR);
+ }
+
+ @Override
+ public void writeNumber(long l) throws IOException, JsonGenerationException
+ {
+ // First: maybe 32 bits is enough?
+ if (l <= MAX_INT_AS_LONG && l >= MIN_INT_AS_LONG) {
+ writeNumber((int) l);
+ return;
+ }
+ _verifyValueWrite("write number");
+ _writeScalar(String.valueOf(l), "long", STYLE_SCALAR);
+ }
+
+ @Override
+ public void writeNumber(BigInteger v) throws IOException, JsonGenerationException
+ {
+ if (v == null) {
+ writeNull();
+ return;
+ }
+ _verifyValueWrite("write number");
+ _writeScalar(String.valueOf(v.toString()), "java.math.BigInteger", STYLE_SCALAR);
+ }
+
+ @Override
+ public void writeNumber(double d) throws IOException, JsonGenerationException
+ {
+ _verifyValueWrite("write number");
+ _writeScalar(String.valueOf(d), "double", STYLE_SCALAR);
+ }
+
+ @Override
+ public void writeNumber(float f) throws IOException, JsonGenerationException
+ {
+ _verifyValueWrite("write number");
+ _writeScalar(String.valueOf(f), "float", STYLE_SCALAR);
+ }
+
+ @Override
+ public void writeNumber(BigDecimal dec) throws IOException, JsonGenerationException
+ {
+ if (dec == null) {
+ writeNull();
+ return;
+ }
+ _verifyValueWrite("write number");
+ _writeScalar(dec.toString(), "java.math.BigDecimal", STYLE_SCALAR);
+ }
+
+ @Override
+ public void writeNumber(String encodedValue) throws IOException,JsonGenerationException, UnsupportedOperationException
+ {
+ if (encodedValue == null) {
+ writeNull();
+ return;
+ }
+ _verifyValueWrite("write number");
+ _writeScalar(encodedValue, "number", STYLE_SCALAR);
+ }
+
+ /*
+ /**********************************************************
+ /* Implementations for methods from base class
+ /**********************************************************
+ */
+
+ @Override
+ protected final void _verifyValueWrite(String typeMsg)
+ throws IOException, JsonGenerationException
+ {
+ int status = _writeContext.writeValue();
+ if (status == JsonWriteContext.STATUS_EXPECT_NAME) {
+ _reportError("Can not "+typeMsg+", expecting field name");
+ }
+ }
+
+ @Override
+ protected void _releaseBuffers() {
+ // nothing special to do...
+ }
+
+ /*
+ /**********************************************************
+ /* Internal methods
+ /**********************************************************
+ */
+
+ // Implicit means that (type) tags won't be shown, right?
+ private final static ImplicitTuple DEFAULT_IMPLICIT = new ImplicitTuple(true, true);
+
+ protected void _writeScalar(String value, String type, Character style) throws IOException
+ {
+ _emitter.emit(_scalarEvent(value, type, style));
+ }
+
+ protected ScalarEvent _scalarEvent(String value, String tag, Character style)
+ {
+ // 'type' can be used as 'tag'... but should we?
+ return new ScalarEvent(null, null, DEFAULT_IMPLICIT, value,
+ null, null, style);
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLParser.java b/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLParser.java
new file mode 100644
index 0000000..756cdea
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLParser.java
@@ -0,0 +1,743 @@
+package com.fasterxml.jackson.dataformat.yaml;
+
+import java.io.*;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.regex.Pattern;
+
+import org.yaml.snakeyaml.error.Mark;
+import org.yaml.snakeyaml.events.*;
+import org.yaml.snakeyaml.parser.ParserImpl;
+import org.yaml.snakeyaml.reader.StreamReader;
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.base.ParserBase;
+import com.fasterxml.jackson.core.io.IOContext;
+import com.fasterxml.jackson.core.util.BufferRecycler;
+import com.fasterxml.jackson.core.util.ByteArrayBuilder;
+
+/**
+ * {@link JsonParser} implementation used to expose YAML documents
+ * in form that allows other Jackson functionality to deal
+ * with it.
+ */
+public class YAMLParser
+ extends ParserBase
+{
+ /**
+ * Enumeration that defines all togglable features for YAML parsers.
+ */
+ public enum Feature {
+
+ BOGUS(false)
+ ;
+
+ final boolean _defaultState;
+ final int _mask;
+
+ /**
+ * Method that calculates bit set (flags) of all features that
+ * are enabled by default.
+ */
+ public static int collectDefaults()
+ {
+ int flags = 0;
+ for (Feature f : values()) {
+ if (f.enabledByDefault()) {
+ flags |= f.getMask();
+ }
+ }
+ return flags;
+ }
+
+ private Feature(boolean defaultState) {
+ _defaultState = defaultState;
+ _mask = (1 << ordinal());
+ }
+
+ public boolean enabledByDefault() { return _defaultState; }
+ public int getMask() { return _mask; }
+ }
+
+ // note: does NOT include '0', handled separately
+// private final static Pattern PATTERN_INT = Pattern.compile("-?[1-9][0-9]*");
+
+ /**
+ * We will use pattern that is bit stricter than YAML definition,
+ * but we will still allow things like extra '_' in there.
+ */
+ private final static Pattern PATTERN_FLOAT = Pattern.compile(
+ "[-+]?([0-9][0-9_]*)?\\.[0-9]*([eE][-+][0-9]+)?");
+
+ /*
+ /**********************************************************************
+ /* Configuration
+ /**********************************************************************
+ */
+
+ /**
+ * Codec used for data binding when (if) requested.
+ */
+ protected ObjectCodec _objectCodec;
+
+ protected int _yamlFeatures;
+
+ /*
+ /**********************************************************************
+ /* Input sources
+ /**********************************************************************
+ */
+
+ /**
+ * Need to keep track of underlying {@link Reader} to be able to
+ * auto-close it (if required to)
+ */
+ protected Reader _reader;
+
+ protected ParserImpl _yamlParser;
+ /*
+ /**********************************************************************
+ /* State
+ /**********************************************************************
+ */
+
+ /**
+ * Keep track of the last event read, to get access to Location info
+ */
+ protected Event _lastEvent;
+
+ /**
+ * We need to keep track of text values.
+ */
+ protected String _textValue;
+
+ /**
+ * Let's also have a local copy of the current field name
+ */
+ protected String _currentFieldName;
+
+ /**
+ * Flag that is set when current token was derived from an Alias
+ * (reference to another value's anchor)
+ *
+ * @since 2.1
+ */
+ protected boolean _currentIsAlias;
+
+ /**
+ * Anchor for the value that parser currently points to: in case of
+ * structured types, value whose first token current token is.
+ */
+ protected String _currentAnchor;
+
+ /*
+ /**********************************************************************
+ /* Life-cycle
+ /**********************************************************************
+ */
+
+ public YAMLParser(IOContext ctxt, BufferRecycler br,
+ int parserFeatures, int csvFeatures,
+ ObjectCodec codec, Reader reader)
+ {
+ super(ctxt, parserFeatures);
+ _objectCodec = codec;
+ _yamlFeatures = csvFeatures;
+ _reader = reader;
+ _yamlParser = new ParserImpl(new StreamReader(reader));
+ }
+
+
+ @Override
+ public ObjectCodec getCodec() {
+ return _objectCodec;
+ }
+
+ @Override
+ public void setCodec(ObjectCodec c) {
+ _objectCodec = c;
+ }
+
+ /*
+ /**********************************************************
+ /* Extended YAML-specific API
+ /**********************************************************
+ */
+
+ /**
+ * Method that can be used to check whether current token was
+ * created from YAML Alias token (reference to an anchor).
+ *
+ * @since 2.1
+ */
+ public boolean isCurrentAlias() {
+ return _currentIsAlias;
+ }
+
+ /**
+ * Method that can be used to check if the current token has an
+ * associated anchor (id to reference via Alias)
+ *
+ * @since 2.1
+ */
+ public String getCurrentAnchor() {
+ return _currentAnchor;
+ }
+
+ /*
+ /**********************************************************
+ /* Versioned
+ /**********************************************************
+ */
+
+ @Override
+ public Version version() {
+ return PackageVersion.VERSION;
+ }
+
+ /*
+ /**********************************************************
+ /* ParserBase method impls
+ /**********************************************************
+ */
+
+
+ @Override
+ protected boolean loadMore() throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected void _finishString() throws IOException, JsonParseException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected void _closeInput() throws IOException {
+ _reader.close();
+ }
+
+ /*
+ /**********************************************************
+ /* Overridden methods
+ /**********************************************************
+ */
+
+ /*
+ /***************************************************
+ /* Public API, configuration
+ /***************************************************
+ */
+
+ /**
+ * Method for enabling specified CSV feature
+ * (check {@link Feature} for list of features)
+ */
+ public JsonParser enable(YAMLParser.Feature f)
+ {
+ _yamlFeatures |= f.getMask();
+ return this;
+ }
+
+ /**
+ * Method for disabling specified CSV feature
+ * (check {@link Feature} for list of features)
+ */
+ public JsonParser disable(YAMLParser.Feature f)
+ {
+ _yamlFeatures &= ~f.getMask();
+ return this;
+ }
+
+ /**
+ * Method for enabling or disabling specified CSV feature
+ * (check {@link Feature} for list of features)
+ */
+ public JsonParser configure(YAMLParser.Feature f, boolean state)
+ {
+ if (state) {
+ enable(f);
+ } else {
+ disable(f);
+ }
+ return this;
+ }
+
+ /**
+ * Method for checking whether specified CSV {@link Feature}
+ * is enabled.
+ */
+ public boolean isEnabled(YAMLParser.Feature f) {
+ return (_yamlFeatures & f.getMask()) != 0;
+ }
+
+// @Override public CsvSchema getSchema()
+
+ /*
+ /**********************************************************
+ /* Location info
+ /**********************************************************
+ */
+
+ @Override
+ public JsonLocation getTokenLocation()
+ {
+ if (_lastEvent == null) {
+ return JsonLocation.NA;
+ }
+ return _locationFor(_lastEvent.getStartMark());
+ }
+
+ @Override
+ public JsonLocation getCurrentLocation() {
+ // can assume we are at the end of token now...
+ if (_lastEvent == null) {
+ return JsonLocation.NA;
+ }
+ return _locationFor(_lastEvent.getEndMark());
+ }
+
+ protected JsonLocation _locationFor(Mark m)
+ {
+ if (m == null) {
+ return new JsonLocation(_ioContext.getSourceReference(),
+ -1, -1, -1);
+ }
+ return new JsonLocation(_ioContext.getSourceReference(),
+ -1,
+ m.getLine() + 1, // from 0- to 1-based
+ m.getColumn() + 1); // ditto
+ }
+
+ // Note: SHOULD override 'getTokenLineNr', 'getTokenColumnNr', but those are final in 2.0
+
+ /*
+ /**********************************************************
+ /* Parsing
+ /**********************************************************
+ */
+
+ @Override
+ public JsonToken nextToken() throws IOException, JsonParseException
+ {
+ _currentIsAlias = false;
+ _binaryValue = null;
+ _currentAnchor = null;
+ if (_closed) {
+ return null;
+ }
+
+ while (true) {
+ Event evt = _yamlParser.getEvent();
+ // is null ok? Assume it is, for now, consider to be same as end-of-doc
+ if (evt == null) {
+ return (_currToken = null);
+ }
+
+ _lastEvent = evt;
+
+ /* One complication: field names are only inferred from the
+ * fact that we are in Object context...
+ */
+ if (_parsingContext.inObject() && _currToken != JsonToken.FIELD_NAME) {
+ if (!evt.is(Event.ID.Scalar)) {
+ // end is fine
+ if (evt.is(Event.ID.MappingEnd)) {
+ if (!_parsingContext.inObject()) { // sanity check is optional, but let's do it for now
+ _reportMismatchedEndMarker('}', ']');
+ }
+ _parsingContext = _parsingContext.getParent();
+ return (_currToken = JsonToken.END_OBJECT);
+ }
+ _reportError("Expected a field name (Scalar value in YAML), got this instead: "+evt);
+ }
+ ScalarEvent scalar = (ScalarEvent) evt;
+ String name = scalar.getValue();
+ _currentFieldName = name;
+ _parsingContext.setCurrentName(name);
+ _currentAnchor = scalar.getAnchor();
+ return (_currToken = JsonToken.FIELD_NAME);
+ }
+ // Ugh. Why not expose id, to be able to Switch?
+
+ // scalar values are probably the commonest:
+ if (evt.is(Event.ID.Scalar)) {
+ return (_currToken = _decodeScalar((ScalarEvent) evt));
+ }
+
+ // followed by maps, then arrays
+ if (evt.is(Event.ID.MappingStart)) {
+ Mark m = evt.getStartMark();
+ _currentAnchor = ((NodeEvent)evt).getAnchor();
+ _parsingContext = _parsingContext.createChildObjectContext(m.getLine(), m.getColumn());
+ return (_currToken = JsonToken.START_OBJECT);
+ }
+ if (evt.is(Event.ID.MappingEnd)) { // actually error; can not have map-end here
+ _reportError("Not expecting END_OBJECT but a value");
+ }
+ if (evt.is(Event.ID.SequenceStart)) {
+ Mark m = evt.getStartMark();
+ _currentAnchor = ((NodeEvent)evt).getAnchor();
+ _parsingContext = _parsingContext.createChildArrayContext(m.getLine(), m.getColumn());
+ return (_currToken = JsonToken.START_ARRAY);
+ }
+ if (evt.is(Event.ID.SequenceEnd)) {
+ if (!_parsingContext.inArray()) { // sanity check is optional, but let's do it for now
+ _reportMismatchedEndMarker(']', '}');
+ }
+ _parsingContext = _parsingContext.getParent();
+ return (_currToken = JsonToken.END_ARRAY);
+ }
+
+ // after this, less common tokens:
+
+ if (evt.is(Event.ID.DocumentEnd)) {
+ // logical end of doc; fine. Two choices; either skip, or
+ // return null as marker. Do latter, for now. But do NOT close.
+ return (_currToken = null);
+ }
+ if (evt.is(Event.ID.DocumentStart)) {
+ // does this matter? Shouldn't, should it?
+ continue;
+ }
+ if (evt.is(Event.ID.Alias)) {
+ AliasEvent alias = (AliasEvent) evt;
+ _currentIsAlias = true;
+ _textValue = alias.getAnchor();
+ // for now, nothing to do: in future, maybe try to expose as ObjectIds?
+ return (_currToken = JsonToken.VALUE_STRING);
+ }
+ if (evt.is(Event.ID.StreamEnd)) { // end-of-input; force closure
+ close();
+ return (_currToken = null);
+ }
+ if (evt.is(Event.ID.StreamStart)) { // useless, skip
+ continue;
+ }
+ }
+ }
+
+ protected JsonToken _decodeScalar(ScalarEvent scalar)
+ {
+ String value = scalar.getValue();
+ _textValue = value;
+ // we may get an explicit tag, if so, use for corroborating...
+ String typeTag = scalar.getTag();
+ final int len = value.length();
+
+ if (typeTag == null) { // no, implicit
+ // We only try to parse the string value if it is in the plain flow style.
+ // The API for ScalarEvent.getStyle() might be read as a null being returned
+ // in the plain flow style, but debugging shows the null-byte character, so
+ // we support both.
+ Character style = scalar.getStyle();
+
+ if ((style == null || style == '\u0000') && len > 0) {
+ char c = value.charAt(0);
+ switch (c) {
+ case 'n':
+ if ("null".equals(value)) {
+ return JsonToken.VALUE_NULL;
+ }
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case '+':
+ case '-':
+ case '.':
+ JsonToken t = _decodeNumberScalar(value, len);
+ if (t != null) {
+ return t;
+ }
+ }
+ Boolean B = _matchYAMLBoolean(value, len);
+ if (B != null) {
+ return B.booleanValue() ? JsonToken.VALUE_TRUE : JsonToken.VALUE_FALSE;
+ }
+ }
+ } else { // yes, got type tag
+ // canonical values by YAML are actually 'y' and 'n'; but plenty more unofficial:
+ if ("bool".equals(typeTag)) { // must be "true" or "false"
+ Boolean B = _matchYAMLBoolean(value, len);
+ if (B != null) {
+ return B.booleanValue() ? JsonToken.VALUE_TRUE : JsonToken.VALUE_FALSE;
+ }
+ } else if ("int".equals(typeTag)) {
+ return JsonToken.VALUE_NUMBER_INT;
+ } else if ("float".equals(typeTag)) {
+ return JsonToken.VALUE_NUMBER_FLOAT;
+ } else if ("null".equals(typeTag)) {
+ return JsonToken.VALUE_NULL;
+ }
+ }
+
+ // any way to figure out actual type? No?
+ return (_currToken = JsonToken.VALUE_STRING);
+ }
+
+ protected Boolean _matchYAMLBoolean(String value, int len)
+ {
+ switch (len) {
+ case 1:
+ switch (value.charAt(0)) {
+ case 'y': case 'Y': return Boolean.TRUE;
+ case 'n': case 'N': return Boolean.FALSE;
+ }
+ break;
+ case 2:
+ if ("no".equalsIgnoreCase(value)) return Boolean.FALSE;
+ if ("on".equalsIgnoreCase(value)) return Boolean.TRUE;
+ break;
+ case 3:
+ if ("yes".equalsIgnoreCase(value)) return Boolean.TRUE;
+ if ("off".equalsIgnoreCase(value)) return Boolean.FALSE;
+ break;
+ case 4:
+ if ("true".equalsIgnoreCase(value)) return Boolean.TRUE;
+ break;
+ case 5:
+ if ("false".equalsIgnoreCase(value)) return Boolean.FALSE;
+ break;
+ }
+ return null;
+ }
+
+ protected JsonToken _decodeNumberScalar(String value, final int len)
+ {
+ if ("0".equals(value)) { // special case for regexp (can't take minus etc)
+ _numberNegative = false;
+ _numberInt = 0;
+ _numTypesValid = NR_INT;
+ return JsonToken.VALUE_NUMBER_INT;
+ }
+ /* 05-May-2012, tatu: Turns out this is a hot spot; so let's write it
+ * out and avoid regexp overhead...
+ */
+ //if (PATTERN_INT.matcher(value).matches()) {
+ int i;
+ if (value.charAt(0) == '-') {
+ _numberNegative = true;
+ i = 1;
+ if (len == 1) {
+ return null;
+ }
+ } else {
+ _numberNegative = false;
+ i = 0;
+ }
+ while (true) {
+ int c = value.charAt(i);
+ if (c > '9' || c < '0') {
+ break;
+ }
+ if (++i == len) {
+ _numTypesValid = 0;
+ return JsonToken.VALUE_NUMBER_INT;
+ }
+ }
+ if (PATTERN_FLOAT.matcher(value).matches()) {
+ _numTypesValid = 0;
+ return JsonToken.VALUE_NUMBER_FLOAT;
+ }
+ return null;
+ }
+
+ /*
+ /**********************************************************
+ /* String value handling
+ /**********************************************************
+ */
+
+ // For now we do not store char[] representation...
+ @Override
+ public boolean hasTextCharacters() {
+ return false;
+ }
+
+ @Override
+ public String getText() throws IOException, JsonParseException
+ {
+ if (_currToken == JsonToken.VALUE_STRING) {
+ return _textValue;
+ }
+ if (_currToken == JsonToken.FIELD_NAME) {
+ return _currentFieldName;
+ }
+ if (_currToken != null) {
+ if (_currToken.isScalarValue()) {
+ return _textValue;
+ }
+ return _currToken.asString();
+ }
+ return null;
+ }
+
+ @Override
+ public String getCurrentName() throws IOException, JsonParseException
+ {
+ if (_currToken == JsonToken.FIELD_NAME) {
+ return _currentFieldName;
+ }
+ return super.getCurrentName();
+ }
+
+ @Override
+ public char[] getTextCharacters() throws IOException, JsonParseException {
+ String text = getText();
+ return (text == null) ? null : text.toCharArray();
+ }
+
+ @Override
+ public int getTextLength() throws IOException, JsonParseException {
+ String text = getText();
+ return (text == null) ? 0 : text.length();
+ }
+
+ @Override
+ public int getTextOffset() throws IOException, JsonParseException {
+ return 0;
+ }
+
+ /*
+ /**********************************************************************
+ /* Binary (base64)
+ /**********************************************************************
+ */
+
+ @Override
+ public Object getEmbeddedObject() throws IOException, JsonParseException {
+ return null;
+ }
+
+ @Override
+ public byte[] getBinaryValue(Base64Variant variant) throws IOException, JsonParseException
+ {
+ if (_binaryValue == null) {
+ if (_currToken != JsonToken.VALUE_STRING) {
+ _reportError("Current token ("+_currToken+") not VALUE_STRING, can not access as binary");
+ }
+ ByteArrayBuilder builder = _getByteArrayBuilder();
+ _decodeBase64(getText(), builder, variant);
+ _binaryValue = builder.toByteArray();
+ }
+ return _binaryValue;
+ }
+
+ /*
+ /**********************************************************************
+ /* Number accessors
+ /**********************************************************************
+ */
+
+ @Override
+ protected void _parseNumericValue(int expType)
+ throws IOException, JsonParseException
+ {
+ // Int or float?
+ if (_currToken == JsonToken.VALUE_NUMBER_INT) {
+ int len = _textValue.length();
+ if (_numberNegative) {
+ len--;
+ }
+ if (len <= 9) { // definitely fits in int
+ _numberInt = Integer.parseInt(_textValue);
+ _numTypesValid = NR_INT;
+ return;
+ }
+ if (len <= 18) { // definitely fits AND is easy to parse using 2 int parse calls
+ long l = Long.parseLong(_textValue);
+ // [JACKSON-230] Could still fit in int, need to check
+ if (len == 10) {
+ if (_numberNegative) {
+ if (l >= Integer.MIN_VALUE) {
+ _numberInt = (int) l;
+ _numTypesValid = NR_INT;
+ return;
+ }
+ } else {
+ if (l <= Integer.MAX_VALUE) {
+ _numberInt = (int) l;
+ _numTypesValid = NR_INT;
+ return;
+ }
+ }
+ }
+ _numberLong = l;
+ _numTypesValid = NR_LONG;
+ return;
+ }
+ // !!! TODO: implement proper bounds checks; now we'll just use BigInteger for convenience
+ try {
+ BigInteger n = new BigInteger(_textValue);
+ // Could still fit in a long, need to check
+ if (len == 19 && n.bitLength() <= 63) {
+ _numberLong = n.longValue();
+ _numTypesValid = NR_LONG;
+ return;
+ }
+ _numberBigInt = n;
+ _numTypesValid = NR_BIGINT;
+ return;
+ } catch (NumberFormatException nex) {
+ // Can this ever occur? Due to overflow, maybe?
+ _wrapError("Malformed numeric value '"+_textValue+"'", nex);
+ }
+ }
+ if (_currToken == JsonToken.VALUE_NUMBER_FLOAT) {
+ // related to [Issue-4]: strip out optional underscores, if any:
+ String str = _cleanYamlDouble(_textValue);
+ try {
+ if (expType == NR_BIGDECIMAL) {
+ _numberBigDecimal = new BigDecimal(str);
+ _numTypesValid = NR_BIGDECIMAL;
+ } else {
+ // Otherwise double has to do
+ _numberDouble = Double.parseDouble(str);
+ _numTypesValid = NR_DOUBLE;
+ }
+ } catch (NumberFormatException nex) {
+ // Can this ever occur? Due to overflow, maybe?
+ _wrapError("Malformed numeric value '"+str+"'", nex);
+ }
+ return;
+ }
+ _reportError("Current token ("+_currToken+") not numeric, can not use numeric value accessors");
+ }
+
+ /*
+ /**********************************************************************
+ /* Internal methods
+ /**********************************************************************
+ */
+
+ /**
+ * Helper method used to clean up YAML floating-point value so it can be parsed
+ * using standard JDK classes.
+ * Currently this just means stripping out optional underscores.
+ */
+ private String _cleanYamlDouble(String str)
+ {
+ final int len = str.length();
+ int ix = str.indexOf('_');
+ if (ix < 0 || len == 0) {
+ return str;
+ }
+ StringBuilder sb = new StringBuilder(len);
+ // first: do we have a leading plus sign to skip?
+ int i = (str.charAt(0) == '+') ? 1 : 0;
+ for (; i < len; ++i) {
+ char c = str.charAt(i);
+ if (c != '_') {
+ sb.append(c);
+ }
+ }
+ return sb.toString();
+ }
+}
diff --git a/src/main/resources/META-INF/LICENSE b/src/main/resources/META-INF/LICENSE
new file mode 100644
index 0000000..8d5775d
--- /dev/null
+++ b/src/main/resources/META-INF/LICENSE
@@ -0,0 +1,8 @@
+This copy of Jackson JSON processor YAML module is licensed under the
+Apache (Software) License, version 2.0 ("the License").
+See the License for details about distribution rights, and the
+specific rights regarding derivate works.
+
+You may obtain a copy of the License at:
+
+http://www.apache.org/licenses/LICENSE-2.0
diff --git a/src/main/resources/META-INF/NOTICE b/src/main/resources/META-INF/NOTICE
new file mode 100644
index 0000000..5ab1e56
--- /dev/null
+++ b/src/main/resources/META-INF/NOTICE
@@ -0,0 +1,20 @@
+# Jackson JSON processor
+
+Jackson is a high-performance, Free/Open Source JSON processing library.
+It was originally written by Tatu Saloranta (tatu.saloranta at iki.fi), and has
+been in development since 2007.
+It is currently developed by a community of developers, as well as supported
+commercially by FasterXML.com.
+
+## Licensing
+
+Jackson core and extension components may be licensed under different licenses.
+To find the details that apply to this artifact see the accompanying LICENSE file.
+For more information, including possible other licensing options, contact
+FasterXML.com (http://fasterxml.com).
+
+## Credits
+
+A list of contributors may be found from CREDITS file, which is included
+in some artifacts (usually source distributions); but is always available
+from the source code management (SCM) system project uses.
diff --git a/src/main/resources/META-INF/services/com.fasterxml.jackson.core.JsonFactory b/src/main/resources/META-INF/services/com.fasterxml.jackson.core.JsonFactory
new file mode 100644
index 0000000..1686b09
--- /dev/null
+++ b/src/main/resources/META-INF/services/com.fasterxml.jackson.core.JsonFactory
@@ -0,0 +1 @@
+com.fasterxml.jackson.dataformat.yaml.YAMLFactory
diff --git a/src/test/java/com/fasterxml/jackson/dataformat/yaml/EventsTest.java b/src/test/java/com/fasterxml/jackson/dataformat/yaml/EventsTest.java
new file mode 100644
index 0000000..41bb56d
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/dataformat/yaml/EventsTest.java
@@ -0,0 +1,52 @@
+package com.fasterxml.jackson.dataformat.yaml;
+
+import com.fasterxml.jackson.core.*;
+
+/**
+ * Tests that test low-level handling of events from YAML source
+ */
+public class EventsTest extends ModuleTestBase
+{
+ public void testBasic() throws Exception
+ {
+ final String YAML =
+ "string: 'text'\n"
++"bool: true\n"
++"bool2: false\n"
++"null: null\n"
++"i: 123\n"
++"d: 1.25\n"
+;
+ YAMLFactory f = new YAMLFactory();
+ JsonParser p = f.createJsonParser(YAML);
+ assertToken(JsonToken.START_OBJECT, p.nextToken());
+
+ assertToken(JsonToken.FIELD_NAME, p.nextToken());
+ assertToken(JsonToken.VALUE_STRING, p.nextToken());
+ assertEquals("text", p.getText());
+ assertToken(JsonToken.FIELD_NAME, p.nextToken());
+ assertToken(JsonToken.VALUE_TRUE, p.nextToken());
+ assertEquals("true", p.getText());
+ assertToken(JsonToken.FIELD_NAME, p.nextToken());
+ assertToken(JsonToken.VALUE_FALSE, p.nextToken());
+ assertEquals("false", p.getText());
+ assertToken(JsonToken.FIELD_NAME, p.nextToken());
+ assertToken(JsonToken.VALUE_NULL, p.nextToken());
+ assertEquals("null", p.getText());
+ assertToken(JsonToken.FIELD_NAME, p.nextToken());
+ assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken());
+ assertEquals("123", p.getText());
+ assertEquals(123, p.getIntValue());
+ assertToken(JsonToken.FIELD_NAME, p.nextToken());
+ assertToken(JsonToken.VALUE_NUMBER_FLOAT, p.nextToken());
+ assertEquals("1.25", p.getText());
+ assertEquals(1.25, p.getDoubleValue());
+ assertEquals(1, p.getIntValue());
+
+ assertToken(JsonToken.END_OBJECT, p.nextToken());
+ assertNull(p.nextToken());
+ assertNull(p.nextToken());
+ assertNull(p.nextToken());
+ p.close();
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/dataformat/yaml/FormatDetectionTest.java b/src/test/java/com/fasterxml/jackson/dataformat/yaml/FormatDetectionTest.java
new file mode 100644
index 0000000..62c435c
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/dataformat/yaml/FormatDetectionTest.java
@@ -0,0 +1,52 @@
+package com.fasterxml.jackson.dataformat.yaml;
+
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.core.format.DataFormatDetector;
+import com.fasterxml.jackson.core.format.DataFormatMatcher;
+import com.fasterxml.jackson.core.format.MatchStrength;
+
+public class FormatDetectionTest extends ModuleTestBase
+{
+ /**
+ * One nifty thing YAML has is the "---" start-doc indicator, which
+ * makes it possible to auto-detect format...
+ */
+ public void testFormatDetection() throws Exception
+ {
+ YAMLFactory yamlF = new YAMLFactory();
+ JsonFactory jsonF = new JsonFactory();
+ DataFormatDetector det = new DataFormatDetector(new JsonFactory[] { yamlF, jsonF });
+ // let's accept about any match; but only if no "solid match" found
+ det = det.withMinimalMatch(MatchStrength.WEAK_MATCH).withOptimalMatch(MatchStrength.SOLID_MATCH);
+
+ // First, give a JSON document...
+ DataFormatMatcher match = det.findFormat("{ \"name\" : \"Bob\" }".getBytes("UTF-8"));
+ assertNotNull(match);
+ assertEquals(jsonF.getFormatName(), match.getMatchedFormatName());
+ // and verify we can parse it
+ JsonParser p = match.createParserWithMatch();
+ assertToken(JsonToken.START_OBJECT, p.nextToken());
+ assertToken(JsonToken.FIELD_NAME, p.nextToken());
+ assertEquals("name", p.getCurrentName());
+ assertToken(JsonToken.VALUE_STRING, p.nextToken());
+ assertEquals("Bob", p.getText());
+ assertToken(JsonToken.END_OBJECT, p.nextToken());
+ p.close();
+
+ // then YAML
+ match = det.findFormat("---\nname: Bob\n".getBytes("UTF-8"));
+ assertNotNull(match);
+ assertEquals(yamlF.getFormatName(), match.getMatchedFormatName());
+ // and parsing
+ p = match.createParserWithMatch();
+ assertToken(JsonToken.START_OBJECT, p.nextToken());
+ assertToken(JsonToken.FIELD_NAME, p.nextToken());
+ assertEquals("name", p.getCurrentName());
+ assertToken(JsonToken.VALUE_STRING, p.nextToken());
+ assertEquals("Bob", p.getText());
+ assertToken(JsonToken.END_OBJECT, p.nextToken());
+ p.close();
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/dataformat/yaml/ModuleTestBase.java b/src/test/java/com/fasterxml/jackson/dataformat/yaml/ModuleTestBase.java
new file mode 100644
index 0000000..a74e620
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/dataformat/yaml/ModuleTestBase.java
@@ -0,0 +1,159 @@
+package com.fasterxml.jackson.dataformat.yaml;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import com.fasterxml.jackson.core.*;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public abstract class ModuleTestBase extends junit.framework.TestCase
+{
+ /**
+ * Slightly modified sample class from Jackson tutorial ("JacksonInFiveMinutes")
+ */
+ protected static class FiveMinuteUser {
+ public enum Gender { MALE, FEMALE };
+
+ private Gender _gender;
+
+ public String firstName, lastName;
+
+ private boolean _isVerified;
+ private byte[] _userImage;
+
+ public FiveMinuteUser() { }
+
+ public FiveMinuteUser(String first, String last, boolean verified, Gender g, byte[] data)
+ {
+ firstName = first;
+ lastName = last;
+ _isVerified = verified;
+ _gender = g;
+ _userImage = data;
+ }
+
+ public boolean isVerified() { return _isVerified; }
+ public Gender getGender() { return _gender; }
+ public byte[] getUserImage() { return _userImage; }
+
+ public void setVerified(boolean b) { _isVerified = b; }
+ public void setGender(Gender g) { _gender = g; }
+ public void setUserImage(byte[] b) { _userImage = b; }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (o == this) return true;
+ if (o == null || o.getClass() != getClass()) return false;
+ FiveMinuteUser other = (FiveMinuteUser) o;
+ if (_isVerified != other._isVerified) return false;
+ if (_gender != other._gender) return false;
+ if (!firstName.equals(other.firstName)) return false;
+ if (!lastName.equals(other.lastName)) return false;
+ byte[] otherImage = other._userImage;
+ if (otherImage.length != _userImage.length) return false;
+ for (int i = 0, len = _userImage.length; i < len; ++i) {
+ if (_userImage[i] != otherImage[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ protected ModuleTestBase() { }
+
+ /*
+ /**********************************************************************
+ /* Helper methods, setup
+ /**********************************************************************
+ */
+
+ protected ObjectMapper mapperForYAML()
+ {
+ return new ObjectMapper(new YAMLFactory());
+ }
+
+ /*
+ /**********************************************************
+ /* Helper methods; low-level
+ /**********************************************************
+ */
+
+ public String quote(String str) {
+ return '"'+str+'"';
+ }
+
+ protected void assertToken(JsonToken expToken, JsonToken actToken)
+ {
+ if (actToken != expToken) {
+ fail("Expected token "+expToken+", current token "+actToken);
+ }
+ }
+
+ protected void assertToken(JsonToken expToken, JsonParser jp)
+ {
+ assertToken(expToken, jp.getCurrentToken());
+ }
+
+ protected void assertType(Object ob, Class<?> expType)
+ {
+ if (ob == null) {
+ fail("Expected an object of type "+expType.getName()+", got null");
+ }
+ Class<?> cls = ob.getClass();
+ if (!expType.isAssignableFrom(cls)) {
+ fail("Expected type "+expType.getName()+", got "+cls.getName());
+ }
+ }
+
+ /**
+ * Method that gets textual contents of the current token using
+ * available methods, and ensures results are consistent, before
+ * returning them
+ */
+ protected String getAndVerifyText(JsonParser jp)
+ throws IOException, JsonParseException
+ {
+ // Ok, let's verify other accessors
+ int actLen = jp.getTextLength();
+ char[] ch = jp.getTextCharacters();
+ String str2 = new String(ch, jp.getTextOffset(), actLen);
+ String str = jp.getText();
+
+ if (str.length() != actLen) {
+ fail("Internal problem (jp.token == "+jp.getCurrentToken()+"): jp.getText().length() ['"+str+"'] == "+str.length()+"; jp.getTextLength() == "+actLen);
+ }
+ assertEquals("String access via getText(), getTextXxx() must be the same", str, str2);
+
+ return str;
+ }
+
+ protected void verifyFieldName(JsonParser jp, String expName)
+ throws IOException
+ {
+ assertEquals(expName, jp.getText());
+ assertEquals(expName, jp.getCurrentName());
+ }
+
+ protected void verifyIntValue(JsonParser jp, long expValue)
+ throws IOException
+ {
+ // First, via textual
+ assertEquals(String.valueOf(expValue), jp.getText());
+ }
+
+ protected void verifyException(Throwable e, String... matches)
+ {
+ String msg = e.getMessage();
+ String lmsg = (msg == null) ? "" : msg.toLowerCase();
+ for (String match : matches) {
+ String lmatch = match.toLowerCase();
+ if (lmsg.indexOf(lmatch) >= 0) {
+ return;
+ }
+ }
+ fail("Expected an exception with one of substrings ("+Arrays.asList(matches)+"): got one with message \""+msg+"\"");
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/dataformat/yaml/SimpleDatabindTest.java b/src/test/java/com/fasterxml/jackson/dataformat/yaml/SimpleDatabindTest.java
new file mode 100644
index 0000000..937acf9
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/dataformat/yaml/SimpleDatabindTest.java
@@ -0,0 +1,81 @@
+package com.fasterxml.jackson.dataformat.yaml;
+
+import java.io.ByteArrayInputStream;
+import java.util.Map;
+import java.util.UUID;
+
+import org.junit.Assert;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * Unit tests for checking functioning of the databinding
+ * on top of YAML layer.
+ */
+public class SimpleDatabindTest extends ModuleTestBase
+{
+ public void testBasicUntyped() throws Exception
+ {
+ final String YAML =
+ "template: Hello, %s!\n"
++"database:\n"
++" driverClass: org.h2.Driver\n"
++" user: scott\n"
++" password: tiger\n"
++" extra: [1,2]"
+;
+ ObjectMapper mapper = mapperForYAML();
+ Map<?,?> result = mapper.readValue(YAML, Map.class);
+ // sanity check first:
+ assertEquals(2, result.size());
+ // then literal comparison; easiest to just write as JSON...
+ ObjectMapper jsonMapper = new ObjectMapper();
+ String json = jsonMapper.writeValueAsString(result);
+ String EXP = "{\"template\":\"Hello, %s!\",\"database\":{"
+ +"\"driverClass\":\"org.h2.Driver\",\"user\":\"scott\",\"password\":\"tiger\","
+ +"\"extra\":[1,2]}}";
+ assertEquals(EXP, json);
+ }
+
+ public void testBasicPOJO() throws Exception
+ {
+ ObjectMapper mapper = mapperForYAML();
+ final String YAML =
+"firstName: Billy\n"
++"lastName: Baggins\n"
++"gender: MALE\n"
++"verified: true\n"
++"userImage: AQIDBAU=" // [1,2,3,4,5]
+;
+ FiveMinuteUser user = mapper.readValue(YAML, FiveMinuteUser.class);
+ assertEquals("Billy", user.firstName);
+ assertEquals("Baggins", user.lastName);
+ assertEquals(FiveMinuteUser.Gender.MALE, user.getGender());
+ assertTrue(user.isVerified());
+ byte[] data = user.getUserImage();
+ assertNotNull(data);
+ Assert.assertArrayEquals(new byte[] { 1, 2, 3, 4, 5 }, data);
+ }
+
+ public void testIssue1() throws Exception
+ {
+ ObjectMapper mapper = mapperForYAML();
+ final byte[] YAML = "firstName: Billy".getBytes("UTF-8");
+ FiveMinuteUser user = new FiveMinuteUser();
+ user.firstName = "Bubba";
+ mapper.readerForUpdating(user).readValue(new ByteArrayInputStream(YAML));
+ assertEquals("Billy", user.firstName);
+ }
+
+ // [Issue-2]
+ public void testUUIDs() throws Exception
+ {
+ ObjectMapper mapper = mapperForYAML();
+ UUID uuid = new UUID(0, 0);
+ String yaml = mapper.writeValueAsString(uuid);
+
+ UUID result = mapper.readValue(yaml, UUID.class);
+
+ assertEquals(uuid, result);
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/dataformat/yaml/SimpleGenerationTest.java b/src/test/java/com/fasterxml/jackson/dataformat/yaml/SimpleGenerationTest.java
new file mode 100644
index 0000000..f2aa032
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/dataformat/yaml/SimpleGenerationTest.java
@@ -0,0 +1,76 @@
+package com.fasterxml.jackson.dataformat.yaml;
+
+import java.io.*;
+import java.util.Iterator;
+import java.util.TreeSet;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class SimpleGenerationTest extends ModuleTestBase
+{
+ public void testStreamingArray() throws Exception
+ {
+ YAMLFactory f = new YAMLFactory();
+ StringWriter w = new StringWriter();
+ JsonGenerator gen = f.createJsonGenerator(w);
+ gen.writeStartArray();
+ gen.writeNumber(3);
+ gen.writeString("foobar");
+ gen.writeEndArray();
+ gen.close();
+
+ String yaml = w.toString();
+ // should probably parse...
+ assertEquals("---\n- 3\n- \"foobar\"\n", yaml);
+ }
+
+ public void testStreamingObject() throws Exception
+ {
+ YAMLFactory f = new YAMLFactory();
+ StringWriter w = new StringWriter();
+ JsonGenerator gen = f.createJsonGenerator(w);
+ gen.writeStartObject();
+ gen.writeStringField("name", "Brad");
+ gen.writeNumberField("age", 39);
+ gen.writeEndObject();
+ gen.close();
+
+ String yaml = w.toString();
+ assertEquals("---\nname: \"Brad\"\nage: 39\n", yaml);
+ }
+
+ public void testBasicPOJO() throws Exception
+ {
+ ObjectMapper mapper = mapperForYAML();
+ FiveMinuteUser user = new FiveMinuteUser("Bob", "Dabolito", false,
+ FiveMinuteUser.Gender.MALE, new byte[] { 1, 3, 13, 79 });
+ String yaml = mapper.writeValueAsString(user).trim();
+ String[] parts = yaml.split("\n");
+ assertEquals(6, parts.length);
+ // unify ordering, need to use TreeSets
+ TreeSet<String> exp = new TreeSet<String>();
+ for (String part : parts) {
+ exp.add(part.trim());
+ }
+ Iterator<String> it = exp.iterator();
+ assertEquals("---", it.next());
+ assertEquals("firstName: \"Bob\"", it.next());
+ assertEquals("gender: \"MALE\"", it.next());
+ assertEquals("lastName: \"Dabolito\"", it.next());
+ assertEquals("userImage: \"AQMNTw==\"", it.next());
+ assertEquals("verified: false", it.next());
+ }
+
+ // Issue#12:
+ public void testWithFile() throws Exception
+ {
+ File f = File.createTempFile("test", ".yml");
+ f.deleteOnExit();
+ ObjectMapper mapper = mapperForYAML();
+ mapper.writeValue(f, "Foobar");
+ assertTrue(f.canRead());
+ assertEquals(13L, f.length());
+ f.delete();
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/dataformat/yaml/SimpleParseTest.java b/src/test/java/com/fasterxml/jackson/dataformat/yaml/SimpleParseTest.java
new file mode 100644
index 0000000..554205a
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/dataformat/yaml/SimpleParseTest.java
@@ -0,0 +1,281 @@
+package com.fasterxml.jackson.dataformat.yaml;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+
+import java.math.BigInteger;
+
+/**
+ * Unit tests for checking functioning of the underlying
+ * parser implementation.
+ */
+public class SimpleParseTest extends ModuleTestBase
+{
+ // Parsing large numbers around the transition from int->long and long->BigInteger
+ public void testIntParsing() throws Exception
+ {
+ YAMLFactory f = new YAMLFactory();
+ String YAML;
+ JsonParser jp;
+
+ // Test positive max-int
+ YAML = "num: 2147483647";
+ jp = f.createJsonParser(YAML);
+ assertToken(JsonToken.START_OBJECT, jp.nextToken());
+ assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+ assertEquals("num", jp.getCurrentName());
+ assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+ assertEquals(Integer.MAX_VALUE, jp.getIntValue());
+ assertEquals(JsonParser.NumberType.INT, jp.getNumberType());
+ assertEquals("2147483647", jp.getText());
+ jp.close();
+
+ // Test negative max-int
+ YAML = "num: -2147483648";
+ jp = f.createJsonParser(YAML);
+ assertToken(JsonToken.START_OBJECT, jp.nextToken());
+ assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+ assertEquals("num", jp.getCurrentName());
+ assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+ assertEquals(Integer.MIN_VALUE, jp.getIntValue());
+ assertEquals(JsonParser.NumberType.INT, jp.getNumberType());
+ assertEquals("-2147483648", jp.getText());
+ jp.close();
+
+ // Test positive max-int + 1
+ YAML = "num: 2147483648";
+ jp = f.createJsonParser(YAML);
+ assertToken(JsonToken.START_OBJECT, jp.nextToken());
+ assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+ assertEquals("num", jp.getCurrentName());
+ assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+ assertEquals(Integer.MAX_VALUE + 1L, jp.getLongValue());
+ assertEquals(JsonParser.NumberType.LONG, jp.getNumberType());
+ assertEquals("2147483648", jp.getText());
+ jp.close();
+
+ // Test negative max-int - 1
+ YAML = "num: -2147483649";
+ jp = f.createJsonParser(YAML);
+ assertToken(JsonToken.START_OBJECT, jp.nextToken());
+ assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+ assertEquals("num", jp.getCurrentName());
+ assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+ assertEquals(Integer.MIN_VALUE - 1L, jp.getLongValue());
+ assertEquals(JsonParser.NumberType.LONG, jp.getNumberType());
+ assertEquals("-2147483649", jp.getText());
+ jp.close();
+
+ // Test positive max-long
+ YAML = "num: 9223372036854775807";
+ jp = f.createJsonParser(YAML);
+ assertToken(JsonToken.START_OBJECT, jp.nextToken());
+ assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+ assertEquals("num", jp.getCurrentName());
+ assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+ assertEquals(Long.MAX_VALUE, jp.getLongValue());
+ assertEquals(JsonParser.NumberType.LONG, jp.getNumberType());
+ assertEquals("9223372036854775807", jp.getText());
+ jp.close();
+
+ // Test negative max-long
+ YAML = "num: -9223372036854775808";
+ jp = f.createJsonParser(YAML);
+ assertToken(JsonToken.START_OBJECT, jp.nextToken());
+ assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+ assertEquals("num", jp.getCurrentName());
+ assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+ assertEquals(Long.MIN_VALUE, jp.getLongValue());
+ assertEquals(JsonParser.NumberType.LONG, jp.getNumberType());
+ assertEquals("-9223372036854775808", jp.getText());
+ jp.close();
+
+ // Test positive max-long + 1
+ YAML = "num: 9223372036854775808";
+ jp = f.createJsonParser(YAML);
+ assertToken(JsonToken.START_OBJECT, jp.nextToken());
+ assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+ assertEquals("num", jp.getCurrentName());
+ assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+ assertEquals(BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE), jp.getBigIntegerValue());
+ assertEquals(JsonParser.NumberType.BIG_INTEGER, jp.getNumberType());
+ assertEquals("9223372036854775808", jp.getText());
+ jp.close();
+
+ // Test negative max-long - 1
+ YAML = "num: -9223372036854775809";
+ jp = f.createJsonParser(YAML);
+ assertToken(JsonToken.START_OBJECT, jp.nextToken());
+ assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+ assertEquals("num", jp.getCurrentName());
+ assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
+ assertEquals(BigInteger.valueOf(Long.MIN_VALUE).subtract(BigInteger.ONE), jp.getBigIntegerValue());
+ assertEquals(JsonParser.NumberType.BIG_INTEGER, jp.getNumberType());
+ assertEquals("-9223372036854775809", jp.getText());
+ jp.close();
+ }
+
+ // [Issue-4]: accidental recognition as double, with multiple dots
+ public void testDoubleParsing() throws Exception
+ {
+ YAMLFactory f = new YAMLFactory();
+
+ // First, test out valid use case.
+ String YAML;
+
+ YAML = "num: +1_000.25"; // note underscores; legal in YAML apparently
+ JsonParser jp = f.createJsonParser(YAML);
+
+ assertToken(JsonToken.START_OBJECT, jp.nextToken());
+ assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+ assertEquals("num", jp.getCurrentName());
+ // should be considered a String...
+ assertToken(JsonToken.VALUE_NUMBER_FLOAT, jp.nextToken());
+ assertEquals(1000.25, jp.getDoubleValue());
+ // let's retain exact representation text however:
+ assertEquals("+1_000.25", jp.getText());
+ jp.close();
+
+ // and then non-number that may be mistaken
+
+ final String IP = "10.12.45.127";
+ YAML = "ip: "+IP+"\n";
+ jp = f.createJsonParser(YAML);
+ assertToken(JsonToken.START_OBJECT, jp.nextToken());
+ assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+ assertEquals("ip", jp.getCurrentName());
+ // should be considered a String...
+ assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+ assertEquals(IP, jp.getText());
+ jp.close();
+ }
+
+ // [Issue#7]
+ // looks like colons in content can be problematic, if unquoted
+ public void testColons() throws Exception
+ {
+ YAMLFactory f = new YAMLFactory();
+
+ // First, test out valid use case. NOTE: spaces matter!
+ String YAML = "section:\n"
+ +" text: foo:bar\n";
+ JsonParser jp = f.createJsonParser(YAML);
+
+ assertToken(JsonToken.START_OBJECT, jp.nextToken());
+ assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+ assertEquals("section", jp.getCurrentName());
+ assertToken(JsonToken.START_OBJECT, jp.nextToken());
+ assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+ assertEquals("text", jp.getCurrentName());
+ assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+ assertEquals("foo:bar", jp.getText());
+ assertToken(JsonToken.END_OBJECT, jp.nextToken());
+ assertToken(JsonToken.END_OBJECT, jp.nextToken());
+ assertNull(jp.nextToken());
+
+ jp.close();
+ }
+
+ /**
+ * How should YAML Anchors be exposed?
+ */
+ public void testAnchorParsing() throws Exception
+ {
+ // silly doc, just to expose an id (anchor) and ref to it
+ final String YAML = "---\n"
+ +"parent: &id1\n"
+ +" name: Bob\n"
+ +"child: &id2\n"
+ +" name: Bill\n"
+ +" parentRef: *id1"
+ ;
+ YAMLFactory f = new YAMLFactory();
+ YAMLParser yp = f.createParser(YAML);
+
+ assertToken(JsonToken.START_OBJECT, yp.nextToken());
+ assertFalse(yp.isCurrentAlias());
+ assertNull(yp.getCurrentAnchor());
+
+ assertToken(JsonToken.FIELD_NAME, yp.nextToken());
+ assertEquals("parent", yp.getCurrentName());
+ assertFalse(yp.isCurrentAlias());
+ assertNull(yp.getCurrentAnchor());
+
+ assertToken(JsonToken.START_OBJECT, yp.nextToken());
+ assertFalse(yp.isCurrentAlias());
+ assertEquals("id1", yp.getCurrentAnchor());
+ assertToken(JsonToken.FIELD_NAME, yp.nextToken());
+ assertEquals("name", yp.getCurrentName());
+ assertToken(JsonToken.VALUE_STRING, yp.nextToken());
+ assertEquals("Bob", yp.getText());
+ assertFalse(yp.isCurrentAlias());
+ assertToken(JsonToken.END_OBJECT, yp.nextToken());
+
+ assertToken(JsonToken.FIELD_NAME, yp.nextToken());
+ assertEquals("child", yp.getCurrentName());
+ assertFalse(yp.isCurrentAlias());
+ assertToken(JsonToken.START_OBJECT, yp.nextToken());
+ assertFalse(yp.isCurrentAlias());
+ assertEquals("id2", yp.getCurrentAnchor());
+ assertToken(JsonToken.FIELD_NAME, yp.nextToken());
+ assertEquals("name", yp.getCurrentName());
+ assertToken(JsonToken.VALUE_STRING, yp.nextToken());
+ assertEquals("Bill", yp.getText());
+ assertToken(JsonToken.FIELD_NAME, yp.nextToken());
+ assertEquals("parentRef", yp.getCurrentName());
+ assertToken(JsonToken.VALUE_STRING, yp.nextToken());
+ assertEquals("id1", yp.getText());
+ assertTrue(yp.isCurrentAlias());
+ assertToken(JsonToken.END_OBJECT, yp.nextToken());
+
+ assertToken(JsonToken.END_OBJECT, yp.nextToken());
+
+ assertNull(yp.nextToken());
+ yp.close();
+ }
+
+ // [Issue#10]
+ // Scalars should not be parsed when not in the plain flow style.
+ public void testQuotedStyles() throws Exception
+ {
+ YAMLFactory f = new YAMLFactory();
+
+ String YAML = "strings: [\"true\", 'false']";
+ JsonParser jp = f.createJsonParser(YAML);
+
+ assertToken(JsonToken.START_OBJECT, jp.nextToken());
+ assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+ assertEquals("strings", jp.getCurrentName());
+ assertToken(JsonToken.START_ARRAY, jp.nextToken());
+ assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+ assertEquals("true", jp.getText());
+ assertToken(JsonToken.VALUE_STRING, jp.nextToken());
+ assertEquals("false", jp.getText());
+ assertToken(JsonToken.END_ARRAY, jp.nextToken());
+ assertToken(JsonToken.END_OBJECT, jp.nextToken());
+ assertNull(jp.nextToken());
+
+ jp.close();
+ }
+
+ // Scalars should be parsed when in the plain flow style.
+ public void testUnquotedStyles() throws Exception
+ {
+ YAMLFactory f = new YAMLFactory();
+
+ String YAML = "booleans: [true, false]";
+ JsonParser jp = f.createJsonParser(YAML);
+
+ assertToken(JsonToken.START_OBJECT, jp.nextToken());
+ assertToken(JsonToken.FIELD_NAME, jp.nextToken());
+ assertEquals("booleans", jp.getCurrentName());
+ assertToken(JsonToken.START_ARRAY, jp.nextToken());
+ assertToken(JsonToken.VALUE_TRUE, jp.nextToken());
+ assertToken(JsonToken.VALUE_FALSE, jp.nextToken());
+ assertToken(JsonToken.END_ARRAY, jp.nextToken());
+ assertToken(JsonToken.END_OBJECT, jp.nextToken());
+ assertNull(jp.nextToken());
+
+ jp.close();
+ }
+}
diff --git a/src/test/java/com/fasterxml/jackson/dataformat/yaml/TestVersions.java b/src/test/java/com/fasterxml/jackson/dataformat/yaml/TestVersions.java
new file mode 100644
index 0000000..a62a303
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/dataformat/yaml/TestVersions.java
@@ -0,0 +1,30 @@
+package com.fasterxml.jackson.dataformat.yaml;
+
+import java.io.*;
+
+import com.fasterxml.jackson.core.*;
+
+public class TestVersions extends ModuleTestBase
+{
+ public void testMapperVersions() throws IOException
+ {
+ YAMLFactory f = new YAMLFactory();
+ assertVersion(f);
+ YAMLParser jp = (YAMLParser) f.createJsonParser("123");
+ assertVersion(jp);
+ YAMLGenerator jgen = (YAMLGenerator) f.createJsonGenerator(new ByteArrayOutputStream());
+ assertVersion(jgen);
+ }
+
+ /*
+ /**********************************************************
+ /* Helper methods
+ /**********************************************************
+ */
+
+ private void assertVersion(Versioned vers)
+ {
+ assertEquals(PackageVersion.VERSION, vers.version());
+ }
+}
+
diff --git a/src/test/java/perf/DeserPerf.java b/src/test/java/perf/DeserPerf.java
new file mode 100644
index 0000000..e3bc347
--- /dev/null
+++ b/src/test/java/perf/DeserPerf.java
@@ -0,0 +1,142 @@
+package perf;
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.type.TypeFactory;
+
+/**
+ * Micro-benchmark for comparing performance of bean deserialization
+ */
+public final class DeserPerf
+{
+ /*
+ /**********************************************************
+ /* Actual test
+ /**********************************************************
+ */
+
+ private final int REPS;
+
+ private DeserPerf() {
+ // Let's try to guestimate suitable size
+ REPS = 9000;
+ }
+
+ private MediaItem buildItem()
+ {
+ MediaItem.Content content = new MediaItem.Content();
+ content.setPlayer(MediaItem.Player.JAVA);
+ content.setUri("http://javaone.com/keynote.mpg");
+ content.setTitle("Javaone Keynote");
+ content.setWidth(640);
+ content.setHeight(480);
+ content.setFormat("video/mpeg4");
+ content.setDuration(18000000L);
+ content.setSize(58982400L);
+ content.setBitrate(262144);
+ content.setCopyright("None");
+ content.addPerson("Bill Gates");
+ content.addPerson("Steve Jobs");
+
+ MediaItem item = new MediaItem(content);
+
+ item.addPhoto(new MediaItem.Photo("http://javaone.com/keynote_large.jpg", "Javaone Keynote", 1024, 768, MediaItem.Size.LARGE));
+ item.addPhoto(new MediaItem.Photo("http://javaone.com/keynote_small.jpg", "Javaone Keynote", 320, 240, MediaItem.Size.SMALL));
+
+ return item;
+ }
+
+ public void test()
+ throws Exception
+ {
+ int sum = 0;
+
+ final MediaItem item = buildItem();
+// JsonFactory jsonF = new JsonFactory();
+// final ObjectMapper jsonMapper = new ObjectMapper(jsonF);
+ JsonFactory yamlF = new com.fasterxml.jackson.dataformat.yaml.YAMLFactory();
+ final ObjectMapper yamlMapper = new ObjectMapper(yamlF);
+
+// final ObjectMapper jsonMapper = new ObjectMapper(jsonF);
+// jsonMapper.configure(SerializationConfig.Feature.USE_STATIC_TYPING, true);
+
+ // Use Jackson?
+// byte[] json = jsonMapper.writeValueAsBytes(item);
+ byte[] yaml = yamlMapper.writeValueAsBytes(item);
+
+ System.out.println("Warmed up: data size is "+yaml.length+" bytes; "+REPS+" reps -> "
+ +((REPS * yaml.length) >> 10)+" kB per iteration");
+ System.out.println();
+
+// for debugging:
+// System.err.println("JSON = "+jsonMapper.writerWithDefaultPrettyPrinter().writeValueAsString(item));
+
+ int round = 0;
+ while (true) {
+// try { Thread.sleep(100L); } catch (InterruptedException ie) { }
+// int round = 2;
+
+ long curr = System.currentTimeMillis();
+ String msg;
+ round = (++round % 2);
+
+//if (true) round = 3;
+
+ boolean lf = (round == 0);
+
+ switch (round) {
+ case 0:
+ case 1:
+ msg = "Deserialize, bind, YAML";
+ sum += testDeser(yamlMapper, yaml, REPS);
+ break;
+
+ /*
+ case 0:
+ msg = "Deserialize, manual, YAML";
+ sum += testDeser(yamlMapper.getJsonFactory(), yaml, REPS);
+ break;
+ */
+
+ default:
+ throw new Error("Internal error");
+ }
+
+ curr = System.currentTimeMillis() - curr;
+ if (lf) {
+ System.out.println();
+ }
+ System.out.println("Test '"+msg+"' -> "+curr+" msecs ("
+ +(sum & 0xFF)+").");
+ }
+ }
+
+ protected int testDeser(ObjectMapper mapper, byte[] input, int reps)
+ throws Exception
+ {
+ JavaType type = TypeFactory.defaultInstance().constructType(MediaItem.class);
+ MediaItem item = null;
+ for (int i = 0; i < reps; ++i) {
+ item = mapper.readValue(input, 0, input.length, type);
+ }
+ return item.hashCode(); // just to get some non-optimizable number
+ }
+
+ protected int testDeser(JsonFactory jf, byte[] input, int reps)
+ throws Exception
+ {
+ MediaItem item = null;
+ for (int i = 0; i < reps; ++i) {
+ JsonParser jp = jf.createParser(input);
+ item = MediaItem.deserialize(jp);
+ jp.close();
+ }
+ return item.hashCode(); // just to get some non-optimizable number
+ }
+
+ public static void main(String[] args) throws Exception
+ {
+ new DeserPerf().test();
+ }
+}
diff --git a/src/test/java/perf/MediaItem.java b/src/test/java/perf/MediaItem.java
new file mode 100644
index 0000000..d58c3ce
--- /dev/null
+++ b/src/test/java/perf/MediaItem.java
@@ -0,0 +1,396 @@
+package perf;
+
+import java.io.IOException;
+import java.util.*;
+
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import com.fasterxml.jackson.core.*;
+
+/**
+ * Value class for performance tests
+ */
+ at JsonPropertyOrder({"content", "images"})
+public class MediaItem
+{
+ final static String NAME_IMAGES = "images";
+ final static String NAME_CONTENT = "content";
+
+ public enum Player { JAVA, FLASH; }
+ public enum Size { SMALL, LARGE; }
+
+ private List<Photo> _photos;
+ private Content _content;
+
+ public MediaItem() { }
+
+ public MediaItem(Content c)
+ {
+ _content = c;
+ }
+
+ public void addPhoto(Photo p) {
+ if (_photos == null) {
+ _photos = new ArrayList<Photo>();
+ }
+ _photos.add(p);
+ }
+
+ public List<Photo> getImages() { return _photos; }
+ public void setImages(List<Photo> p) { _photos = p; }
+
+ public Content getContent() { return _content; }
+ public void setContent(Content c) { _content = c; }
+
+ // Custom deser
+ public static MediaItem deserialize(JsonParser jp) throws IOException
+ {
+ if (jp.nextToken() != JsonToken.START_OBJECT) {
+ throw new IOException("Need START_OBJECT for MediaItem");
+ }
+ MediaItem item = new MediaItem();
+ while (jp.nextToken() == JsonToken.FIELD_NAME) {
+ String name = jp.getCurrentName();
+ if (name == "images") {
+ item._photos = deserializeImages(jp);
+ } else if (name == "content") {
+ item._content = Content.deserialize(jp);
+ } else throw new IOException("Unknown field");
+ }
+ if (jp.getCurrentToken() != JsonToken.END_OBJECT) {
+ throw new IOException("Need END_OBJECT to complete MediaItem");
+ }
+ return item;
+ }
+
+ private static List<Photo> deserializeImages(JsonParser jp) throws IOException
+ {
+ if (jp.nextToken() != JsonToken.START_ARRAY) {
+ throw new IOException("Need START_ARRAY for List of Photos");
+ }
+ ArrayList<Photo> images = new ArrayList<Photo>(4);
+ while (jp.nextToken() == JsonToken.START_OBJECT) {
+ images.add(Photo.deserialize(jp));
+ }
+ if (jp.getCurrentToken() != JsonToken.END_ARRAY) {
+ throw new IOException("Need END_ARRAY to complete List of Photos");
+ }
+ return images;
+ }
+
+ // Custom serializer
+ public void serialize(JsonGenerator jgen) throws IOException
+ {
+ jgen.writeStartObject();
+
+ jgen.writeFieldName("content");
+ if (_content == null) {
+ jgen.writeNull();
+ } else {
+ _content.serialize(jgen);
+ }
+ if (_photos == null) {
+ jgen.writeNullField("images");
+ } else {
+ jgen.writeArrayFieldStart("images");
+ for (Photo photo : _photos) {
+ photo.serialize(jgen);
+ }
+ jgen.writeEndArray();
+ }
+
+ jgen.writeEndObject();
+ }
+
+ /*
+ /**********************************************************
+ /* Helper types
+ /**********************************************************
+ */
+
+ @JsonPropertyOrder({"uri","title","width","height","size"})
+ public static class Photo
+ {
+ public final static int F_URI = 1;
+ public final static int F_TITLE = 2;
+ public final static int F_WIDTH = 3;
+ public final static int F_HEIGHT = 4;
+ public final static int F_SIZE = 5;
+
+ public final static HashMap<String,Integer> sFields = new HashMap<String,Integer>();
+ static {
+ // MediaItem fields
+ sFields.put("uri", F_URI);
+ sFields.put("title", F_TITLE);
+ sFields.put("width", F_WIDTH);
+ sFields.put("height", F_HEIGHT);
+ sFields.put("size", F_SIZE);
+ }
+
+ private String _uri;
+ private String _title;
+ private int _width;
+ private int _height;
+ private Size _size;
+
+ public Photo() {}
+ public Photo(String uri, String title, int w, int h, Size s)
+ {
+ _uri = uri;
+ _title = title;
+ _width = w;
+ _height = h;
+ _size = s;
+ }
+
+ public String getUri() { return _uri; }
+ public String getTitle() { return _title; }
+ public int getWidth() { return _width; }
+ public int getHeight() { return _height; }
+ public Size getSize() { return _size; }
+
+ public void setUri(String u) { _uri = u; }
+ public void setTitle(String t) { _title = t; }
+ public void setWidth(int w) { _width = w; }
+ public void setHeight(int h) { _height = h; }
+ public void setSize(Size s) { _size = s; }
+
+ private static Size findSize(String id)
+ {
+ if (id.charAt(0) == 'L') {
+ if ("LARGE".equals(id)) {
+ return Size.LARGE;
+ }
+ } else if ("SMALL".equals(id)) {
+ return Size.SMALL;
+ }
+ throw new IllegalArgumentException();
+ }
+
+ public static Photo deserialize(JsonParser jp) throws IOException
+ {
+ Photo photo = new Photo();
+ while (jp.nextToken() == JsonToken.FIELD_NAME) {
+ String name = jp.getCurrentName();
+ jp.nextToken();
+ Integer I = sFields.get(name);
+ if (I != null) {
+ switch (I.intValue()) {
+ case F_URI:
+ photo.setUri(jp.getText());
+ continue;
+ case F_TITLE:
+ photo.setTitle(jp.getText());
+ continue;
+ case F_WIDTH:
+ photo.setWidth(jp.getIntValue());
+ continue;
+ case F_HEIGHT:
+ photo.setHeight(jp.getIntValue());
+ continue;
+ case F_SIZE:
+ photo.setSize(findSize(jp.getText()));
+ continue;
+ }
+ }
+ throw new IOException("Unknown field '"+name+"'");
+ }
+ if (jp.getCurrentToken() != JsonToken.END_OBJECT) {
+ throw new IOException("Need END_OBJECT to complete Photo");
+ }
+ return photo;
+ }
+
+ public void serialize(JsonGenerator jgen) throws IOException
+ {
+ jgen.writeStartObject();
+ jgen.writeStringField("uri", _uri);
+ jgen.writeStringField("title", _title);
+ jgen.writeNumberField("width", _width);
+ jgen.writeNumberField("height", _height);
+ jgen.writeStringField("size", (_size == null) ? null : _size.name());
+ jgen.writeEndObject();
+ }
+ }
+
+ @JsonPropertyOrder({"player","uri","title","width","height","format","duration","size","bitrate","persons","copyright"})
+ public static class Content
+ {
+ public final static int F_PLAYER = 0;
+ public final static int F_URI = 1;
+ public final static int F_TITLE = 2;
+ public final static int F_WIDTH = 3;
+ public final static int F_HEIGHT = 4;
+ public final static int F_FORMAT = 5;
+ public final static int F_DURATION = 6;
+ public final static int F_SIZE = 7;
+ public final static int F_BITRATE = 8;
+ public final static int F_PERSONS = 9;
+ public final static int F_COPYRIGHT = 10;
+
+ public final static HashMap<String,Integer> sFields = new HashMap<String,Integer>();
+ static {
+ sFields.put("player", F_PLAYER);
+ sFields.put("uri", F_URI);
+ sFields.put("title", F_TITLE);
+ sFields.put("width", F_WIDTH);
+ sFields.put("height", F_HEIGHT);
+ sFields.put("format", F_FORMAT);
+ sFields.put("duration", F_DURATION);
+ sFields.put("size", F_SIZE);
+ sFields.put("bitrate", F_BITRATE);
+ sFields.put("persons", F_PERSONS);
+ sFields.put("copyright", F_COPYRIGHT);
+ }
+
+ private Player _player;
+ private String _uri;
+ private String _title;
+ private int _width;
+ private int _height;
+ private String _format;
+ private long _duration;
+ private long _size;
+ private int _bitrate;
+ private List<String> _persons;
+ private String _copyright;
+
+ public Content() { }
+
+ public void addPerson(String p) {
+ if (_persons == null) {
+ _persons = new ArrayList<String>();
+ }
+ _persons.add(p);
+ }
+
+ public Player getPlayer() { return _player; }
+ public String getUri() { return _uri; }
+ public String getTitle() { return _title; }
+ public int getWidth() { return _width; }
+ public int getHeight() { return _height; }
+ public String getFormat() { return _format; }
+ public long getDuration() { return _duration; }
+ public long getSize() { return _size; }
+ public int getBitrate() { return _bitrate; }
+ public List<String> getPersons() { return _persons; }
+ public String getCopyright() { return _copyright; }
+
+ public void setPlayer(Player p) { _player = p; }
+ public void setUri(String u) { _uri = u; }
+ public void setTitle(String t) { _title = t; }
+ public void setWidth(int w) { _width = w; }
+ public void setHeight(int h) { _height = h; }
+ public void setFormat(String f) { _format = f; }
+ public void setDuration(long d) { _duration = d; }
+ public void setSize(long s) { _size = s; }
+ public void setBitrate(int b) { _bitrate = b; }
+ public void setPersons(List<String> p) { _persons = p; }
+ public void setCopyright(String c) { _copyright = c; }
+
+ private static Player findPlayer(String id)
+ {
+ if ("JAVA".equals(id)) {
+ return Player.JAVA;
+ }
+ if ("FLASH".equals(id)) {
+ return Player.FLASH;
+ }
+ throw new IllegalArgumentException("Weird Player value of '"+id+"'");
+ }
+
+ public static Content deserialize(JsonParser jp) throws IOException
+ {
+ if (jp.nextToken() != JsonToken.START_OBJECT) {
+ throw new IOException("Need START_OBJECT for Content");
+ }
+ Content content = new Content();
+
+ while (jp.nextToken() == JsonToken.FIELD_NAME) {
+ String name = jp.getCurrentName();
+ jp.nextToken();
+ Integer I = sFields.get(name);
+ if (I != null) {
+ switch (I.intValue()) {
+ case F_PLAYER:
+ content.setPlayer(findPlayer(jp.getText()));
+ case F_URI:
+ content.setUri(jp.getText());
+ continue;
+ case F_TITLE:
+ content.setTitle(jp.getText());
+ continue;
+ case F_WIDTH:
+ content.setWidth(jp.getIntValue());
+ continue;
+ case F_HEIGHT:
+ content.setHeight(jp.getIntValue());
+ continue;
+ case F_FORMAT:
+ content.setCopyright(jp.getText());
+ continue;
+ case F_DURATION:
+ content.setDuration(jp.getLongValue());
+ continue;
+ case F_SIZE:
+ content.setSize(jp.getLongValue());
+ continue;
+ case F_BITRATE:
+ content.setBitrate(jp.getIntValue());
+ continue;
+ case F_PERSONS:
+ content.setPersons(deserializePersons(jp));
+ continue;
+ case F_COPYRIGHT:
+ content.setCopyright(jp.getText());
+ continue;
+ }
+ }
+ throw new IOException("Unknown field '"+name+"'");
+ }
+ if (jp.getCurrentToken() != JsonToken.END_OBJECT) {
+ throw new IOException("Need END_OBJECT to complete Content");
+ }
+ return content;
+ }
+
+ private static List<String> deserializePersons(JsonParser jp) throws IOException
+ {
+ if (jp.getCurrentToken() != JsonToken.START_ARRAY) {
+ throw new IOException("Need START_ARRAY for List of Persons (got "+jp.getCurrentToken()+")");
+ }
+ ArrayList<String> persons = new ArrayList<String>(4);
+ while (jp.nextToken() == JsonToken.VALUE_STRING) {
+ persons.add(jp.getText());
+ }
+ if (jp.getCurrentToken() != JsonToken.END_ARRAY) {
+ throw new IOException("Need END_ARRAY to complete List of Persons");
+ }
+ return persons;
+ }
+
+ public void serialize(JsonGenerator jgen) throws IOException
+ {
+ jgen.writeStartObject();
+ jgen.writeStringField("player", (_player == null) ? null : _player.name());
+ jgen.writeStringField("uri", _uri);
+ jgen.writeStringField("title", _title);
+ jgen.writeNumberField("width", _width);
+ jgen.writeNumberField("height", _height);
+ jgen.writeStringField("format", _format);
+ jgen.writeNumberField("duration", _duration);
+ jgen.writeNumberField("size", _size);
+ jgen.writeNumberField("bitrate", _bitrate);
+ jgen.writeStringField("copyright", _copyright);
+ if (_persons == null) {
+ jgen.writeNullField("persons");
+ } else {
+ jgen.writeArrayFieldStart("persons");
+ for (String p : _persons) {
+ jgen.writeString(p);
+ }
+ jgen.writeEndArray();
+ }
+ jgen.writeEndObject();
+ }
+ }
+}
diff --git a/src/test/java/perf/SerPerf.java b/src/test/java/perf/SerPerf.java
new file mode 100644
index 0000000..d3c3e32
--- /dev/null
+++ b/src/test/java/perf/SerPerf.java
@@ -0,0 +1,146 @@
+package perf;
+
+import java.io.*;
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
+
+public final class SerPerf
+{
+ /*
+ /**********************************************************
+ /* Actual test
+ /**********************************************************
+ */
+
+ private final int REPS;
+
+ private SerPerf() throws Exception
+ {
+ // Let's try to guesstimate suitable size...
+ REPS = 6000;
+ }
+
+ private MediaItem buildItem()
+ {
+ MediaItem.Content content = new MediaItem.Content();
+ content.setPlayer(MediaItem.Player.JAVA);
+ content.setUri("http://javaone.com/keynote.mpg");
+ content.setTitle("Javaone Keynote");
+ content.setWidth(640);
+ content.setHeight(480);
+ content.setFormat("video/mpeg4");
+ content.setDuration(18000000L);
+ content.setSize(58982400L);
+ content.setBitrate(262144);
+ content.setCopyright("None");
+ content.addPerson("Bill Gates");
+ content.addPerson("Steve Jobs");
+
+ MediaItem item = new MediaItem(content);
+
+ item.addPhoto(new MediaItem.Photo("http://javaone.com/keynote_large.jpg", "Javaone Keynote", 1024, 768, MediaItem.Size.LARGE));
+ item.addPhoto(new MediaItem.Photo("http://javaone.com/keynote_small.jpg", "Javaone Keynote", 320, 240, MediaItem.Size.SMALL));
+
+ return item;
+ }
+
+ public void test()
+ throws Exception
+ {
+ int i = 0;
+ int sum = 0;
+
+ ByteArrayOutputStream result = new ByteArrayOutputStream();
+
+ final MediaItem item = buildItem();
+ final JsonFactory jsonF = new YAMLFactory();
+
+ final ObjectMapper jsonMapper = new ObjectMapper(jsonF);
+
+ JsonNode root = jsonMapper.valueToTree(item);
+
+ while (true) {
+// Thread.sleep(150L);
+ ++i;
+ int round = (i % 3);
+
+ // override?
+ round = 0;
+
+ long curr = System.currentTimeMillis();
+ String msg;
+
+ switch (round) {
+
+ case 0:
+ msg = "Serialize, JSON";
+ sum += testObjectSer(jsonMapper, item, REPS+REPS, result);
+ break;
+
+ case 1:
+ msg = "Serialize, JSON/manual";
+ sum += testObjectSer(jsonMapper.getFactory(), item, REPS+REPS, result);
+ break;
+
+ case 2:
+ msg = "Serialize, JsonNode";
+ sum += testNodeSer(jsonMapper, root, REPS+REPS, result);
+// sum += testNodeSer(smileMapper, root, REPS+REPS, result);
+ break;
+
+ default:
+ throw new Error("Internal error");
+ }
+
+ curr = System.currentTimeMillis() - curr;
+ if (round == 0) { System.out.println(); }
+ System.out.println("Test '"+msg+"' -> "+curr+" msecs ("+(sum & 0xFF)+").");
+ if ((i & 0x1F) == 0) { // GC every 64 rounds
+ System.out.println("[GC]");
+ Thread.sleep(20L);
+ System.gc();
+ Thread.sleep(20L);
+ }
+ }
+ }
+
+ protected int testObjectSer(ObjectMapper mapper, Object value, int reps, ByteArrayOutputStream result)
+ throws Exception
+ {
+ for (int i = 0; i < reps; ++i) {
+ result.reset();
+ mapper.writeValue(result, value);
+ }
+ return result.size(); // just to get some non-optimizable number
+ }
+
+ protected int testNodeSer(ObjectMapper mapper, JsonNode value, int reps, ByteArrayOutputStream result)
+ throws Exception
+ {
+ for (int i = 0; i < reps; ++i) {
+ result.reset();
+ mapper.writeValue(result, value);
+ }
+ return result.size(); // just to get some non-optimizable number
+ }
+
+ protected int testObjectSer(JsonFactory jf, MediaItem value, int reps, ByteArrayOutputStream result)
+ throws Exception
+ {
+ for (int i = 0; i < reps; ++i) {
+ result.reset();
+ JsonGenerator jgen = jf.createGenerator(result, JsonEncoding.UTF8);
+ value.serialize(jgen);
+ jgen.close();
+ }
+ return result.size(); // just to get some non-optimizable number
+ }
+
+ public static void main(String[] args) throws Exception
+ {
+ new SerPerf().test();
+ }
+}
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-java/jackson-dataformat-yaml.git
More information about the pkg-java-commits
mailing list