diff --git a/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/ExtensionTypeCustomDeserializers.java b/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/ExtensionTypeCustomDeserializers.java index ae2e6353..aa587975 100644 --- a/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/ExtensionTypeCustomDeserializers.java +++ b/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/ExtensionTypeCustomDeserializers.java @@ -35,15 +35,7 @@ public ExtensionTypeCustomDeserializers(ExtensionTypeCustomDeserializers src) public void addCustomDeser(byte type, final Deser deser) { - deserTable.put(type, new Deser() - { - @Override - public Object deserialize(byte[] data) - throws IOException - { - return deser.deserialize(data); - } - }); + deserTable.put(type, deser); } public Deser getDeser(byte type) diff --git a/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/JavaInfo.java b/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/JavaInfo.java new file mode 100644 index 00000000..f5fda8c2 --- /dev/null +++ b/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/JavaInfo.java @@ -0,0 +1,41 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +package org.msgpack.jackson.dataformat; + +import java.lang.reflect.Field; +import java.util.function.Supplier; + +public final class JavaInfo +{ + static final Supplier STRING_VALUE_FIELD_IS_CHARS; + static { + boolean stringValueFieldIsChars = false; + try { + Field stringValueField = String.class.getDeclaredField("value"); + stringValueFieldIsChars = stringValueField.getType() == char[].class; + } + catch (NoSuchFieldException ignored) { + } + if (stringValueFieldIsChars) { + STRING_VALUE_FIELD_IS_CHARS = () -> true; + } + else { + STRING_VALUE_FIELD_IS_CHARS = () -> false; + } + } + + private JavaInfo() {} +} diff --git a/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/MessagePackExtensionType.java b/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/MessagePackExtensionType.java index 00b4f7de..e11c2cd0 100644 --- a/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/MessagePackExtensionType.java +++ b/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/MessagePackExtensionType.java @@ -1,7 +1,6 @@ package org.msgpack.jackson.dataformat; import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonSerialize; @@ -52,7 +51,7 @@ public boolean equals(Object o) @Override public int hashCode() { - int result = (int) type; + int result = type; result = 31 * result + Arrays.hashCode(data); return result; } @@ -61,7 +60,7 @@ public static class Serializer extends JsonSerializer { @Override public void serialize(MessagePackExtensionType value, JsonGenerator gen, SerializerProvider serializers) - throws IOException, JsonProcessingException + throws IOException { if (gen instanceof MessagePackGenerator) { MessagePackGenerator msgpackGenerator = (MessagePackGenerator) gen; diff --git a/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/MessagePackFactory.java b/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/MessagePackFactory.java index 1e7acc5e..bb5064f1 100644 --- a/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/MessagePackFactory.java +++ b/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/MessagePackFactory.java @@ -18,7 +18,6 @@ import com.fasterxml.jackson.core.JsonEncoding; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.io.IOContext; import org.msgpack.core.MessagePack; @@ -97,14 +96,13 @@ public JsonGenerator createGenerator(File f, JsonEncoding enc) @Override public JsonGenerator createGenerator(Writer w) - throws IOException { throw new UnsupportedOperationException(); } @Override public JsonParser createParser(byte[] data) - throws IOException, JsonParseException + throws IOException { IOContext ioContext = _createContext(data, false); return _createParser(data, 0, data.length, ioContext); @@ -112,7 +110,7 @@ public JsonParser createParser(byte[] data) @Override public JsonParser createParser(InputStream in) - throws IOException, JsonParseException + throws IOException { IOContext ioContext = _createContext(in, false); return _createParser(in, ioContext); @@ -131,7 +129,7 @@ protected MessagePackParser _createParser(InputStream in, IOContext ctxt) @Override protected JsonParser _createParser(byte[] data, int offset, int len, IOContext ctxt) - throws IOException, JsonParseException + throws IOException { if (offset != 0 || len != data.length) { data = Arrays.copyOfRange(data, offset, offset + len); @@ -155,12 +153,6 @@ MessagePack.PackerConfig getPackerConfig() return packerConfig; } - @VisibleForTesting - boolean isReuseResourceInGenerator() - { - return reuseResourceInGenerator; - } - @VisibleForTesting boolean isReuseResourceInParser() { @@ -178,4 +170,10 @@ public String getFormatName() { return "msgpack"; } + + @Override + public boolean canHandleBinaryNatively() + { + return true; + } } diff --git a/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/MessagePackGenerator.java b/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/MessagePackGenerator.java index 96aa6a06..11aba6e5 100644 --- a/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/MessagePackGenerator.java +++ b/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/MessagePackGenerator.java @@ -16,14 +16,14 @@ package org.msgpack.jackson.dataformat; import com.fasterxml.jackson.core.Base64Variant; -import com.fasterxml.jackson.core.JsonGenerationException; import com.fasterxml.jackson.core.ObjectCodec; import com.fasterxml.jackson.core.SerializableString; import com.fasterxml.jackson.core.base.GeneratorBase; import com.fasterxml.jackson.core.io.SerializedString; -import com.fasterxml.jackson.core.json.JsonWriteContext; import org.msgpack.core.MessagePack; import org.msgpack.core.MessagePacker; +import org.msgpack.core.annotations.Nullable; +import org.msgpack.core.buffer.MessageBufferOutput; import org.msgpack.core.buffer.OutputStreamBufferOutput; import java.io.ByteArrayOutputStream; @@ -33,73 +33,170 @@ import java.math.BigInteger; import java.nio.ByteBuffer; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; -import java.util.LinkedList; import java.util.List; +import static org.msgpack.jackson.dataformat.JavaInfo.STRING_VALUE_FIELD_IS_CHARS; + public class MessagePackGenerator extends GeneratorBase { - private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); + private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; + private static final int IN_ROOT = 0; + private static final int IN_OBJECT = 1; + private static final int IN_ARRAY = 2; private final MessagePacker messagePacker; - private static ThreadLocal messageBufferOutputHolder = new ThreadLocal(); + private static final ThreadLocal messageBufferOutputHolder = new ThreadLocal<>(); private final OutputStream output; private final MessagePack.PackerConfig packerConfig; - private LinkedList stack; - private StackItem rootStackItem; - private abstract static class StackItem + private int currentParentElementIndex = -1; + private int currentState = IN_ROOT; + private final List nodes; + private boolean isElementsClosed = false; + + private static final class AsciiCharString + { + public final byte[] bytes; + + public AsciiCharString(byte[] bytes) + { + this.bytes = bytes; + } + } + + private abstract static class Node + { + // Root containers have -1. + final int parentIndex; + + public Node(int parentIndex) + { + this.parentIndex = parentIndex; + } + + abstract void incrementChildCount(); + + abstract int currentStateAsParent(); + } + + private abstract static class NodeContainer extends Node { - protected List objectKeys = new ArrayList(); - protected List objectValues = new ArrayList(); + // Only for containers. + int childCount; + + public NodeContainer(int parentIndex) + { + super(parentIndex); + } + + @Override + void incrementChildCount() + { + childCount++; + } + } - abstract void addKey(Object key); + private static final class NodeArray extends NodeContainer + { + public NodeArray(int parentIndex) + { + super(parentIndex); + } - void addValue(Object value) + @Override + int currentStateAsParent() { - objectValues.add(value); + return IN_ARRAY; } + } - abstract List getKeys(); + private static final class NodeObject extends NodeContainer + { + public NodeObject(int parentIndex) + { + super(parentIndex); + } - List getValues() + @Override + int currentStateAsParent() { - return objectValues; + return IN_OBJECT; } } - private static class StackItemForObject - extends StackItem + private static final class NodeEntryInArray extends Node { + final Object value; + + public NodeEntryInArray(int parentIndex, Object value) + { + super(parentIndex); + this.value = value; + } + @Override - void addKey(Object key) + void incrementChildCount() { - objectKeys.add(key); + throw new UnsupportedOperationException(); } @Override - List getKeys() + int currentStateAsParent() { - return objectKeys; + throw new UnsupportedOperationException(); } } - private static class StackItemForArray - extends StackItem + private static final class NodeEntryInObject extends Node { + final Object key; + // Lazily initialized. + Object value; + + public NodeEntryInObject(int parentIndex, Object key) + { + super(parentIndex); + this.key = key; + } + @Override - void addKey(Object key) + void incrementChildCount() { - throw new IllegalStateException("This method shouldn't be called"); + assert value instanceof NodeContainer; + ((NodeContainer) value).childCount++; } @Override - List getKeys() + int currentStateAsParent() { - throw new IllegalStateException("This method shouldn't be called"); + if (value instanceof NodeObject) { + return IN_OBJECT; + } + else if (value instanceof NodeArray) { + return IN_ARRAY; + } + else { + throw new AssertionError(); + } } } + // This is an internal constructor for nested serialization. + private MessagePackGenerator( + int features, + ObjectCodec codec, + OutputStream out, + MessagePack.PackerConfig packerConfig) + { + super(features, codec); + this.output = out; + this.messagePacker = packerConfig.newPacker(out); + this.packerConfig = packerConfig; + this.nodes = new ArrayList<>(); + } + public MessagePackGenerator( int features, ObjectCodec codec, @@ -110,7 +207,16 @@ public MessagePackGenerator( { super(features, codec); this.output = out; + this.messagePacker = packerConfig.newPacker(getMessageBufferOutputForOutputStream(out, reuseResourceInGenerator)); + this.packerConfig = packerConfig; + this.nodes = new ArrayList<>(); + } + private MessageBufferOutput getMessageBufferOutputForOutputStream( + OutputStream out, + boolean reuseResourceInGenerator) + throws IOException + { OutputStreamBufferOutput messageBufferOutput; if (reuseResourceInGenerator) { messageBufferOutput = messageBufferOutputHolder.get(); @@ -125,91 +231,106 @@ public MessagePackGenerator( else { messageBufferOutput = new OutputStreamBufferOutput(out); } - this.messagePacker = packerConfig.newPacker(messageBufferOutput); - - this.packerConfig = packerConfig; + return messageBufferOutput; + } - this.stack = new LinkedList(); + private String currentStateStr() + { + switch (currentState) { + case IN_OBJECT: + return "IN_OBJECT"; + case IN_ARRAY: + return "IN_ARRAY"; + default: + return "IN_ROOT"; + } } @Override public void writeStartArray() - throws IOException, JsonGenerationException { - _writeContext = _writeContext.createChildArrayContext(); - stack.push(new StackItemForArray()); + if (currentState == IN_OBJECT) { + Node node = nodes.get(nodes.size() - 1); + assert node instanceof NodeEntryInObject; + NodeEntryInObject nodeEntryInObject = (NodeEntryInObject) node; + nodeEntryInObject.value = new NodeArray(currentParentElementIndex); + } + else { + nodes.add(new NodeArray(currentParentElementIndex)); + } + currentParentElementIndex = nodes.size() - 1; + currentState = IN_ARRAY; } @Override public void writeEndArray() - throws IOException, JsonGenerationException + throws IOException { - if (!_writeContext.inArray()) { - _reportError("Current context not an array but " + _writeContext.getTypeDesc()); + if (currentState != IN_ARRAY) { + _reportError("Current context not an array but " + currentStateStr()); } - - getStackTopForArray(); - - _writeContext = _writeContext.getParent(); - - popStackAndStoreTheItemAsValue(); + endCurrentContainer(); } @Override public void writeStartObject() - throws IOException, JsonGenerationException { - _writeContext = _writeContext.createChildObjectContext(); - stack.push(new StackItemForObject()); + if (currentState == IN_OBJECT) { + Node node = nodes.get(nodes.size() - 1); + assert node instanceof NodeEntryInObject; + NodeEntryInObject nodeEntryInObject = (NodeEntryInObject) node; + nodeEntryInObject.value = new NodeObject(currentParentElementIndex); + } + else { + nodes.add(new NodeObject(currentParentElementIndex)); + } + currentParentElementIndex = nodes.size() - 1; + currentState = IN_OBJECT; } @Override public void writeEndObject() - throws IOException, JsonGenerationException + throws IOException { - if (!_writeContext.inObject()) { - _reportError("Current context not an object but " + _writeContext.getTypeDesc()); + if (currentState != IN_OBJECT) { + _reportError("Current context not an object but " + currentStateStr()); } + endCurrentContainer(); + } - StackItemForObject stackTop = getStackTopForObject(); - - if (stackTop.getKeys().size() != stackTop.getValues().size()) { - throw new IllegalStateException( - String.format( - "objectKeys.size() and objectValues.size() is not same: depth=%d, key=%d, value=%d", - stack.size(), stackTop.getKeys().size(), stackTop.getValues().size())); + private void endCurrentContainer() + { + Node parent = nodes.get(currentParentElementIndex); + if (currentParentElementIndex == 0) { + isElementsClosed = true; + currentParentElementIndex = parent.parentIndex; + return; } - _writeContext = _writeContext.getParent(); - popStackAndStoreTheItemAsValue(); + currentParentElementIndex = parent.parentIndex; + assert currentParentElementIndex >= 0; + Node currentParent = nodes.get(currentParentElementIndex); + currentParent.incrementChildCount(); + currentState = currentParent.currentStateAsParent(); } - private void pack(Object v) + private void packNonContainer(Object v) throws IOException { MessagePacker messagePacker = getMessagePacker(); - if (v == null) { - messagePacker.packNil(); + if (v instanceof String) { + messagePacker.packString((String) v); + } + else if (v instanceof AsciiCharString) { + byte[] bytes = ((AsciiCharString) v).bytes; + messagePacker.packRawStringHeader(bytes.length); + messagePacker.writePayload(bytes); } else if (v instanceof Integer) { messagePacker.packInt((Integer) v); } - else if (v instanceof ByteBuffer) { - ByteBuffer bb = (ByteBuffer) v; - int len = bb.remaining(); - if (bb.hasArray()) { - messagePacker.packBinaryHeader(len); - messagePacker.writePayload(bb.array(), bb.arrayOffset(), len); - } - else { - byte[] data = new byte[len]; - bb.get(data); - messagePacker.packBinaryHeader(len); - messagePacker.addPayload(data); - } - } - else if (v instanceof String) { - messagePacker.packString((String) v); + else if (v == null) { + messagePacker.packNil(); } else if (v instanceof Float) { messagePacker.packFloat((Float) v); @@ -217,12 +338,6 @@ else if (v instanceof Float) { else if (v instanceof Long) { messagePacker.packLong((Long) v); } - else if (v instanceof StackItemForObject) { - packObject((StackItemForObject) v); - } - else if (v instanceof StackItemForArray) { - packArray((StackItemForArray) v); - } else if (v instanceof Double) { messagePacker.packDouble((Double) v); } @@ -235,6 +350,20 @@ else if (v instanceof BigDecimal) { else if (v instanceof Boolean) { messagePacker.packBoolean((Boolean) v); } + else if (v instanceof ByteBuffer) { + ByteBuffer bb = (ByteBuffer) v; + int len = bb.remaining(); + if (bb.hasArray()) { + messagePacker.packBinaryHeader(len); + messagePacker.writePayload(bb.array(), bb.arrayOffset(), len); + } + else { + byte[] data = new byte[len]; + bb.get(data); + messagePacker.packBinaryHeader(len); + messagePacker.addPayload(data); + } + } else if (v instanceof MessagePackExtensionType) { MessagePackExtensionType extensionType = (MessagePackExtensionType) v; byte[] extData = extensionType.getData(); @@ -244,7 +373,7 @@ else if (v instanceof MessagePackExtensionType) { else { messagePacker.flush(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - MessagePackGenerator messagePackGenerator = new MessagePackGenerator(getFeatureMask(), getCodec(), outputStream, packerConfig, false); + MessagePackGenerator messagePackGenerator = new MessagePackGenerator(getFeatureMask(), getCodec(), outputStream, packerConfig); getCodec().writeValue(messagePackGenerator, v); output.write(outputStream.toByteArray()); } @@ -260,10 +389,7 @@ private void packBigDecimal(BigDecimal decimal) BigInteger integer = decimal.toBigIntegerExact(); messagePacker.packBigInteger(integer); } - catch (ArithmeticException e) { - failedToPackAsBI = true; - } - catch (IllegalArgumentException e) { + catch (ArithmeticException | IllegalArgumentException e) { failedToPackAsBI = true; } @@ -278,200 +404,297 @@ private void packBigDecimal(BigDecimal decimal) } } - private void packObject(StackItemForObject stackItem) + private void packObject(NodeObject container) throws IOException { - List keys = stackItem.getKeys(); - List values = stackItem.getValues(); + MessagePacker messagePacker = getMessagePacker(); + messagePacker.packMapHeader(container.childCount); + } + private void packArray(NodeArray container) + throws IOException + { MessagePacker messagePacker = getMessagePacker(); - messagePacker.packMapHeader(keys.size()); + messagePacker.packArrayHeader(container.childCount); + } - for (int i = 0; i < keys.size(); i++) { - pack(keys.get(i)); - pack(values.get(i)); + private void addKeyNode(Object key) + { + if (currentState != IN_OBJECT) { + throw new IllegalStateException(); } + Node node = new NodeEntryInObject(currentParentElementIndex, key); + nodes.add(node); } - private void packArray(StackItemForArray stackItem) - throws IOException + private void addValueNode(Object value) throws IOException { - List values = stackItem.getValues(); + switch (currentState) { + case IN_OBJECT: { + Node node = nodes.get(nodes.size() - 1); + assert node instanceof NodeEntryInObject; + NodeEntryInObject nodeEntryInObject = (NodeEntryInObject) node; + nodeEntryInObject.value = value; + nodes.get(node.parentIndex).incrementChildCount(); + break; + } + case IN_ARRAY: { + Node node = new NodeEntryInArray(currentParentElementIndex, value); + nodes.add(node); + nodes.get(node.parentIndex).incrementChildCount(); + break; + } + default: + packNonContainer(value); + flushMessagePacker(); + break; + } + } - MessagePacker messagePacker = getMessagePacker(); - messagePacker.packArrayHeader(values.size()); + @Nullable + private byte[] getBytesIfAscii(char[] chars, int offset, int len) + { + byte[] bytes = new byte[len]; + for (int i = offset; i < offset + len; i++) { + char c = chars[i]; + if (c >= 0x80) { + return null; + } + bytes[i] = (byte) c; + } + return bytes; + } - for (int i = 0; i < values.size(); i++) { - Object v = values.get(i); - pack(v); + private boolean areAllAsciiBytes(byte[] bytes, int offset, int len) + { + for (int i = offset; i < offset + len; i++) { + if ((bytes[i] & 0x80) != 0) { + return false; + } } + return true; + } + + private void writeCharArrayTextKey(char[] text, int offset, int len) + { + byte[] bytes = getBytesIfAscii(text, offset, len); + if (bytes != null) { + addKeyNode(new AsciiCharString(bytes)); + return; + } + addKeyNode(new String(text, offset, len)); + } + + private void writeCharArrayTextValue(char[] text, int offset, int len) throws IOException + { + byte[] bytes = getBytesIfAscii(text, offset, len); + if (bytes != null) { + addValueNode(new AsciiCharString(bytes)); + return; + } + addValueNode(new String(text, offset, len)); + } + + private void writeByteArrayTextValue(byte[] text, int offset, int len) throws IOException + { + if (areAllAsciiBytes(text, offset, len)) { + addValueNode(new AsciiCharString(text)); + return; + } + addValueNode(new String(text, offset, len, DEFAULT_CHARSET)); + } + + private void writeByteArrayTextKey(byte[] text, int offset, int len) throws IOException + { + if (areAllAsciiBytes(text, offset, len)) { + addValueNode(new AsciiCharString(text)); + return; + } + addValueNode(new String(text, offset, len, DEFAULT_CHARSET)); } @Override public void writeFieldName(String name) - throws IOException, JsonGenerationException { - addKeyToStackTop(name); + if (STRING_VALUE_FIELD_IS_CHARS.get()) { + char[] chars = name.toCharArray(); + writeCharArrayTextKey(chars, 0, chars.length); + } + else { + addKeyNode(name); + } } @Override public void writeFieldName(SerializableString name) - throws IOException { - if (name instanceof MessagePackSerializedString) { - addKeyToStackTop(((MessagePackSerializedString) name).getRawValue()); + if (name instanceof SerializedString) { + writeFieldName(name.getValue()); } - else if (name instanceof SerializedString) { - addKeyToStackTop(name.getValue()); + else if (name instanceof MessagePackSerializedString) { + addKeyNode(((MessagePackSerializedString) name).getRawValue()); } else { - System.out.println(name.getClass()); throw new IllegalArgumentException("Unsupported key: " + name); } } @Override public void writeString(String text) - throws IOException, JsonGenerationException + throws IOException { - addValueToStackTop(text); + if (STRING_VALUE_FIELD_IS_CHARS.get()) { + char[] chars = text.toCharArray(); + writeCharArrayTextValue(chars, 0, chars.length); + } + else { + addValueNode(text); + } } @Override public void writeString(char[] text, int offset, int len) - throws IOException, JsonGenerationException + throws IOException { - addValueToStackTop(new String(text, offset, len)); + writeCharArrayTextValue(text, offset, len); } @Override public void writeRawUTF8String(byte[] text, int offset, int length) - throws IOException, JsonGenerationException + throws IOException { - addValueToStackTop(new String(text, offset, length, DEFAULT_CHARSET)); + writeByteArrayTextValue(text, offset, length); } @Override public void writeUTF8String(byte[] text, int offset, int length) - throws IOException, JsonGenerationException + throws IOException { - addValueToStackTop(new String(text, offset, length, DEFAULT_CHARSET)); + writeByteArrayTextValue(text, offset, length); } @Override public void writeRaw(String text) - throws IOException, JsonGenerationException + throws IOException { - addValueToStackTop(text); + if (STRING_VALUE_FIELD_IS_CHARS.get()) { + char[] chars = text.toCharArray(); + writeCharArrayTextValue(chars, 0, chars.length); + } + else { + addValueNode(text); + } } @Override public void writeRaw(String text, int offset, int len) - throws IOException, JsonGenerationException + throws IOException { - addValueToStackTop(text.substring(0, len)); + // TODO: There is room to optimize this. + char[] chars = text.toCharArray(); + writeCharArrayTextValue(chars, offset, len); } @Override public void writeRaw(char[] text, int offset, int len) - throws IOException, JsonGenerationException + throws IOException { - addValueToStackTop(new String(text, offset, len)); + writeCharArrayTextValue(text, offset, len); } @Override public void writeRaw(char c) - throws IOException, JsonGenerationException + throws IOException { - addValueToStackTop(String.valueOf(c)); + writeCharArrayTextValue(new char[] { c }, 0, 1); } @Override public void writeBinary(Base64Variant b64variant, byte[] data, int offset, int len) - throws IOException, JsonGenerationException + throws IOException { - addValueToStackTop(ByteBuffer.wrap(data, offset, len)); + addValueNode(ByteBuffer.wrap(data, offset, len)); } @Override public void writeNumber(int v) - throws IOException, JsonGenerationException + throws IOException { - addValueToStackTop(Integer.valueOf(v)); + addValueNode(v); } @Override public void writeNumber(long v) - throws IOException, JsonGenerationException + throws IOException { - addValueToStackTop(Long.valueOf(v)); + addValueNode(v); } @Override public void writeNumber(BigInteger v) - throws IOException, JsonGenerationException + throws IOException { - addValueToStackTop(v); + addValueNode(v); } @Override public void writeNumber(double d) - throws IOException, JsonGenerationException + throws IOException { - addValueToStackTop(Double.valueOf(d)); + addValueNode(d); } @Override public void writeNumber(float f) - throws IOException, JsonGenerationException + throws IOException { - addValueToStackTop(Float.valueOf(f)); + addValueNode(f); } @Override public void writeNumber(BigDecimal dec) - throws IOException, JsonGenerationException + throws IOException { - addValueToStackTop(dec); + addValueNode(dec); } @Override public void writeNumber(String encodedValue) - throws IOException, JsonGenerationException, UnsupportedOperationException + throws IOException, UnsupportedOperationException { // There is a room to improve this API's performance while the implementation is robust. // If users can use other MessagePackGenerator#writeNumber APIs that accept // proper numeric types not String, it's better to use the other APIs instead. try { long l = Long.parseLong(encodedValue); - addValueToStackTop(l); + addValueNode(l); return; } - catch (NumberFormatException e) { + catch (NumberFormatException ignored) { } try { double d = Double.parseDouble(encodedValue); - addValueToStackTop(d); + addValueNode(d); return; } - catch (NumberFormatException e) { + catch (NumberFormatException ignored) { } try { BigInteger bi = new BigInteger(encodedValue); - addValueToStackTop(bi); + addValueNode(bi); return; } - catch (NumberFormatException e) { + catch (NumberFormatException ignored) { } try { BigDecimal bc = new BigDecimal(encodedValue); - addValueToStackTop(bc); + addValueNode(bc); return; } - catch (NumberFormatException e) { + catch (NumberFormatException ignored) { } throw new NumberFormatException(encodedValue); @@ -479,22 +702,22 @@ public void writeNumber(String encodedValue) @Override public void writeBoolean(boolean state) - throws IOException, JsonGenerationException + throws IOException { - addValueToStackTop(Boolean.valueOf(state)); + addValueNode(state); } @Override public void writeNull() - throws IOException, JsonGenerationException + throws IOException { - addValueToStackTop(null); + addValueNode(null); } public void writeExtensionType(MessagePackExtensionType extensionType) throws IOException { - addValueToStackTop(extensionType); + addValueNode(extensionType); } @Override @@ -516,19 +739,42 @@ public void close() public void flush() throws IOException { - if (rootStackItem != null) { - if (rootStackItem instanceof StackItemForObject) { - packObject((StackItemForObject) rootStackItem); + if (!isElementsClosed) { + // The whole elements are not closed yet. + return; + } + + for (int i = 0; i < nodes.size(); i++) { + Node node = nodes.get(i); + if (node instanceof NodeEntryInObject) { + NodeEntryInObject nodeEntry = (NodeEntryInObject) node; + packNonContainer(nodeEntry.key); + if (nodeEntry.value instanceof NodeObject) { + packObject((NodeObject) nodeEntry.value); + } + else if (nodeEntry.value instanceof NodeArray) { + packArray((NodeArray) nodeEntry.value); + } + else { + packNonContainer(nodeEntry.value); + } + } + else if (node instanceof NodeObject) { + packObject((NodeObject) node); + } + else if (node instanceof NodeEntryInArray) { + packNonContainer(((NodeEntryInArray) node).value); } - else if (rootStackItem instanceof StackItemForArray) { - packArray((StackItemForArray) rootStackItem); + else if (node instanceof NodeArray) { + packArray((NodeArray) node); } else { - throw new IllegalStateException("Unexpected rootStackItem: " + rootStackItem); + throw new AssertionError(); } - rootStackItem = null; - flushMessagePacker(); } + flushMessagePacker(); + nodes.clear(); + isElementsClosed = false; } private void flushMessagePacker() @@ -541,76 +787,18 @@ private void flushMessagePacker() @Override protected void _releaseBuffers() { - } - - @Override - protected void _verifyValueWrite(String typeMsg) - throws IOException, JsonGenerationException - { - int status = _writeContext.writeValue(); - if (status == JsonWriteContext.STATUS_EXPECT_NAME) { - _reportError("Can not " + typeMsg + ", expecting field name"); - } - } - - private StackItem getStackTop() - { - if (stack.isEmpty()) { - throw new IllegalStateException("The stack is empty"); - } - return stack.getFirst(); - } - - private StackItemForObject getStackTopForObject() - { - StackItem stackTop = getStackTop(); - if (!(stackTop instanceof StackItemForObject)) { - throw new IllegalStateException("The stack top should be Object: " + stackTop); - } - return (StackItemForObject) stackTop; - } - - private StackItemForArray getStackTopForArray() - { - StackItem stackTop = getStackTop(); - if (!(stackTop instanceof StackItemForArray)) { - throw new IllegalStateException("The stack top should be Array: " + stackTop); - } - return (StackItemForArray) stackTop; - } - - private void addKeyToStackTop(Object key) - { - getStackTop().addKey(key); - } - - private void addValueToStackTop(Object value) - throws IOException - { - if (stack.isEmpty()) { - pack(value); - flushMessagePacker(); + try { + messagePacker.close(); } - else { - getStackTop().addValue(value); + catch (IOException e) { + throw new RuntimeException("Failed to close MessagePacker", e); } } - private void popStackAndStoreTheItemAsValue() - throws IOException + @Override + protected void _verifyValueWrite(String typeMsg) throws IOException { - StackItem child = stack.pop(); - if (stack.size() > 0) { - addValueToStackTop(child); - } - else { - if (rootStackItem != null) { - throw new IllegalStateException("rootStackItem is not null"); - } - else { - rootStackItem = child; - } - } + // FIXME? } private MessagePacker getMessagePacker() diff --git a/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/MessagePackKeySerializer.java b/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/MessagePackKeySerializer.java index 8eaa8014..36fb235d 100644 --- a/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/MessagePackKeySerializer.java +++ b/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/MessagePackKeySerializer.java @@ -15,7 +15,6 @@ // package org.msgpack.jackson.dataformat; -import com.fasterxml.jackson.core.JsonGenerationException; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.ser.std.StdSerializer; @@ -32,7 +31,7 @@ public MessagePackKeySerializer() @Override public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) - throws JsonGenerationException, IOException + throws IOException { jgen.writeFieldName(new MessagePackSerializedString(value)); } diff --git a/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/MessagePackParser.java b/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/MessagePackParser.java index 2a95b69a..4876c797 100644 --- a/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/MessagePackParser.java +++ b/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/MessagePackParser.java @@ -17,9 +17,7 @@ import com.fasterxml.jackson.core.Base64Variant; import com.fasterxml.jackson.core.JsonLocation; -import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonStreamContext; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.core.ObjectCodec; @@ -28,7 +26,6 @@ import com.fasterxml.jackson.core.io.IOContext; import com.fasterxml.jackson.core.io.JsonEOFException; import com.fasterxml.jackson.core.json.DupDetector; -import com.fasterxml.jackson.core.json.JsonReadContext; import org.msgpack.core.ExtensionTypeHeader; import org.msgpack.core.MessageFormat; import org.msgpack.core.MessagePack; @@ -42,27 +39,29 @@ import java.io.InputStream; import java.math.BigDecimal; import java.math.BigInteger; -import java.util.LinkedList; +import java.nio.charset.StandardCharsets; + +import static org.msgpack.jackson.dataformat.JavaInfo.STRING_VALUE_FIELD_IS_CHARS; public class MessagePackParser extends ParserMinimalBase { - private static final ThreadLocal> messageUnpackerHolder = - new ThreadLocal>(); + private static final ThreadLocal> messageUnpackerHolder = new ThreadLocal<>(); private final MessageUnpacker messageUnpacker; - private static final BigInteger LONG_MIN = BigInteger.valueOf((long) Long.MIN_VALUE); - private static final BigInteger LONG_MAX = BigInteger.valueOf((long) Long.MAX_VALUE); + private static final BigInteger LONG_MIN = BigInteger.valueOf(Long.MIN_VALUE); + private static final BigInteger LONG_MAX = BigInteger.valueOf(Long.MAX_VALUE); private ObjectCodec codec; - private JsonReadContext parsingContext; + private MessagePackReadContext streamReadContext; - private final LinkedList stack = new LinkedList(); private boolean isClosed; private long tokenPosition; private long currentPosition; private final IOContext ioContext; private ExtensionTypeCustomDeserializers extTypeCustomDesers; + private final byte[] tempBytes = new byte[64]; + private final char[] tempChars = new char[64]; private enum Type { @@ -76,51 +75,6 @@ private enum Type private String stringValue; private BigInteger biValue; private MessagePackExtensionType extensionTypeValue; - private boolean reuseResourceInParser; - - private abstract static class StackItem - { - private long numOfElements; - - protected StackItem(long numOfElements) - { - this.numOfElements = numOfElements; - } - - public void consume() - { - numOfElements--; - } - - public boolean isEmpty() - { - return numOfElements == 0; - } - } - - private static class StackItemForObject - extends StackItem - { - StackItemForObject(long numOfElements) - { - super(numOfElements); - } - } - - private static class StackItemForArray - extends StackItem - { - StackItemForArray(long numOfElements) - { - super(numOfElements); - } - } - - public MessagePackParser(IOContext ctxt, int features, ObjectCodec objectCodec, InputStream in) - throws IOException - { - this(ctxt, features, objectCodec, in, true); - } public MessagePackParser( IOContext ctxt, @@ -133,12 +87,6 @@ public MessagePackParser( this(ctxt, features, new InputStreamBufferInput(in), objectCodec, in, reuseResourceInParser); } - public MessagePackParser(IOContext ctxt, int features, ObjectCodec objectCodec, byte[] bytes) - throws IOException - { - this(ctxt, features, objectCodec, bytes, true); - } - public MessagePackParser( IOContext ctxt, int features, @@ -164,17 +112,12 @@ private MessagePackParser(IOContext ctxt, ioContext = ctxt; DupDetector dups = Feature.STRICT_DUPLICATE_DETECTION.enabledIn(features) ? DupDetector.rootDetector(this) : null; - parsingContext = JsonReadContext.createRootContext(dups); - this.reuseResourceInParser = reuseResourceInParser; + streamReadContext = MessagePackReadContext.createRootContext(dups); if (!reuseResourceInParser) { - this.messageUnpacker = MessagePack.newDefaultUnpacker(input); + messageUnpacker = MessagePack.newDefaultUnpacker(input); return; } - else { - this.messageUnpacker = null; - } - MessageUnpacker messageUnpacker; Tuple messageUnpackerTuple = messageUnpackerHolder.get(); if (messageUnpackerTuple == null) { messageUnpacker = MessagePack.newDefaultUnpacker(input); @@ -189,7 +132,7 @@ private MessagePackParser(IOContext ctxt, } messageUnpacker = messageUnpackerTuple.second(); } - messageUnpackerHolder.set(new Tuple(src, messageUnpacker)); + messageUnpackerHolder.set(new Tuple<>(src, messageUnpacker)); } public void setExtensionTypeCustomDeserializers(ExtensionTypeCustomDeserializers extTypeCustomDesers) @@ -215,21 +158,47 @@ public Version version() return null; } + private String unpackString(MessageUnpacker messageUnpacker) throws IOException + { + int strLen = messageUnpacker.unpackRawStringHeader(); + if (strLen <= tempBytes.length) { + messageUnpacker.readPayload(tempBytes, 0, strLen); + if (STRING_VALUE_FIELD_IS_CHARS.get()) { + for (int i = 0; i < strLen; i++) { + byte b = tempBytes[i]; + if ((0x80 & b) != 0) { + return new String(tempBytes, 0, strLen, StandardCharsets.UTF_8); + } + tempChars[i] = (char) b; + } + return new String(tempChars, 0, strLen); + } + else { + return new String(tempBytes, 0, strLen); + } + } + else { + byte[] bytes = messageUnpacker.readPayload(strLen); + return new String(bytes, 0, strLen, StandardCharsets.UTF_8); + } + } + @Override - public JsonToken nextToken() - throws IOException, JsonParseException + public JsonToken nextToken() throws IOException { - MessageUnpacker messageUnpacker = getMessageUnpacker(); tokenPosition = messageUnpacker.getTotalReadBytes(); - JsonToken nextToken = null; - if (parsingContext.inObject() || parsingContext.inArray()) { - if (stack.getFirst().isEmpty()) { - stack.pop(); - _currToken = parsingContext.inObject() ? JsonToken.END_OBJECT : JsonToken.END_ARRAY; - parsingContext = parsingContext.getParent(); - - return _currToken; + boolean isObjectValueSet = streamReadContext.inObject() && _currToken != JsonToken.FIELD_NAME; + if (isObjectValueSet) { + if (!streamReadContext.expectMoreValues()) { + streamReadContext = streamReadContext.getParent(); + return _updateToken(JsonToken.END_OBJECT); + } + } + else if (streamReadContext.inArray()) { + if (!streamReadContext.expectMoreValues()) { + streamReadContext = streamReadContext.getParent(); + return _updateToken(JsonToken.END_ARRAY); } } @@ -238,24 +207,19 @@ public JsonToken nextToken() } MessageFormat format = messageUnpacker.getNextFormat(); - ValueType valueType = messageUnpacker.getNextFormat().getValueType(); - - // We should push a new StackItem lazily after updating the current stack. - StackItem newStack = null; + ValueType valueType = format.getValueType(); + JsonToken nextToken; switch (valueType) { - case NIL: - messageUnpacker.unpackNil(); - nextToken = JsonToken.VALUE_NULL; - break; - case BOOLEAN: - boolean b = messageUnpacker.unpackBoolean(); - if (parsingContext.inObject() && _currToken != JsonToken.FIELD_NAME) { - parsingContext.setCurrentName(Boolean.toString(b)); + case STRING: + type = Type.STRING; + stringValue = unpackString(messageUnpacker); + if (isObjectValueSet) { + streamReadContext.setCurrentName(stringValue); nextToken = JsonToken.FIELD_NAME; } else { - nextToken = b ? JsonToken.VALUE_TRUE : JsonToken.VALUE_FALSE; + nextToken = JsonToken.VALUE_STRING; } break; case INTEGER: @@ -289,42 +253,45 @@ public JsonToken nextToken() break; } - if (parsingContext.inObject() && _currToken != JsonToken.FIELD_NAME) { - parsingContext.setCurrentName(String.valueOf(v)); + if (isObjectValueSet) { + streamReadContext.setCurrentName(String.valueOf(v)); nextToken = JsonToken.FIELD_NAME; } else { nextToken = JsonToken.VALUE_NUMBER_INT; } break; - case FLOAT: - type = Type.DOUBLE; - doubleValue = messageUnpacker.unpackDouble(); - if (parsingContext.inObject() && _currToken != JsonToken.FIELD_NAME) { - parsingContext.setCurrentName(String.valueOf(doubleValue)); + case NIL: + messageUnpacker.unpackNil(); + nextToken = JsonToken.VALUE_NULL; + break; + case BOOLEAN: + boolean b = messageUnpacker.unpackBoolean(); + if (isObjectValueSet) { + streamReadContext.setCurrentName(Boolean.toString(b)); nextToken = JsonToken.FIELD_NAME; } else { - nextToken = JsonToken.VALUE_NUMBER_FLOAT; + nextToken = b ? JsonToken.VALUE_TRUE : JsonToken.VALUE_FALSE; } break; - case STRING: - type = Type.STRING; - stringValue = messageUnpacker.unpackString(); - if (parsingContext.inObject() && _currToken != JsonToken.FIELD_NAME) { - parsingContext.setCurrentName(stringValue); + case FLOAT: + type = Type.DOUBLE; + doubleValue = messageUnpacker.unpackDouble(); + if (isObjectValueSet) { + streamReadContext.setCurrentName(String.valueOf(doubleValue)); nextToken = JsonToken.FIELD_NAME; } else { - nextToken = JsonToken.VALUE_STRING; + nextToken = JsonToken.VALUE_NUMBER_FLOAT; } break; case BINARY: type = Type.BYTES; int len = messageUnpacker.unpackBinaryHeader(); bytesValue = messageUnpacker.readPayload(len); - if (parsingContext.inObject() && _currToken != JsonToken.FIELD_NAME) { - parsingContext.setCurrentName(new String(bytesValue, MessagePack.UTF8)); + if (isObjectValueSet) { + streamReadContext.setCurrentName(new String(bytesValue, MessagePack.UTF8)); nextToken = JsonToken.FIELD_NAME; } else { @@ -332,17 +299,19 @@ public JsonToken nextToken() } break; case ARRAY: - newStack = new StackItemForArray(messageUnpacker.unpackArrayHeader()); + nextToken = JsonToken.START_ARRAY; + streamReadContext = streamReadContext.createChildArrayContext(messageUnpacker.unpackArrayHeader()); break; case MAP: - newStack = new StackItemForObject(messageUnpacker.unpackMapHeader()); + nextToken = JsonToken.START_OBJECT; + streamReadContext = streamReadContext.createChildObjectContext(messageUnpacker.unpackMapHeader()); break; case EXTENSION: type = Type.EXT; ExtensionTypeHeader header = messageUnpacker.unpackExtensionTypeHeader(); extensionTypeValue = new MessagePackExtensionType(header.getType(), messageUnpacker.readPayload(header.getLength())); - if (parsingContext.inObject() && _currToken != JsonToken.FIELD_NAME) { - parsingContext.setCurrentName(deserializedExtensionTypeValue().toString()); + if (isObjectValueSet) { + streamReadContext.setCurrentName(deserializedExtensionTypeValue().toString()); nextToken = JsonToken.FIELD_NAME; } else { @@ -354,35 +323,18 @@ public JsonToken nextToken() } currentPosition = messageUnpacker.getTotalReadBytes(); - if (parsingContext.inObject() && nextToken != JsonToken.FIELD_NAME || parsingContext.inArray()) { - stack.getFirst().consume(); - } - - if (newStack != null) { - stack.push(newStack); - if (newStack instanceof StackItemForArray) { - nextToken = JsonToken.START_ARRAY; - parsingContext = parsingContext.createChildArrayContext(-1, -1); - } - else if (newStack instanceof StackItemForObject) { - nextToken = JsonToken.START_OBJECT; - parsingContext = parsingContext.createChildObjectContext(-1, -1); - } - } - _currToken = nextToken; + _updateToken(nextToken); return nextToken; } @Override protected void _handleEOF() - throws JsonParseException { } @Override - public String getText() - throws IOException, JsonParseException + public String getText() throws IOException { switch (type) { case STRING: @@ -405,8 +357,7 @@ public String getText() } @Override - public char[] getTextCharacters() - throws IOException, JsonParseException + public char[] getTextCharacters() throws IOException { return getText().toCharArray(); } @@ -418,22 +369,19 @@ public boolean hasTextCharacters() } @Override - public int getTextLength() - throws IOException, JsonParseException + public int getTextLength() throws IOException { return getText().length(); } @Override public int getTextOffset() - throws IOException, JsonParseException { return 0; } @Override public byte[] getBinaryValue(Base64Variant b64variant) - throws IOException, JsonParseException { switch (type) { case BYTES: @@ -449,7 +397,6 @@ public byte[] getBinaryValue(Base64Variant b64variant) @Override public Number getNumberValue() - throws IOException, JsonParseException { switch (type) { case INT: @@ -467,7 +414,6 @@ public Number getNumberValue() @Override public int getIntValue() - throws IOException, JsonParseException { switch (type) { case INT: @@ -485,7 +431,6 @@ public int getIntValue() @Override public long getLongValue() - throws IOException, JsonParseException { switch (type) { case INT: @@ -503,7 +448,6 @@ public long getLongValue() @Override public BigInteger getBigIntegerValue() - throws IOException, JsonParseException { switch (type) { case INT: @@ -521,7 +465,6 @@ public BigInteger getBigIntegerValue() @Override public float getFloatValue() - throws IOException, JsonParseException { switch (type) { case INT: @@ -539,11 +482,10 @@ public float getFloatValue() @Override public double getDoubleValue() - throws IOException, JsonParseException { switch (type) { case INT: - return (double) intValue; + return intValue; case LONG: return (double) longValue; case DOUBLE: @@ -557,7 +499,6 @@ public double getDoubleValue() @Override public BigDecimal getDecimalValue() - throws IOException { switch (type) { case INT: @@ -586,8 +527,7 @@ private Object deserializedExtensionTypeValue() } @Override - public Object getEmbeddedObject() - throws IOException, JsonParseException + public Object getEmbeddedObject() throws IOException { switch (type) { case BYTES: @@ -601,7 +541,6 @@ public Object getEmbeddedObject() @Override public NumberType getNumberType() - throws IOException, JsonParseException { switch (type) { case INT: @@ -623,7 +562,6 @@ public void close() { try { if (isEnabled(JsonParser.Feature.AUTO_CLOSE_SOURCE)) { - MessageUnpacker messageUnpacker = getMessageUnpacker(); messageUnpacker.close(); } } @@ -641,7 +579,7 @@ public boolean isClosed() @Override public JsonStreamContext getParsingContext() { - return parsingContext; + return streamReadContext; } @Override @@ -659,41 +597,34 @@ public JsonLocation getCurrentLocation() @Override public void overrideCurrentName(String name) { + // Simple, but need to look for START_OBJECT/ARRAY's "off-by-one" thing: + MessagePackReadContext ctxt = streamReadContext; + if (_currToken == JsonToken.START_OBJECT || _currToken == JsonToken.START_ARRAY) { + ctxt = ctxt.getParent(); + } + // Unfortunate, but since we did not expose exceptions, need to wrap try { - if (_currToken == JsonToken.START_OBJECT || _currToken == JsonToken.START_ARRAY) { - JsonReadContext parent = parsingContext.getParent(); - parent.setCurrentName(name); - } - else { - parsingContext.setCurrentName(name); - } + ctxt.setCurrentName(name); } - catch (JsonProcessingException e) { + catch (IOException e) { throw new IllegalStateException(e); } } @Override - public String getCurrentName() - throws IOException + public String currentName() { if (_currToken == JsonToken.START_OBJECT || _currToken == JsonToken.START_ARRAY) { - JsonReadContext parent = parsingContext.getParent(); + MessagePackReadContext parent = streamReadContext.getParent(); return parent.getCurrentName(); } - return parsingContext.getCurrentName(); + return streamReadContext.getCurrentName(); } - private MessageUnpacker getMessageUnpacker() + @Override + public String getCurrentName() + throws IOException { - if (!reuseResourceInParser) { - return this.messageUnpacker; - } - - Tuple messageUnpackerTuple = messageUnpackerHolder.get(); - if (messageUnpackerTuple == null) { - throw new IllegalStateException("messageUnpacker is null"); - } - return messageUnpackerTuple.second(); + return currentName(); } } diff --git a/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/MessagePackReadContext.java b/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/MessagePackReadContext.java new file mode 100644 index 00000000..403f1b36 --- /dev/null +++ b/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/MessagePackReadContext.java @@ -0,0 +1,269 @@ +package org.msgpack.jackson.dataformat; + +import com.fasterxml.jackson.core.JsonLocation; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.JsonStreamContext; +import com.fasterxml.jackson.core.io.CharTypes; +import com.fasterxml.jackson.core.io.ContentReference; +import com.fasterxml.jackson.core.json.DupDetector; + +/** + * Replacement of {@link com.fasterxml.jackson.core.json.JsonReadContext} + * to support features needed by MessagePack format. + */ +public final class MessagePackReadContext + extends JsonStreamContext +{ + /** + * Parent context for this context; null for root context. + */ + protected final MessagePackReadContext parent; + + // // // Optional duplicate detection + + protected final DupDetector dups; + + /** + * For fixed-size Arrays, Objects, this indicates expected number of entries. + */ + protected int expEntryCount; + + // // // Location information (minus source reference) + + protected String currentName; + + protected Object currentValue; + + /* + /********************************************************** + /* Simple instance reuse slots + /********************************************************** + */ + + protected MessagePackReadContext child = null; + + /* + /********************************************************** + /* Instance construction, reuse + /********************************************************** + */ + + public MessagePackReadContext(MessagePackReadContext parent, DupDetector dups, + int type, int expEntryCount) + { + super(); + this.parent = parent; + this.dups = dups; + _type = type; + this.expEntryCount = expEntryCount; + _index = -1; + _nestingDepth = parent == null ? 0 : parent._nestingDepth + 1; + } + + protected void reset(int type, int expEntryCount) + { + _type = type; + this.expEntryCount = expEntryCount; + _index = -1; + currentName = null; + currentValue = null; + if (dups != null) { + dups.reset(); + } + } + + @Override + public Object getCurrentValue() + { + return currentValue; + } + + @Override + public void setCurrentValue(Object v) + { + currentValue = v; + } + + // // // Factory methods + + public static MessagePackReadContext createRootContext(DupDetector dups) + { + return new MessagePackReadContext(null, dups, TYPE_ROOT, -1); + } + + public MessagePackReadContext createChildArrayContext(int expEntryCount) + { + MessagePackReadContext ctxt = child; + if (ctxt == null) { + ctxt = new MessagePackReadContext(this, + (dups == null) ? null : dups.child(), + TYPE_ARRAY, expEntryCount); + child = ctxt; + } + else { + ctxt.reset(TYPE_ARRAY, expEntryCount); + } + return ctxt; + } + + public MessagePackReadContext createChildObjectContext(int expEntryCount) + { + MessagePackReadContext ctxt = child; + if (ctxt == null) { + ctxt = new MessagePackReadContext(this, + (dups == null) ? null : dups.child(), + TYPE_OBJECT, expEntryCount); + child = ctxt; + return ctxt; + } + ctxt.reset(TYPE_OBJECT, expEntryCount); + return ctxt; + } + + /* + /********************************************************** + /* Abstract method implementation + /********************************************************** + */ + + @Override + public String getCurrentName() + { + return currentName; + } + + @Override + public MessagePackReadContext getParent() + { + return parent; + } + + /* + /********************************************************** + /* Extended API + /********************************************************** + */ + + public boolean hasExpectedLength() + { + return (expEntryCount >= 0); + } + + public int getExpectedLength() + { + return expEntryCount; + } + + public boolean isEmpty() + { + return expEntryCount == 0; + } + + public int getRemainingExpectedLength() + { + int diff = expEntryCount - _index; + // Negative values would occur when expected count is -1 + return Math.max(0, diff); + } + + public boolean acceptsBreakMarker() + { + return (expEntryCount < 0) && _type != TYPE_ROOT; + } + + /** + * Method called to increment the current entry count (Object property, Array + * element or Root value) for this context level + * and then see if more entries are accepted. + * The only case where more entries are NOT expected is for fixed-count + * Objects and Arrays that just reached the entry count. + *

+ * Note that since the entry count is updated this is a state-changing method. + */ + public boolean expectMoreValues() + { + if (++_index == expEntryCount) { + return false; + } + return true; + } + + /** + * @return Location pointing to the point where the context + * start marker was found + */ + @Override + public JsonLocation startLocation(ContentReference srcRef) + { + return new JsonLocation(srcRef, 1L, -1, -1); + } + + @Override + @Deprecated // since 2.13 + public JsonLocation getStartLocation(Object rawSrc) + { + return startLocation(ContentReference.rawReference(rawSrc)); + } + + /* + /********************************************************** + /* State changes + /********************************************************** + */ + + public void setCurrentName(String name) throws JsonProcessingException + { + currentName = name; + if (dups != null) { + _checkDup(dups, name); + } + } + + private void _checkDup(DupDetector dd, String name) throws JsonProcessingException + { + if (dd.isDup(name)) { + throw new JsonParseException(null, + "Duplicate field '" + name + "'", dd.findLocation()); + } + } + + /* + /********************************************************** + /* Overridden standard methods + /********************************************************** + */ + + /** + * Overridden to provide developer readable "JsonPath" representation + * of the context. + */ + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(64); + switch (_type) { + case TYPE_ROOT: + sb.append("/"); + break; + case TYPE_ARRAY: + sb.append('['); + sb.append(getCurrentIndex()); + sb.append(']'); + break; + case TYPE_OBJECT: + sb.append('{'); + if (currentName != null) { + sb.append('"'); + CharTypes.appendQuoted(sb, currentName); + sb.append('"'); + } + else { + sb.append('?'); + } + sb.append('}'); + break; + } + return sb.toString(); + } +} diff --git a/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/MessagePackSerializedString.java b/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/MessagePackSerializedString.java index c7c65ff2..72ed5d8d 100644 --- a/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/MessagePackSerializedString.java +++ b/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/MessagePackSerializedString.java @@ -17,15 +17,15 @@ import com.fasterxml.jackson.core.SerializableString; -import java.io.IOException; import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; public class MessagePackSerializedString implements SerializableString { - private static final Charset UTF8 = Charset.forName("UTF-8"); + private static final Charset UTF8 = StandardCharsets.UTF_8; private final Object value; public MessagePackSerializedString(Object value) @@ -89,28 +89,24 @@ public int appendUnquoted(char[] chars, int i) @Override public int writeQuotedUTF8(OutputStream outputStream) - throws IOException { return 0; } @Override public int writeUnquotedUTF8(OutputStream outputStream) - throws IOException { return 0; } @Override public int putQuotedUTF8(ByteBuffer byteBuffer) - throws IOException { return 0; } @Override public int putUnquotedUTF8(ByteBuffer byteBuffer) - throws IOException { return 0; } diff --git a/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/MessagePackDataformatForPojoTest.java b/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/MessagePackDataformatForPojoTest.java index c6b02068..52269a7d 100644 --- a/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/MessagePackDataformatForPojoTest.java +++ b/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/MessagePackDataformatForPojoTest.java @@ -46,6 +46,7 @@ public void testNormal() assertArrayEquals(normalPojo.b, value.b); assertEquals(normalPojo.bi, value.bi); assertEquals(normalPojo.suit, Suit.HEART); + assertEquals(normalPojo.sMultibyte, value.sMultibyte); } @Test @@ -59,13 +60,38 @@ public void testNestedList() } @Test - public void testNestedListComplex() + public void testNestedListComplex1() throws IOException { - byte[] bytes = objectMapper.writeValueAsBytes(nestedListComplexPojo); - NestedListComplexPojo value = objectMapper.readValue(bytes, NestedListComplexPojo.class); - assertEquals(nestedListPojo.s, value.s); - assertEquals(nestedListComplexPojo.foos.get(0).t, value.foos.get(0).t); + byte[] bytes = objectMapper.writeValueAsBytes(nestedListComplexPojo1); + NestedListComplexPojo1 value = objectMapper.readValue(bytes, NestedListComplexPojo1.class); + assertEquals(nestedListComplexPojo1.s, value.s); + assertEquals(1, nestedListComplexPojo1.foos.size()); + assertEquals(nestedListComplexPojo1.foos.get(0).t, value.foos.get(0).t); + } + + @Test + public void testNestedListComplex2() + throws IOException + { + byte[] bytes = objectMapper.writeValueAsBytes(nestedListComplexPojo2); + NestedListComplexPojo2 value = objectMapper.readValue(bytes, NestedListComplexPojo2.class); + assertEquals(nestedListComplexPojo2.s, value.s); + assertEquals(2, nestedListComplexPojo2.foos.size()); + assertEquals(nestedListComplexPojo2.foos.get(0).t, value.foos.get(0).t); + assertEquals(nestedListComplexPojo2.foos.get(1).t, value.foos.get(1).t); + } + + @Test + public void testStrings() + throws IOException + { + byte[] bytes = objectMapper.writeValueAsBytes(stringPojo); + StringPojo value = objectMapper.readValue(bytes, StringPojo.class); + assertEquals(stringPojo.shortSingleByte, value.shortSingleByte); + assertEquals(stringPojo.longSingleByte, value.longSingleByte); + assertEquals(stringPojo.shortMultiByte, value.shortMultiByte); + assertEquals(stringPojo.longMultiByte, value.longMultiByte); } @Test diff --git a/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/MessagePackDataformatTestBase.java b/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/MessagePackDataformatTestBase.java index d2d3d456..b9ef4cf3 100644 --- a/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/MessagePackDataformatTestBase.java +++ b/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/MessagePackDataformatTestBase.java @@ -43,7 +43,9 @@ public class MessagePackDataformatTestBase protected ObjectMapper objectMapper; protected NormalPojo normalPojo; protected NestedListPojo nestedListPojo; - protected NestedListComplexPojo nestedListComplexPojo; + protected NestedListComplexPojo1 nestedListComplexPojo1; + protected NestedListComplexPojo2 nestedListComplexPojo2; + protected StringPojo stringPojo; protected TinyPojo tinyPojo; protected ComplexPojo complexPojo; @@ -65,18 +67,31 @@ public void setup() normalPojo.b = new byte[] {0x01, 0x02, (byte) 0xFE, (byte) 0xFF}; normalPojo.bi = BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE); normalPojo.suit = Suit.HEART; + normalPojo.sMultibyte = "text文字"; nestedListPojo = new NestedListPojo(); nestedListPojo.s = "a string"; - nestedListPojo.strs = Arrays.asList("string", "another string", "another string"); + nestedListPojo.strs = Arrays.asList("string#1", "string#2", "string#3"); tinyPojo = new TinyPojo(); tinyPojo.t = "t string"; - nestedListComplexPojo = new NestedListComplexPojo(); - nestedListComplexPojo.s = "a string"; - nestedListComplexPojo.foos = new ArrayList(); - nestedListComplexPojo.foos.add(tinyPojo); + nestedListComplexPojo1 = new NestedListComplexPojo1(); + nestedListComplexPojo1.s = "a string"; + nestedListComplexPojo1.foos = new ArrayList<>(); + nestedListComplexPojo1.foos.add(tinyPojo); + + nestedListComplexPojo2 = new NestedListComplexPojo2(); + nestedListComplexPojo2.foos = new ArrayList<>(); + nestedListComplexPojo2.foos.add(tinyPojo); + nestedListComplexPojo2.foos.add(tinyPojo); + nestedListComplexPojo2.s = "another string"; + + stringPojo = new StringPojo(); + stringPojo.shortSingleByte = "hello"; + stringPojo.longSingleByte = "helloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworld"; + stringPojo.shortMultiByte = "こんにちは"; + stringPojo.longMultiByte = "こんにちは、世界!!こんにちは、世界!!こんにちは、世界!!こんにちは、世界!!こんにちは、世界!!"; complexPojo = new ComplexPojo(); complexPojo.name = "komamitsu"; @@ -131,12 +146,26 @@ public static class TinyPojo public String t; } - public static class NestedListComplexPojo + public static class NestedListComplexPojo1 { public String s; public List foos; } + public static class NestedListComplexPojo2 + { + public List foos; + public String s; + } + + public static class StringPojo + { + public String shortSingleByte; + public String longSingleByte; + public String shortMultiByte; + public String longMultiByte; + } + public static class NormalPojo { String s; @@ -148,6 +177,7 @@ public static class NormalPojo public byte[] b; public BigInteger bi; public Suit suit; + public String sMultibyte; public String getS() { diff --git a/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/MessagePackGeneratorTest.java b/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/MessagePackGeneratorTest.java index 931a33f5..7ba48a61 100644 --- a/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/MessagePackGeneratorTest.java +++ b/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/MessagePackGeneratorTest.java @@ -40,6 +40,7 @@ import java.math.BigDecimal; import java.math.BigInteger; import java.nio.ByteBuffer; +import java.nio.file.Files; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -283,6 +284,8 @@ public void testWritePrimitives() generator.writeNumber(0); generator.writeString("one"); generator.writeNumber(2.0f); + generator.writeString("三"); + generator.writeString("444④"); generator.flush(); generator.close(); @@ -291,6 +294,8 @@ public void testWritePrimitives() assertEquals(0, unpacker.unpackInt()); assertEquals("one", unpacker.unpackString()); assertEquals(2.0f, unpacker.unpackFloat(), 0.001f); + assertEquals("三", unpacker.unpackString()); + assertEquals("444④", unpacker.unpackString()); assertFalse(unpacker.hasNext()); } @@ -382,23 +387,24 @@ public void testWritePrimitiveObjectViaObjectMapper() throws Exception { File tempFile = createTempFile(); - OutputStream out = new FileOutputStream(tempFile); - - ObjectMapper objectMapper = new ObjectMapper(new MessagePackFactory()); - objectMapper.configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false); - objectMapper.writeValue(out, 1); - objectMapper.writeValue(out, "two"); - objectMapper.writeValue(out, 3.14); - objectMapper.writeValue(out, Arrays.asList(4)); - objectMapper.writeValue(out, 5L); - - MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(new FileInputStream(tempFile)); - assertEquals(1, unpacker.unpackInt()); - assertEquals("two", unpacker.unpackString()); - assertEquals(3.14, unpacker.unpackFloat(), 0.0001); - assertEquals(1, unpacker.unpackArrayHeader()); - assertEquals(4, unpacker.unpackInt()); - assertEquals(5, unpacker.unpackLong()); + try (OutputStream out = Files.newOutputStream(tempFile.toPath())) { + ObjectMapper objectMapper = new ObjectMapper(new MessagePackFactory()); + objectMapper.configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false); + objectMapper.writeValue(out, 1); + objectMapper.writeValue(out, "two"); + objectMapper.writeValue(out, 3.14); + objectMapper.writeValue(out, Arrays.asList(4)); + objectMapper.writeValue(out, 5L); + } + + try (MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(new FileInputStream(tempFile))) { + assertEquals(1, unpacker.unpackInt()); + assertEquals("two", unpacker.unpackString()); + assertEquals(3.14, unpacker.unpackFloat(), 0.0001); + assertEquals(1, unpacker.unpackArrayHeader()); + assertEquals(4, unpacker.unpackInt()); + assertEquals(5, unpacker.unpackLong()); + } } @Test @@ -442,10 +448,11 @@ public Exception call() throw exception; } else { - ByteArrayOutputStream outputStream = buffers.get(ti); - MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(outputStream.toByteArray()); - for (int i = 0; i < loopCount; i++) { - assertEquals(ti, unpacker.unpackInt()); + try (ByteArrayOutputStream outputStream = buffers.get(ti); + MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(outputStream.toByteArray())) { + for (int i = 0; i < loopCount; i++) { + assertEquals(ti, unpacker.unpackInt()); + } } } } diff --git a/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/benchmark/MessagePackDataformatPojoBenchmarkTest.java b/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/benchmark/MessagePackDataformatPojoBenchmarkTest.java index 179b0989..2713eaea 100644 --- a/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/benchmark/MessagePackDataformatPojoBenchmarkTest.java +++ b/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/benchmark/MessagePackDataformatPojoBenchmarkTest.java @@ -15,7 +15,6 @@ // package org.msgpack.jackson.dataformat.benchmark; -import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Test; @@ -23,9 +22,6 @@ import static org.msgpack.jackson.dataformat.MessagePackDataformatTestBase.NormalPojo; import static org.msgpack.jackson.dataformat.MessagePackDataformatTestBase.Suit; -import java.io.File; -import java.io.FileOutputStream; -import java.io.OutputStream; import java.math.BigInteger; import java.util.ArrayList; import java.util.List; @@ -45,9 +41,6 @@ public class MessagePackDataformatPojoBenchmarkTest public MessagePackDataformatPojoBenchmarkTest() { - origObjectMapper.configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false); - msgpackObjectMapper.configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false); - for (int i = 0; i < LOOP_MAX; i++) { NormalPojo pojo = new NormalPojo(); pojo.i = i; @@ -76,6 +69,7 @@ public MessagePackDataformatPojoBenchmarkTest() break; } pojo.b = new byte[] {(byte) i}; + pojo.sMultibyte = "012345678Ⅸ"; pojos.add(pojo); } @@ -104,14 +98,6 @@ public void testBenchmark() { Benchmarker benchmarker = new Benchmarker(); - File tempFileJackson = File.createTempFile("msgpack-jackson-", "-huge-jackson"); - tempFileJackson.deleteOnExit(); - final OutputStream outputStreamJackson = new FileOutputStream(tempFileJackson); - - File tempFileMsgpack = File.createTempFile("msgpack-jackson-", "-huge-msgpack"); - tempFileMsgpack.deleteOnExit(); - final OutputStream outputStreamMsgpack = new FileOutputStream(tempFileMsgpack); - benchmarker.addBenchmark(new Benchmarker.Benchmarkable("serialize(pojo) with JSON") { @Override public void run() @@ -119,7 +105,7 @@ public void run() { for (int j = 0; j < LOOP_FACTOR_SER; j++) { for (int i = 0; i < LOOP_MAX; i++) { - origObjectMapper.writeValue(outputStreamJackson, pojos.get(i)); + origObjectMapper.writeValueAsBytes(pojos.get(i)); } } } @@ -132,7 +118,7 @@ public void run() { for (int j = 0; j < LOOP_FACTOR_SER; j++) { for (int i = 0; i < LOOP_MAX; i++) { - msgpackObjectMapper.writeValue(outputStreamMsgpack, pojos.get(i)); + msgpackObjectMapper.writeValueAsBytes(pojos.get(i)); } } } @@ -164,12 +150,6 @@ public void run() } }); - try { - benchmarker.run(COUNT, WARMUP_COUNT); - } - finally { - outputStreamJackson.close(); - outputStreamMsgpack.close(); - } + benchmarker.run(COUNT, WARMUP_COUNT); } }