package org.msgpack.jackson.dataformat.msgpack; import com.fasterxml.jackson.core.Base64Variant; import com.fasterxml.jackson.core.JsonGenerationException; import com.fasterxml.jackson.core.ObjectCodec; import com.fasterxml.jackson.core.base.GeneratorBase; import com.fasterxml.jackson.core.json.JsonWriteContext; import org.msgpack.core.MessagePacker; import org.msgpack.core.buffer.OutputStreamBufferOutput; import sun.reflect.generics.reflectiveObjects.NotImplementedException; import java.io.IOException; import java.io.OutputStream; import java.math.BigDecimal; import java.math.BigInteger; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; public class MessagePackGenerator extends GeneratorBase { private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); private static ThreadLocal<MessagePacker> messagePackersHolder = new ThreadLocal<MessagePacker>(); private static ThreadLocal<OutputStreamBufferOutput> messageBufferOutputHolder = new ThreadLocal<OutputStreamBufferOutput>(); private LinkedList<StackItem> stack; private StackItem rootStackItem; private static abstract class StackItem { protected List<String> objectKeys = new ArrayList<String>(); protected List<Object> objectValues = new ArrayList<Object>(); abstract void addKey(String key); void addValue(Object value) { objectValues.add(value); } abstract List<String> getKeys(); List<Object> getValues() { return objectValues; } } private static class StackItemForObject extends StackItem { @Override void addKey(String key) { objectKeys.add(key); } @Override List<String> getKeys() { return objectKeys; } } private static class StackItemForArray extends StackItem { @Override void addKey(String key) { throw new IllegalStateException("This method shouldn't be called"); } @Override List<String> getKeys() { throw new IllegalStateException("This method shouldn't be called"); } } public MessagePackGenerator(int features, ObjectCodec codec, OutputStream out) throws IOException { super(features, codec); MessagePacker messagePacker = messagePackersHolder.get(); OutputStreamBufferOutput messageBufferOutput = messageBufferOutputHolder.get(); if (messageBufferOutput == null) { messageBufferOutput = new OutputStreamBufferOutput(out); } else { messageBufferOutput.reset(out); } messageBufferOutputHolder.set(messageBufferOutput); if (messagePacker == null) { messagePacker = new MessagePacker(messageBufferOutput); } else { messagePacker.reset(messageBufferOutput); } messagePackersHolder.set(messagePacker); this.stack = new LinkedList<StackItem>(); } @Override public void writeStartArray() throws IOException, JsonGenerationException { _writeContext = _writeContext.createChildArrayContext(); stack.push(new StackItemForArray()); } @Override public void writeEndArray() throws IOException, JsonGenerationException { if (!_writeContext.inArray()) { _reportError("Current context not an array but " + _writeContext.getTypeDesc()); } getStackTopForArray(); _writeContext = _writeContext.getParent(); popStackAndStoreTheItemAsValue(); } @Override public void writeStartObject() throws IOException, JsonGenerationException { _writeContext = _writeContext.createChildObjectContext(); stack.push(new StackItemForObject()); } @Override public void writeEndObject() throws IOException, JsonGenerationException { if (!_writeContext.inObject()) { _reportError("Current context not an object but " + _writeContext.getTypeDesc()); } 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())); } _writeContext = _writeContext.getParent(); popStackAndStoreTheItemAsValue(); } private void packValue(Object v) throws IOException { MessagePacker messagePacker = getMessagePacker(); if (v == null) { messagePacker.packNil(); } else if (v instanceof Integer) { messagePacker.packInt((Integer) v); } else if (v instanceof ByteBuffer) { ByteBuffer bb = (ByteBuffer) v; messagePacker.packBinaryHeader(bb.limit()); messagePacker.writePayload(bb); } else if (v instanceof String) { messagePacker.packString((String) v); } else if (v instanceof Float) { messagePacker.packFloat((Float) v); } 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); } else if (v instanceof BigInteger) { messagePacker.packBigInteger((BigInteger) v); } else if (v instanceof BigDecimal) { // TODO throw new NotImplementedException(); } else if (v instanceof Boolean) { messagePacker.packBoolean((Boolean) v); } else { throw new IllegalArgumentException(v.toString()); } } private void packObject(StackItemForObject stackItem) throws IOException { List<String> keys = stackItem.getKeys(); List<Object> values = stackItem.getValues(); MessagePacker messagePacker = getMessagePacker(); messagePacker.packMapHeader(keys.size()); for (int i = 0; i < keys.size(); i++) { messagePacker.packString(keys.get(i)); Object v = values.get(i); packValue(v); } } private void packArray(StackItemForArray stackItem) throws IOException { List<Object> values = stackItem.getValues(); MessagePacker messagePacker = getMessagePacker(); messagePacker.packArrayHeader(values.size()); for (int i = 0; i < values.size(); i++) { Object v = values.get(i); packValue(v); } } @Override public void writeFieldName(String name) throws IOException, JsonGenerationException { addKeyToStackTop(name); } @Override public void writeString(String text) throws IOException, JsonGenerationException { addValueToStackTop(text); } @Override public void writeString(char[] text, int offset, int len) throws IOException, JsonGenerationException { addValueToStackTop(new String(text, offset, len)); } @Override public void writeRawUTF8String(byte[] text, int offset, int length) throws IOException, JsonGenerationException { addValueToStackTop(new String(text, offset, length, DEFAULT_CHARSET)); } @Override public void writeUTF8String(byte[] text, int offset, int length) throws IOException, JsonGenerationException { addValueToStackTop(new String(text, offset, length, DEFAULT_CHARSET)); } @Override public void writeRaw(String text) throws IOException, JsonGenerationException { addValueToStackTop(text); } @Override public void writeRaw(String text, int offset, int len) throws IOException, JsonGenerationException { addValueToStackTop(text.substring(0, len)); } @Override public void writeRaw(char[] text, int offset, int len) throws IOException, JsonGenerationException { addValueToStackTop(new String(text, offset, len)); } @Override public void writeRaw(char c) throws IOException, JsonGenerationException { addValueToStackTop(String.valueOf(c)); } @Override public void writeBinary(Base64Variant b64variant, byte[] data, int offset, int len) throws IOException, JsonGenerationException { addValueToStackTop(ByteBuffer.wrap(data, offset, len)); } @Override public void writeNumber(int v) throws IOException, JsonGenerationException { addValueToStackTop(Integer.valueOf(v)); } @Override public void writeNumber(long v) throws IOException, JsonGenerationException { addValueToStackTop(Long.valueOf(v)); } @Override public void writeNumber(BigInteger v) throws IOException, JsonGenerationException { addValueToStackTop(v); } @Override public void writeNumber(double d) throws IOException, JsonGenerationException { addValueToStackTop(Double.valueOf(d)); } @Override public void writeNumber(float f) throws IOException, JsonGenerationException { addValueToStackTop(Float.valueOf(f)); } @Override public void writeNumber(BigDecimal dec) throws IOException, JsonGenerationException { addValueToStackTop(dec); } @Override public void writeNumber(String encodedValue) throws IOException, JsonGenerationException, UnsupportedOperationException { throw new NotImplementedException(); } @Override public void writeBoolean(boolean state) throws IOException, JsonGenerationException { addValueToStackTop(Boolean.valueOf(state)); } @Override public void writeNull() throws IOException, JsonGenerationException { addValueToStackTop(null); } @Override public void close() throws IOException { try { flush(); } catch (Exception e) { e.printStackTrace(); } finally { MessagePacker messagePacker = getMessagePacker(); messagePacker.close(); } } @Override public void flush() throws IOException { if (rootStackItem != null) { if (rootStackItem instanceof StackItemForObject) { packObject((StackItemForObject) rootStackItem); } else if (rootStackItem instanceof StackItemForArray) { packArray((StackItemForArray) rootStackItem); } else { throw new IllegalStateException("Unexpected rootStackItem: " + rootStackItem); } MessagePacker messagePacker = getMessagePacker(); messagePacker.flush(); } } @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(String key) { getStackTop().addKey(key); } private void addValueToStackTop(Object value) { getStackTop().addValue(value); } private void popStackAndStoreTheItemAsValue() { StackItem child = stack.pop(); if (stack.size() > 0) { addValueToStackTop(child); } else { if (rootStackItem != null) { throw new IllegalStateException("rootStackItem is not null"); } else { rootStackItem = child; } } } private MessagePacker getMessagePacker() { MessagePacker messagePacker = messagePackersHolder.get(); if (messagePacker == null) { throw new IllegalStateException("messagePacker is null"); } return messagePacker; } }