package org.msgpack.jackson.dataformat;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.base.ParserMinimalBase;
import com.fasterxml.jackson.core.io.IOContext;
import com.fasterxml.jackson.core.json.DupDetector;
import com.fasterxml.jackson.core.json.JsonReadContext;
import org.msgpack.core.MessageFormat;
import org.msgpack.core.MessageUnpacker;
import org.msgpack.core.buffer.ArrayBufferInput;
import org.msgpack.core.buffer.InputStreamBufferInput;
import org.msgpack.core.buffer.MessageBufferInput;
import org.msgpack.value.NumberValue;
import org.msgpack.value.ValueType;
import org.msgpack.value.holder.ValueHolder;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.LinkedList;
public class MessagePackParser extends ParserMinimalBase {
private static final ThreadLocal<MessageUnpacker> messageUnpackerHolder = new ThreadLocal<MessageUnpacker>();
private ObjectCodec codec;
private JsonReadContext parsingContext;
private final LinkedList<StackItem> stack = new LinkedList<StackItem>();
private final ValueHolder valueHolder = new ValueHolder();
private boolean isClosed;
private static abstract 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, InputStream in) throws IOException {
this(ctxt, features, new InputStreamBufferInput(in));
}
public MessagePackParser(IOContext ctxt, int features, byte[] bytes) throws IOException {
this(ctxt, features, new ArrayBufferInput(bytes));
}
private MessagePackParser(IOContext ctxt, int features, MessageBufferInput input) throws IOException {
DupDetector dups = Feature.STRICT_DUPLICATE_DETECTION.enabledIn(features)
? DupDetector.rootDetector(this) : null;
parsingContext = JsonReadContext.createRootContext(dups);
MessageUnpacker messageUnpacker = messageUnpackerHolder.get();
if (messageUnpacker == null) {
messageUnpacker = new MessageUnpacker(input);
}
else {
messageUnpacker.reset(input);
}
messageUnpackerHolder.set(messageUnpacker);
}
@Override
public ObjectCodec getCodec() {
return codec;
}
@Override
public void setCodec(ObjectCodec c) {
codec = c;
}
@Override
public Version version() {
return null;
}
@Override
public JsonToken nextToken() throws IOException, JsonParseException {
MessageUnpacker messageUnpacker = getMessageUnpacker();
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;
}
}
MessageFormat nextFormat = messageUnpacker.getNextFormat();
ValueType valueType = nextFormat.getValueType();
// We should push a new StackItem lazily after updating the current stack.
StackItem newStack = null;
switch (valueType) {
case NIL:
messageUnpacker.unpackNil();
nextToken = JsonToken.VALUE_NULL;
break;
case BOOLEAN:
boolean b = messageUnpacker.unpackBoolean();
nextToken = b ? JsonToken.VALUE_TRUE : JsonToken.VALUE_FALSE;
break;
case INTEGER:
messageUnpacker.unpackValue(valueHolder);
nextToken = JsonToken.VALUE_NUMBER_INT;
break;
case FLOAT:
messageUnpacker.unpackValue(valueHolder);
nextToken = JsonToken.VALUE_NUMBER_FLOAT;
break;
case STRING:
messageUnpacker.unpackValue(valueHolder);
if (parsingContext.inObject() && _currToken != JsonToken.FIELD_NAME) {
parsingContext.setCurrentName(valueHolder.getRef().asRaw().toString());
nextToken = JsonToken.FIELD_NAME;
}
else {
nextToken = JsonToken.VALUE_STRING;
}
break;
case BINARY:
messageUnpacker.unpackValue(valueHolder);
nextToken = JsonToken.VALUE_EMBEDDED_OBJECT;
break;
case ARRAY:
newStack = new StackItemForArray(messageUnpacker.unpackArrayHeader());
break;
case MAP:
newStack = new StackItemForObject(messageUnpacker.unpackMapHeader());
break;
case EXTENDED:
throw new UnsupportedOperationException();
default:
throw new IllegalStateException("Shouldn't reach here");
}
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;
return nextToken;
}
@Override
protected void _handleEOF() throws JsonParseException {}
@Override
public String getText() throws IOException, JsonParseException {
// This method can be called for new BigInteger(text)
return valueHolder.getRef().toString();
}
@Override
public char[] getTextCharacters() throws IOException, JsonParseException {
return getText().toCharArray();
}
@Override
public boolean hasTextCharacters() {
return false;
}
@Override
public int getTextLength() throws IOException, JsonParseException {
return getText().length();
}
@Override
public int getTextOffset() throws IOException, JsonParseException {
return 0;
}
@Override
public byte[] getBinaryValue(Base64Variant b64variant) throws IOException, JsonParseException {
return valueHolder.getRef().asBinary().toByteArray();
}
@Override
public Number getNumberValue() throws IOException, JsonParseException {
NumberValue numberValue = valueHolder.getRef().asNumber();
if (numberValue.isValidInt()) {
return numberValue.toInt();
}
else if (numberValue.isValidLong()) {
return numberValue.toLong();
}
else {
return numberValue.toBigInteger();
}
}
@Override
public int getIntValue() throws IOException, JsonParseException {
return valueHolder.getRef().asNumber().toInt();
}
@Override
public long getLongValue() throws IOException, JsonParseException {
return valueHolder.getRef().asNumber().toLong();
}
@Override
public BigInteger getBigIntegerValue() throws IOException, JsonParseException {
return valueHolder.getRef().asNumber().toBigInteger();
}
@Override
public float getFloatValue() throws IOException, JsonParseException {
return valueHolder.getRef().asFloat().toFloat();
}
@Override
public double getDoubleValue() throws IOException, JsonParseException {
return valueHolder.getRef().asFloat().toDouble();
}
@Override
public BigDecimal getDecimalValue() throws IOException {
return null;
}
@Override
public Object getEmbeddedObject() throws IOException, JsonParseException {
return valueHolder.getRef().asBinary().toByteArray();
}
@Override
public NumberType getNumberType() throws IOException, JsonParseException {
NumberValue numberValue = valueHolder.getRef().asNumber();
if (numberValue.isValidInt()) {
return NumberType.INT;
}
else if (numberValue.isValidLong()) {
return NumberType.LONG;
}
else {
return NumberType.BIG_INTEGER;
}
}
@Override
public void close() throws IOException {
try {
MessageUnpacker messageUnpacker = getMessageUnpacker();
messageUnpacker.close();
}
catch (Exception e) {
e.printStackTrace();
}
finally {
isClosed = true;
}
}
@Override
public boolean isClosed() {
return isClosed;
}
@Override
public JsonStreamContext getParsingContext() {
return parsingContext;
}
@Override
public JsonLocation getTokenLocation() {
throw new UnsupportedOperationException("Not implemented yet");
}
@Override
public JsonLocation getCurrentLocation() {
throw new UnsupportedOperationException("Not implemented yet");
}
@Override
public void overrideCurrentName(String name) {
throw new UnsupportedOperationException("Not implemented yet");
}
@Override public String getCurrentName() throws IOException {
if (_currToken == JsonToken.START_OBJECT || _currToken == JsonToken.START_ARRAY) {
JsonReadContext parent = parsingContext.getParent();
return parent.getCurrentName();
}
return parsingContext.getCurrentName();
}
private MessageUnpacker getMessageUnpacker() {
MessageUnpacker messageUnpacker = messageUnpackerHolder.get();
if (messageUnpacker == null) {
throw new IllegalStateException("messageUnpacker is null");
}
return messageUnpacker;
}
}