diff --git a/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORGenerator.java b/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORGenerator.java index 86865f8..56b6cd0 100644 --- a/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORGenerator.java +++ b/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORGenerator.java @@ -3,6 +3,7 @@ import java.io.*; import java.math.BigDecimal; import java.math.BigInteger; +import java.util.Stack; import com.fasterxml.jackson.core.*; import com.fasterxml.jackson.core.io.*; @@ -489,15 +490,14 @@ public void writeBytes(byte[] data, int offset, int len) throws IOException { /********************************************************** */ + Stack _arraySize = new Stack(); + @Override public final void writeStartArray() throws IOException { - _verifyValueWrite("start an array"); - _writeContext = _writeContext.createChildArrayContext(); - _writeByte(BYTE_ARRAY_INDEFINITE); + writeStartArray(-1); } - // TODO: implement this for CBOR /* * Unlike with JSON, this method can use slightly optimized version * since CBOR has a variant that allows embedding length in array @@ -509,42 +509,66 @@ public final void writeStartArray() throws IOException public void writeStartArray(int size) throws IOException { _verifyValueWrite("start an array"); _writeContext = _writeContext.createChildArrayContext(); - /* - if (size >= 31 || size < 0) { - _writeByte(BYTE_ARRAY_INDEFINITE); + + _arraySize.push(size); + + if (size < 0 || size >= 31) { + + _writeByte(BYTE_ARRAY_INDEFINITE); } else { + + _writeByte((byte)(PREFIX_TYPE_ARRAY + size)); } - */ - _writeByte(BYTE_ARRAY_INDEFINITE); } - + @Override public final void writeEndArray() throws IOException { if (!_writeContext.inArray()) { _reportError("Current context not an ARRAY but "+_writeContext.getTypeDesc()); } - _writeByte(BYTE_BREAK); + + int size = _arraySize.pop(); + if (size < 0 || size >= 31) { + _writeByte(BYTE_BREAK); + } _writeContext = _writeContext.getParent(); } + Stack _objectSize = new Stack(); + @Override public final void writeStartObject() throws IOException + { + writeStartObject(-1); + } + + public void writeStartObject(int size) throws IOException { _verifyValueWrite("start an object"); _writeContext = _writeContext.createChildObjectContext(); - _writeByte(BYTE_OBJECT_INDEFINITE); + + _objectSize.push(size); + + if (size < 0 || size >= 31) { + + _writeByte(BYTE_OBJECT_INDEFINITE); + } else { + + _writeByte((byte)(PREFIX_TYPE_OBJECT + size)); + } } @Override // since 2.8 - public final void writeStartObject(Object forValue) throws IOException - { + public final void writeStartObject(Object forValue) throws IOException { _verifyValueWrite("start an object"); JsonWriteContext ctxt = _writeContext.createChildObjectContext(); _writeContext = ctxt; if (forValue != null) { ctxt.setCurrentValue(forValue); } + + _objectSize.push(-1); _writeByte(BYTE_OBJECT_INDEFINITE); } @@ -555,7 +579,11 @@ public final void writeEndObject() throws IOException _reportError("Current context not an object but "+_writeContext.getTypeDesc()); } _writeContext = _writeContext.getParent(); - _writeByte(BYTE_BREAK); + + int size = _objectSize.pop(); + if (size < 0 || size >= 31) { + _writeByte(BYTE_BREAK); + } } /* diff --git a/src/main/java/com/fasterxml/jackson/dataformat/cbor/sizer/CBORCommands.java b/src/main/java/com/fasterxml/jackson/dataformat/cbor/sizer/CBORCommands.java new file mode 100644 index 0000000..005d56a --- /dev/null +++ b/src/main/java/com/fasterxml/jackson/dataformat/cbor/sizer/CBORCommands.java @@ -0,0 +1,390 @@ +package com.fasterxml.jackson.dataformat.cbor.sizer; + +import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Queue; + +import com.fasterxml.jackson.core.Base64Variant; +import com.fasterxml.jackson.core.JsonGenerationException; +import com.fasterxml.jackson.dataformat.cbor.CBORGenerator; + +/** + * Interface will be implemented for the writing method storage + */ +abstract class Command { + protected CBORGenerator _cborGenerator; + + public Command(CBORGenerator cborGenerator) { + _cborGenerator = cborGenerator; + } + + public abstract void execute() throws JsonGenerationException, IOException; +} + +/** + * This class is implemented by ExecuterOfArraySubCommands and + * ExecuterOfObjectSubCommands. It offers a method to execute all the commands + * stored in the LinkedList of these classes. + */ +abstract class ExecuterOfSubCommands extends Command { + protected Queue _subListToExecute; + + public ExecuterOfSubCommands(CBORGenerator cborGenerator, Queue list) { + super(cborGenerator); + this._subListToExecute = list; + } + + protected void executeQueueContent(Queue commandList) throws IOException { + try { + for (Command element : commandList) { + element.execute(); + } + } finally { + commandList.clear(); + } + } +} + +/** + * Class used to represent a sub array in data When the execute function is + * called, the array size is determined by the number of elements contained in + * the LinkedList. + */ +class ExecuterOfArraySubCommands extends ExecuterOfSubCommands { + public ExecuterOfArraySubCommands(CBORGenerator cborGenerator, Queue list) { + super(cborGenerator, list); + } + + @Override + public void execute() throws JsonGenerationException, IOException { + _cborGenerator.writeStartArray(_subListToExecute.size()); + + executeQueueContent(_subListToExecute); + + _cborGenerator.writeEndArray(); + } +} + +/** + * Class used to represent a sub object in data When the execute function is + * called, the number of pairs is determined by dividing by two the number of + * elements contained in the LinkedList. + */ +class ExecuterOfObjectSubCommands extends ExecuterOfSubCommands { + public ExecuterOfObjectSubCommands(CBORGenerator cborGenerator, Queue list) { + super(cborGenerator, list); + } + + @Override + public void execute() throws JsonGenerationException, IOException { + _cborGenerator.writeStartObject(Math.round(_subListToExecute.size() / 2)); + + executeQueueContent(_subListToExecute); + + _cborGenerator.writeEndObject(); + } +} + +class WriterFieldName extends Command { + private String _name; + + public WriterFieldName(CBORGenerator generator, String name) { + super(generator); + this._name = name; + } + + @Override + public void execute() throws JsonGenerationException, IOException { + _cborGenerator.writeFieldName(_name); + } +} + +class WriterStringStr extends Command { + private String _text; + + public WriterStringStr(CBORGenerator generator, String text) { + super(generator); + this._text = text; + } + + @Override + public void execute() throws JsonGenerationException, IOException { + _cborGenerator.writeString(_text); + } +} + +class WriterStringChar extends Command { + private char[] _text; + private int _offset; + private int _len; + + public WriterStringChar(CBORGenerator generator, char[] text, int offset, int len) { + super(generator); + this._text = text; + this._offset = offset; + this._len = len; + } + + @Override + public void execute() throws JsonGenerationException, IOException { + _cborGenerator.writeString(_text, _offset, _len); + } +} + +class WriterRawUTF8String extends Command { + private byte[] _text; + private int _offset; + private int _len; + + public WriterRawUTF8String(CBORGenerator generator, byte[] text, int offset, int length) { + super(generator); + this._text = text; + this._offset = offset; + this._len = length; + } + + @Override + public void execute() throws JsonGenerationException, IOException { + _cborGenerator.writeRawUTF8String(_text, _offset, _len); + } +} + +class WriterUTF8String extends Command { + private byte[] _text; + private int _offset; + private int _len; + + public WriterUTF8String(CBORGenerator generator, byte[] text, int offset, int length) { + super(generator); + this._text = text; + this._offset = offset; + this._len = length; + } + + @Override + public void execute() throws JsonGenerationException, IOException { + _cborGenerator.writeUTF8String(_text, _offset, _len); + } +} + +class WriterRaw extends Command { + private String _text; + + public WriterRaw(CBORGenerator generator, String text) { + super(generator); + this._text = text; + } + + @Override + public void execute() throws JsonGenerationException, IOException { + _cborGenerator.writeRaw(_text); + } +} + +class WriterRawSo extends Command { + private String _text; + private int _offset; + private int _len; + + public WriterRawSo(CBORGenerator generator, String text, int offset, int length) { + super(generator); + this._text = text; + this._offset = offset; + this._len = length; + } + + @Override + public void execute() throws JsonGenerationException, IOException { + _cborGenerator.writeRaw(_text, _offset, _len); + } +} + +class WriterRawCo extends Command { + private char[] _text; + private int _offset; + private int _len; + + public WriterRawCo(CBORGenerator generator, char[] text, int offset, int length) { + super(generator); + this._text = text; + this._offset = offset; + this._len = length; + } + + @Override + public void execute() throws JsonGenerationException, IOException { + _cborGenerator.writeRaw(_text, _offset, _len); + } +} + +class WriterRawC extends Command { + private char _c; + + public WriterRawC(CBORGenerator generator, char c) { + super(generator); + this._c = c; + } + + @Override + public void execute() throws JsonGenerationException, IOException { + _cborGenerator.writeRaw(_c); + } +} + +class WriterBinary extends Command { + private Base64Variant _bv; + private byte[] _data; + private int _offset; + private int _len; + + public WriterBinary(CBORGenerator generator, Base64Variant bv, byte[] data, int offset, int len) { + super(generator); + this._bv = bv; + this._data = data; + this._offset = offset; + this._len = len; + } + + @Override + public void execute() throws JsonGenerationException, IOException { + _cborGenerator.writeBinary(_bv, _data, _offset, _len); + } +} + +class WriterNumberInt extends Command { + private int _number; + + public WriterNumberInt(CBORGenerator generator, int v) { + super(generator); + this._number = v; + } + + @Override + public void execute() throws JsonGenerationException, IOException { + _cborGenerator.writeNumber(_number); + } +} + +class WriterNumberLong extends Command { + private long _number; + + public WriterNumberLong(CBORGenerator generator, long v) { + super(generator); + this._number = v; + } + + @Override + public void execute() throws JsonGenerationException, IOException { + _cborGenerator.writeNumber(_number); + } +} + +class WriterNumberBigInteger extends Command { + private BigInteger _number; + + public WriterNumberBigInteger(CBORGenerator generator, BigInteger v) { + super(generator); + this._number = v; + } + + @Override + public void execute() throws JsonGenerationException, IOException { + _cborGenerator.writeNumber(_number); + } +} + +class WriterNumberDouble extends Command { + private double _number; + + public WriterNumberDouble(CBORGenerator generator, double v) { + super(generator); + this._number = v; + } + + @Override + public void execute() throws JsonGenerationException, IOException { + _cborGenerator.writeNumber(_number); + } +} + +class WriterNumberFloat extends Command { + private float _number; + + public WriterNumberFloat(CBORGenerator generator, float v) { + super(generator); + this._number = v; + } + + @Override + public void execute() throws JsonGenerationException, IOException { + _cborGenerator.writeNumber(_number); + } +} + +class WriterNumberBigDecimal extends Command { + private BigDecimal _number; + + public WriterNumberBigDecimal(CBORGenerator generator, BigDecimal v) { + super(generator); + this._number = v; + } + + @Override + public void execute() throws JsonGenerationException, IOException { + _cborGenerator.writeNumber(_number); + } +} + +class WriterNumberString extends Command { + private String _number; + + public WriterNumberString(CBORGenerator generator, String encodedValue) { + super(generator); + this._number = encodedValue; + } + + @Override + public void execute() throws JsonGenerationException, IOException { + _cborGenerator.writeNumber(_number); + } +} + +class WriterBoolean extends Command { + private boolean _state; + + public WriterBoolean(CBORGenerator generator, boolean state) { + super(generator); + this._state = state; + } + + @Override + public void execute() throws JsonGenerationException, IOException { + _cborGenerator.writeBoolean(_state); + } +} + +class WriterNull extends Command { + public WriterNull(CBORGenerator generator) { + super(generator); + } + + @Override + public void execute() throws JsonGenerationException, IOException { + _cborGenerator.writeNull(); + } +} + +class WriterTag extends Command { + private int _tag; + + public WriterTag(CBORGenerator generator, int tag) { + super(generator); + this._tag = tag; + } + + @Override + public void execute() throws JsonGenerationException, IOException { + _cborGenerator.writeTag(_tag); + } +} diff --git a/src/main/java/com/fasterxml/jackson/dataformat/cbor/sizer/CBORFactorySizer.java b/src/main/java/com/fasterxml/jackson/dataformat/cbor/sizer/CBORFactorySizer.java new file mode 100644 index 0000000..e8d5dfc --- /dev/null +++ b/src/main/java/com/fasterxml/jackson/dataformat/cbor/sizer/CBORFactorySizer.java @@ -0,0 +1,395 @@ +package com.fasterxml.jackson.dataformat.cbor.sizer; + +import java.io.*; +import java.net.URL; + +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; +import com.fasterxml.jackson.dataformat.cbor.CBORConstants; +import com.fasterxml.jackson.dataformat.cbor.CBORGenerator; +import com.fasterxml.jackson.dataformat.cbor.CBORParser; +import com.fasterxml.jackson.dataformat.cbor.CBORParserBootstrapper; +import com.fasterxml.jackson.dataformat.cbor.PackageVersion; + +/** + * Factory used for constructing {@link CBORParser} and + * {@link CBORGeneratorSizer} instances; both of which handle + * CBOR encoded data. + *

+ * Extends {@link JsonFactory} mostly so that users can actually use it in place + * of regular non-CBOR factory instances. + *

+ * Note on using non-byte-based sources/targets (char based, like + * {@link java.io.Reader} and {@link java.io.Writer}): these can not be used for + * CBOR documents; attempt will throw exception. + * + */ +public class CBORFactorySizer extends JsonFactory { + private static final long serialVersionUID = 1; // 2.6 + + /* + * Constants + */ + + /** + * Name used to identify CBOR format. (and returned by + * {@link #getFormatName()} + */ + public final static String FORMAT_NAME = "CBOR"; + + /** + * Bitfield (set of flags) of all parser features that are enabled by + * default. + */ + final static int DEFAULT_CBOR_PARSER_FEATURE_FLAGS = CBORParser.Feature.collectDefaults(); + + /** + * Bitfield (set of flags) of all generator features that are enabled by + * default. + */ + final static int DEFAULT_CBOR_GENERATOR_FEATURE_FLAGS = CBORGeneratorSizer.Feature.collectDefaults(); + + /* + * Configuration + */ + + protected int _formatParserFeatures; + protected int _formatGeneratorFeatures; + + /* + * Factory construction, configuration + */ + + /** + * 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 CBORFactorySizer() { + this(null); + } + + public CBORFactorySizer(ObjectCodec oc) { + super(oc); + _formatParserFeatures = DEFAULT_CBOR_PARSER_FEATURE_FLAGS; + _formatGeneratorFeatures = DEFAULT_CBOR_GENERATOR_FEATURE_FLAGS; + } + + /** + * Note: REQUIRES at least 2.2.1 -- unfortunate intra-patch dep but seems + * preferable to just leaving bug be as is + * + * @since 2.2.1 + */ + public CBORFactorySizer(CBORFactorySizer src, ObjectCodec oc) { + super(src, oc); + _formatParserFeatures = src._formatParserFeatures; + _formatGeneratorFeatures = src._formatGeneratorFeatures; + } + + @Override + public CBORFactorySizer copy() { + _checkInvalidCopy(CBORFactorySizer.class); + // note: as with base class, must NOT copy mapper reference + return new CBORFactorySizer(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() { + return new CBORFactorySizer(this, _objectCodec); + } + + /* + * /********************************************************** /* Versioned + * /********************************************************** + */ + + @Override + public Version version() { + return PackageVersion.VERSION; + } + + /* + * /********************************************************** /* Format + * detection functionality + * /********************************************************** + */ + + @Override + public String getFormatName() { + return FORMAT_NAME; + } + + // Defaults work fine for this: + // public boolean canUseSchema(FormatSchema schema) { } + + @Override + public MatchStrength hasFormat(InputAccessor acc) throws IOException { + return CBORParserBootstrapper.hasCBORFormat(acc); + } + + /* + * /********************************************************** /* Capability + * introspection /********************************************************** + */ + + @Override + public boolean canHandleBinaryNatively() { + return true; + } + + @Override // since 2.6 + public Class getFormatReadFeatureType() { + return CBORParser.Feature.class; + } + + @Override // since 2.6 + public Class getFormatWriteFeatureType() { + return CBORGenerator.Feature.class; + } + + /* + * /********************************************************** /* + * Configuration, parser settings + * /********************************************************** + */ + + /** + * Method for enabling or disabling specified parser feature (check + * {@link CBORParser.Feature} for list of features) + */ + public final CBORFactorySizer configure(CBORParser.Feature f, boolean state) { + if (state) { + enable(f); + } else { + disable(f); + } + return this; + } + + /** + * Method for enabling specified parser feature (check + * {@link CBORParser.Feature} for list of features) + */ + public CBORFactorySizer enable(CBORParser.Feature f) { + _formatParserFeatures |= f.getMask(); + return this; + } + + /** + * Method for disabling specified parser features (check + * {@link CBORParser.Feature} for list of features) + */ + public CBORFactorySizer disable(CBORParser.Feature f) { + _formatParserFeatures &= ~f.getMask(); + return this; + } + + /** + * Checked whether specified parser feature is enabled. + */ + public final boolean isEnabled(CBORParser.Feature f) { + return (_formatParserFeatures & f.getMask()) != 0; + } + + /* + * /********************************************************** /* + * Configuration, generator settings + * /********************************************************** + */ + + /** + * Method for enabling or disabling specified generator feature (check + * {@link CBORGeneratorSizer.Feature} for list of features) + */ + public CBORFactorySizer configure(CBORGenerator.Feature f, boolean state) { + if (state) { + enable(f); + } else { + disable(f); + } + return this; + } + + /** + * Method for enabling specified generator features (check + * {@link CBORGeneratorSizer.Feature} for list of features) + */ + public CBORFactorySizer enable(CBORGenerator.Feature f) { + _formatGeneratorFeatures |= f.getMask(); + return this; + } + + /** + * Method for disabling specified generator feature (check + * {@link CBORGeneratorSizer.Feature} for list of features) + */ + public CBORFactorySizer disable(CBORGenerator.Feature f) { + _formatGeneratorFeatures &= ~f.getMask(); + return this; + } + + /** + * Check whether specified generator feature is enabled. + */ + public boolean isEnabled(CBORGenerator.Feature f) { + return (_formatGeneratorFeatures & f.getMask()) != 0; + } + + /* + * /********************************************************** /* Overridden + * parser factory methods, new (2.1) + * /********************************************************** + */ + + @SuppressWarnings("resource") + @Override + public CBORParser createParser(File f) throws IOException { + return _createParser(new FileInputStream(f), _createContext(f, true)); + } + + @Override + public CBORParser createParser(URL url) throws IOException { + return _createParser(_optimizedStreamFromURL(url), _createContext(url, true)); + } + + @Override + public CBORParser createParser(InputStream in) throws IOException { + return _createParser(in, _createContext(in, false)); + } + + @Override + public CBORParser createParser(byte[] data) throws IOException { + return _createParser(data, 0, data.length, _createContext(data, true)); + } + + @Override + public CBORParser createParser(byte[] data, int offset, int len) throws IOException { + return _createParser(data, offset, len, _createContext(data, true)); + } + + /* + * /********************************************************** /* Overridden + * generator factory methods + * /********************************************************** + */ + + /** + * Method for constructing {@link JsonGenerator} for generating CBOR-encoded + * output. + *

+ * Since CBOR format always uses UTF-8 internally, enc argument + * is ignored. + */ + @Override + public CBORGeneratorSizer createGenerator(OutputStream out, JsonEncoding enc) throws IOException { + return _createCBORGeneratorSizer(_createContext(out, false), _generatorFeatures, _formatGeneratorFeatures, + _objectCodec, out); + } + + /** + * Method for constructing {@link JsonGenerator} for generating CBOR-encoded + * output. + *

+ * Since CBOR format always uses UTF-8 internally, no encoding need to be + * passed to this method. + */ + @Override + public CBORGeneratorSizer createGenerator(OutputStream out) throws IOException { + return _createCBORGeneratorSizer(_createContext(out, false), _generatorFeatures, _formatGeneratorFeatures, + _objectCodec, out); + } + + /* + * /****************************************************** /* Overridden + * internal factory methods + * /****************************************************** + */ + + @Override + protected IOContext _createContext(Object srcRef, boolean resourceManaged) { + return super._createContext(srcRef, resourceManaged); + } + + /** + * Overridable factory method that actually instantiates desired parser. + */ + @Override + protected CBORParser _createParser(InputStream in, IOContext ctxt) throws IOException { + return new CBORParserBootstrapper(ctxt, in).constructParser(_factoryFeatures, _parserFeatures, + _formatParserFeatures, _objectCodec, _byteSymbolCanonicalizer); + } + + /** + * Overridable factory method that actually instantiates desired parser. + */ + @Override + protected JsonParser _createParser(Reader r, IOContext ctxt) throws IOException { + return _nonByteSource(); + } + + @Override + protected JsonParser _createParser(char[] data, int offset, int len, IOContext ctxt, boolean recyclable) + throws IOException { + return _nonByteSource(); + } + + /** + * Overridable factory method that actually instantiates desired parser. + */ + @Override + protected CBORParser _createParser(byte[] data, int offset, int len, IOContext ctxt) throws IOException { + return new CBORParserBootstrapper(ctxt, data, offset, len).constructParser(_factoryFeatures, _parserFeatures, + _formatParserFeatures, _objectCodec, _byteSymbolCanonicalizer); + } + + @Override + protected CBORGeneratorSizer _createGenerator(Writer out, IOContext ctxt) throws IOException { + return _nonByteTarget(); + } + + @Override + protected CBORGeneratorSizer _createUTF8Generator(OutputStream out, IOContext ctxt) throws IOException { + return _createCBORGeneratorSizer(ctxt, _generatorFeatures, _formatGeneratorFeatures, _objectCodec, out); + } + + @Override + protected Writer _createWriter(OutputStream out, JsonEncoding enc, IOContext ctxt) throws IOException { + return _nonByteTarget(); + } + + private final CBORGeneratorSizer _createCBORGeneratorSizer(IOContext ctxt, int stdFeat, int formatFeat, + ObjectCodec codec, OutputStream out) throws IOException { + // false -> we won't manage the stream unless explicitly directed to + CBORGeneratorSizer gen = new CBORGeneratorSizer(ctxt, stdFeat, formatFeat, _objectCodec, out); + + if (CBORGenerator.Feature.WRITE_TYPE_HEADER.enabledIn(formatFeat)) { + gen.writeTag(CBORConstants.TAG_ID_SELF_DESCRIBE); + } + + return gen; + } + + protected T _nonByteTarget() { + throw new UnsupportedOperationException("Can not create generator for non-byte-based target"); + } + + protected T _nonByteSource() { + throw new UnsupportedOperationException("Can not create generator for non-byte-based source"); + } +} diff --git a/src/main/java/com/fasterxml/jackson/dataformat/cbor/sizer/CBORGeneratorSizer.java b/src/main/java/com/fasterxml/jackson/dataformat/cbor/sizer/CBORGeneratorSizer.java new file mode 100644 index 0000000..1fce89c --- /dev/null +++ b/src/main/java/com/fasterxml/jackson/dataformat/cbor/sizer/CBORGeneratorSizer.java @@ -0,0 +1,324 @@ +package com.fasterxml.jackson.dataformat.cbor.sizer; + +import java.io.IOException; +import java.io.OutputStream; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.LinkedList; +import java.util.Queue; +import java.util.Stack; + +import com.fasterxml.jackson.core.Base64Variant; +import com.fasterxml.jackson.core.ObjectCodec; +import com.fasterxml.jackson.core.base.GeneratorBase; +import com.fasterxml.jackson.core.io.IOContext; +import com.fasterxml.jackson.dataformat.cbor.CBORGenerator; + +/** + * + * Implementation of JsonGenerator which permits to use definite size array amd + * maps when it is possible. To do so, the data is encoded when all the array or + * map content is known. This class should not be used in the cases where the + * memory efficiency is a priority. + */ +public class CBORGeneratorSizer extends GeneratorBase { + /** + * This class decorate CBORGenerator. All the writing methods are wrapped + */ + private CBORGenerator _cborGenerator; + + /** + * For arrays and map, the data is stored in these lists. + */ + private Stack> _commandsQueueStack; + private Queue _commandsQueue; + + /** + * The constructor initializes the wrapped CBORGenerator. + * + * @param ctxt + * @param stdFeatures + * @param formatFeatures + * @param codec + * @param out + */ + public CBORGeneratorSizer(IOContext ctxt, int stdFeatures, int formatFeatures, ObjectCodec codec, + OutputStream out) { + super(stdFeatures, codec); + _cborGenerator = new CBORGenerator(ctxt, stdFeatures, formatFeatures, codec, out); + + _commandsQueueStack = new Stack>(); + } + + private void enqueue(Command cmd) { + _commandsQueue.add(cmd); + } + + /** + * The queuing is activated only for the map and arrays + */ + private boolean isQueuingEnabled() { + return _commandsQueue != null; + } + + private void createObjectContext() { + if (_commandsQueue != null) { + _commandsQueueStack.add(_commandsQueue); + } + _commandsQueue = new LinkedList(); + } + + @Override + public void flush() throws IOException { + _cborGenerator.flush(); + } + + @Override + /** + * The array is opened only at the end when the number of object is known. + * The queuing is activated and the current list of data is pushed in the + * queue list. + */ + public void writeStartArray() throws IOException { + createObjectContext(); + } + + @Override + /** + * The content of the last array is added to the list of element to write. + * If this is the last end, all the stored commands are executed + */ + public void writeEndArray() throws IOException { + Command arraySubCommand = new ExecuterOfArraySubCommands(_cborGenerator, _commandsQueue); + if (!_commandsQueueStack.empty()) { + _commandsQueue = _commandsQueueStack.pop(); + _commandsQueue.add(arraySubCommand); + } else { + arraySubCommand.execute(); + _commandsQueue = null; + } + } + + @Override + /** + * The map is opened only at the end when the number of object is known. The + * queuing is activated and the current list of data is pushed in the queue + * list. + */ + public void writeStartObject() throws IOException { + createObjectContext(); + } + + @Override + /** + * The content of the map array is added to the list of element to write If + * this is the last end, all the stored commands are executed + */ + public void writeEndObject() throws IOException { + Command objectSubCommand = new ExecuterOfObjectSubCommands(_cborGenerator, _commandsQueue); + if (!_commandsQueueStack.empty()) { + _commandsQueue = _commandsQueueStack.pop(); + _commandsQueue.add(objectSubCommand); + } else { + objectSubCommand.execute(); + _commandsQueue = null; + } + } + + /** + * Wrapped methods: if the queuing is activated, the commands are stored and + * will be executed on the last map or array end. + */ + + @Override + public void writeFieldName(String name) throws IOException { + if (isQueuingEnabled()) { + enqueue(new WriterFieldName(this._cborGenerator, name)); + } else { + _cborGenerator.writeFieldName(name); + } + } + + @Override + public void writeString(String text) throws IOException { + if (isQueuingEnabled()) { + enqueue(new WriterStringStr(this._cborGenerator, text)); + } else { + _cborGenerator.writeString(text); + } + } + + @Override + public void writeString(char[] text, int offset, int len) throws IOException { + if (isQueuingEnabled()) { + enqueue(new WriterStringChar(this._cborGenerator, text, offset, len)); + } else { + _cborGenerator.writeString(text, offset, len); + } + } + + @Override + public void writeRawUTF8String(byte[] text, int offset, int length) throws IOException { + if (isQueuingEnabled()) { + enqueue(new WriterRawUTF8String(this._cborGenerator, text, offset, length)); + } else { + _cborGenerator.writeRawUTF8String(text, offset, length); + } + } + + @Override + public void writeUTF8String(byte[] text, int offset, int length) throws IOException { + if (isQueuingEnabled()) { + enqueue(new WriterUTF8String(this._cborGenerator, text, offset, length)); + } else { + _cborGenerator.writeUTF8String(text, offset, length); + } + } + + @Override + public void writeRaw(String text) throws IOException { + if (isQueuingEnabled()) { + enqueue(new WriterRaw(this._cborGenerator, text)); + } else { + _cborGenerator.writeRaw(text); + } + } + + @Override + public void writeRaw(String text, int offset, int len) throws IOException { + if (isQueuingEnabled()) { + enqueue(new WriterRawSo(this._cborGenerator, text, offset, len)); + } else { + _cborGenerator.writeRaw(text, offset, len); + } + } + + @Override + public void writeRaw(char[] text, int offset, int len) throws IOException { + if (isQueuingEnabled()) { + enqueue(new WriterRawCo(this._cborGenerator, text, offset, len)); + } else { + _cborGenerator.writeRaw(text, offset, len); + } + } + + @Override + public void writeRaw(char c) throws IOException { + if (isQueuingEnabled()) { + enqueue(new WriterRawC(this._cborGenerator, c)); + } else { + _cborGenerator.writeRaw(c); + } + } + + @Override + public void writeBinary(Base64Variant bv, byte[] data, int offset, int len) throws IOException { + if (isQueuingEnabled()) { + enqueue(new WriterBinary(this._cborGenerator, bv, data, offset, len)); + } else { + _cborGenerator.writeBinary(bv, data, offset, len); + } + } + + @Override + public void writeNumber(int v) throws IOException { + if (isQueuingEnabled()) { + enqueue(new WriterNumberInt(_cborGenerator, v)); + } else { + _cborGenerator.writeNumber(v); + } + } + + @Override + public void writeNumber(long v) throws IOException { + if (isQueuingEnabled()) { + enqueue(new WriterNumberLong(_cborGenerator, v)); + } else { + _cborGenerator.writeNumber(v); + } + } + + @Override + public void writeNumber(BigInteger v) throws IOException { + if (isQueuingEnabled()) { + enqueue(new WriterNumberBigInteger(_cborGenerator, v)); + } else { + _cborGenerator.writeNumber(v); + } + } + + @Override + public void writeNumber(double v) throws IOException { + if (isQueuingEnabled()) { + enqueue(new WriterNumberDouble(_cborGenerator, v)); + } else { + _cborGenerator.writeNumber(v); + } + } + + @Override + public void writeNumber(float v) throws IOException { + if (isQueuingEnabled()) { + enqueue(new WriterNumberFloat(_cborGenerator, v)); + } else { + _cborGenerator.writeNumber(v); + } + } + + @Override + public void writeNumber(BigDecimal v) throws IOException { + if (isQueuingEnabled()) { + enqueue(new WriterNumberBigDecimal(_cborGenerator, v)); + } else { + _cborGenerator.writeNumber(v); + } + } + + @Override + public void writeNumber(String encodedValue) throws IOException { + if (isQueuingEnabled()) { + enqueue(new WriterNumberString(_cborGenerator, encodedValue)); + } else { + _cborGenerator.writeNumber(encodedValue); + } + } + + @Override + public void writeBoolean(boolean state) throws IOException { + if (isQueuingEnabled()) { + enqueue(new WriterBoolean(_cborGenerator, state)); + } else { + _cborGenerator.writeBoolean(state); + } + } + + public void writeTag(int tagId) throws IOException { + if (isQueuingEnabled()) { + enqueue(new WriterTag(_cborGenerator, tagId)); + } else { + _cborGenerator.writeTag(tagId); + } + } + + @Override + public void writeNull() throws IOException { + if (isQueuingEnabled()) { + enqueue(new WriterNull(_cborGenerator)); + } else { + _cborGenerator.writeNull(); + } + } + + @Override + public void close() throws IOException { + _cborGenerator.close(); + } + + @Override + protected void _releaseBuffers() { + } + + @Override + protected void _verifyValueWrite(String typeMsg) throws IOException { + } +} diff --git a/src/test/java/com/fasterxml/jackson/dataformat/cbor/CBORTestBase.java b/src/test/java/com/fasterxml/jackson/dataformat/cbor/CBORTestBase.java index 8273cf0..0d9a0f6 100644 --- a/src/test/java/com/fasterxml/jackson/dataformat/cbor/CBORTestBase.java +++ b/src/test/java/com/fasterxml/jackson/dataformat/cbor/CBORTestBase.java @@ -11,7 +11,7 @@ import com.fasterxml.jackson.core.*; import com.fasterxml.jackson.databind.ObjectMapper; -abstract class CBORTestBase +public abstract class CBORTestBase extends junit.framework.TestCase { @@ -37,20 +37,20 @@ protected CBORParser cborParser(InputStream in) throws IOException { protected CBORParser cborParser(CBORFactory f, byte[] input) throws IOException { return f.createParser(input); } - + protected CBORParser cborParser(CBORFactory f, InputStream in) throws IOException { return f.createParser(in); } protected ObjectMapper cborMapper() { return new ObjectMapper(cborFactory()); - } - + } + protected CBORFactory cborFactory() { CBORFactory f = new CBORFactory(); return f; } - + protected byte[] cborDoc(String json) throws IOException { return cborDoc(cborFactory(), json); } @@ -82,7 +82,7 @@ protected CBORGenerator cborGenerator(CBORFactory f, { return f.createGenerator(result, null); } - + /* /********************************************************** /* Additional assertion methods @@ -228,6 +228,26 @@ protected static String generateAsciiString(int length, Random rnd) return sw.toString(); } + protected static String byteToHexString(byte[] cborValue) + { + StringBuilder hexValue = new StringBuilder(); + + String prefix = ""; + + for(byte b : cborValue) { + + hexValue.append(prefix + byteToHexString(b)); + prefix = " "; + } + + return hexValue.toString(); + } + + protected static String byteToHexString(byte byteVal) + { + return String.format("%02x", byteVal); + } + /* /********************************************************** /* Other helper methods @@ -241,4 +261,5 @@ protected static String aposToQuotes(String str) { protected static String quote(String str) { return '"'+str+'"'; } + } diff --git a/src/test/java/com/fasterxml/jackson/dataformat/cbor/GeneratorSimpleTest.java b/src/test/java/com/fasterxml/jackson/dataformat/cbor/GeneratorSimpleTest.java index c8105b8..3ea4b11 100644 --- a/src/test/java/com/fasterxml/jackson/dataformat/cbor/GeneratorSimpleTest.java +++ b/src/test/java/com/fasterxml/jackson/dataformat/cbor/GeneratorSimpleTest.java @@ -171,7 +171,7 @@ public void testFloatValues() throws Exception gen.close(); int raw = Float.floatToIntBits(f); _verifyBytes(out.toByteArray(), - (byte) (CBORConstants.BYTE_FLOAT32), + (CBORConstants.BYTE_FLOAT32), (byte) (raw >> 24), (byte) (raw >> 16), (byte) (raw >> 8), @@ -185,7 +185,7 @@ public void testFloatValues() throws Exception gen.close(); long rawL = Double.doubleToLongBits(d); _verifyBytes(out.toByteArray(), - (byte) (CBORConstants.BYTE_FLOAT64), + (CBORConstants.BYTE_FLOAT64), (byte) (rawL >> 56), (byte) (rawL >> 48), (byte) (rawL >> 40), @@ -210,23 +210,24 @@ public void testEmptyArray() throws Exception public void testEmptyObject() throws Exception { - // First: empty array (2 bytes) + // First: empty map (2 bytes) ByteArrayOutputStream out = new ByteArrayOutputStream(); CBORGenerator gen = cborGenerator(out); gen.writeStartObject(); gen.writeEndObject(); gen.close(); _verifyBytes(out.toByteArray(), CBORConstants.BYTE_OBJECT_INDEFINITE, - CBORConstants.BYTE_BREAK); - } + CBORConstants.BYTE_BREAK); + //_verifyBytes(out.toByteArray(), (byte) (CBORConstants.PREFIX_TYPE_BYTES+0)); + } - public void testIntArray() throws Exception + public void testIntArraySmall() throws Exception { ByteArrayOutputStream out = new ByteArrayOutputStream(); CBORGenerator gen = cborGenerator(out); - // currently will produce indefinite-length array - gen.writeStartArray(); + // currently will produce a 3-length array + gen.writeStartArray(3); gen.writeNumber(1); gen.writeNumber(2); gen.writeNumber(3); @@ -234,11 +235,10 @@ public void testIntArray() throws Exception gen.close(); final byte[] EXP = new byte[] { - CBORConstants.BYTE_ARRAY_INDEFINITE, + (byte) (CBORConstants.PREFIX_TYPE_ARRAY+3), (byte) (CBORConstants.PREFIX_TYPE_INT_POS + 1), (byte) (CBORConstants.PREFIX_TYPE_INT_POS + 2), (byte) (CBORConstants.PREFIX_TYPE_INT_POS + 3), - CBORConstants.BYTE_BREAK }; _verifyBytes(out.toByteArray(), EXP); @@ -247,6 +247,40 @@ public void testIntArray() throws Exception byte[] b = MAPPER.writeValueAsBytes(new int[] { 1, 2, 3 }); _verifyBytes(b, EXP); } + + public void testIntArrayLarge() throws Exception + { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGenerator gen = cborGenerator(out); + + // currently will produce indefinite-length array + gen.writeStartArray(50); + for(int i = 0; i < 50; i++) { + gen.writeNumber(i%16); + } + gen.writeEndArray(); + gen.close(); + + final byte[] EXP = new byte[52]; + + EXP[0] = CBORConstants.BYTE_ARRAY_INDEFINITE; + EXP[51] = CBORConstants.BYTE_BREAK; + + int[] vals = new int[50]; + for(int i = 0; i < 50; i++) { + + vals[i] = i%16; + EXP[i+1] = (byte) (CBORConstants.PREFIX_TYPE_INT_POS + (i%16)); + } + + _verifyBytes(out.toByteArray(), EXP); + + + // Also, data-binding should produce identical + byte[] b = MAPPER.writeValueAsBytes(vals); + _verifyBytes(b, EXP); + } + public void testTrivialObject() throws Exception { diff --git a/src/test/java/com/fasterxml/jackson/dataformat/cbor/TestBiggerData.java b/src/test/java/com/fasterxml/jackson/dataformat/cbor/TestBiggerData.java index f20c22a..fb0ba9f 100644 --- a/src/test/java/com/fasterxml/jackson/dataformat/cbor/TestBiggerData.java +++ b/src/test/java/com/fasterxml/jackson/dataformat/cbor/TestBiggerData.java @@ -11,7 +11,7 @@ */ public class TestBiggerData extends CBORTestBase { - static class Citm + public static class Citm { public Map areaNames; public Map audienceSubCategoryNames; @@ -27,7 +27,7 @@ static class Citm public List performances; } - static class Event + public static class Event { public int id; public String name; @@ -39,7 +39,7 @@ static class Event public LinkedHashSet subTopicIds; } - static class Performance + public static class Performance { public int id; public int eventId; @@ -55,18 +55,18 @@ static class Performance public String venueCode; } - static class Price { + public static class Price { public int amount; public int audienceSubCategoryId; public int seatCategoryId; } - static class SeatCategory { + public static class SeatCategory { public int seatCategoryId; public List areas; } - static class Area { + public static class Area { public int areaId; public int[] blockIds; } diff --git a/src/test/java/com/fasterxml/jackson/dataformat/cbor/sizer/CBORTestBaseSizer.java b/src/test/java/com/fasterxml/jackson/dataformat/cbor/sizer/CBORTestBaseSizer.java new file mode 100644 index 0000000..01681d9 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/dataformat/cbor/sizer/CBORTestBaseSizer.java @@ -0,0 +1,75 @@ +package com.fasterxml.jackson.dataformat.cbor.sizer; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import com.fasterxml.jackson.core.*; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.cbor.CBORGenerator; +import com.fasterxml.jackson.dataformat.cbor.CBORParser; +import com.fasterxml.jackson.dataformat.cbor.CBORTestBase; +import com.fasterxml.jackson.dataformat.cbor.sizer.CBORFactorySizer; + +public abstract class CBORTestBaseSizer extends CBORTestBase { + /* + * Factory methods + */ + + @Override + protected CBORParser cborParser(byte[] input) throws IOException { + return cborParser(cborFactorySizer(), input); + } + + @Override + protected CBORParser cborParser(InputStream in) throws IOException { + CBORFactorySizer f = cborFactorySizer(); + return cborParser(f, in); + } + + protected CBORParser cborParser(CBORFactorySizer f, byte[] input) throws IOException { + return f.createParser(input); + } + + protected CBORParser cborParser(CBORFactorySizer f, InputStream in) throws IOException { + return f.createParser(in); + } + + @Override + protected ObjectMapper cborMapper() { + return new ObjectMapper(cborFactorySizer()); + } + + protected CBORFactorySizer cborFactorySizer() { + CBORFactorySizer f = new CBORFactorySizer(); + f.disable(CBORGenerator.Feature.WRITE_TYPE_HEADER); + return f; + } + + @Override + protected byte[] cborDoc(String json) throws IOException { + return cborDoc(cborFactorySizer(), json); + } + + protected byte[] cborDoc(CBORFactorySizer cborF, String json) throws IOException { + JsonFactory jf = new JsonFactory(); + JsonParser jp = jf.createParser(json); + ByteArrayOutputStream out = new ByteArrayOutputStream(json.length()); + JsonGenerator dest = cborF.createGenerator(out); + + while (jp.nextToken() != null) { + dest.copyCurrentEvent(jp); + } + jp.close(); + dest.close(); + return out.toByteArray(); + } + + protected CBORGeneratorSizer cborGeneratorSizer(ByteArrayOutputStream result) throws IOException { + return cborGeneratorSizer(cborFactorySizer(), result); + } + + protected CBORGeneratorSizer cborGeneratorSizer(CBORFactorySizer f, ByteArrayOutputStream result) + throws IOException { + return f.createGenerator(result, null); + } +} diff --git a/src/test/java/com/fasterxml/jackson/dataformat/cbor/sizer/GeneratorSizerTest.java b/src/test/java/com/fasterxml/jackson/dataformat/cbor/sizer/GeneratorSizerTest.java new file mode 100644 index 0000000..789ff23 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/dataformat/cbor/sizer/GeneratorSizerTest.java @@ -0,0 +1,670 @@ +package com.fasterxml.jackson.dataformat.cbor.sizer; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; + +import com.fasterxml.jackson.dataformat.cbor.CBORConstants; + +public class GeneratorSizerTest extends CBORTestBaseSizer { + /** + * Tests + */ + public void testArray_less31elt() throws Exception { + // Test arrays with less of 31 element + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + + gen.writeStartArray(); + gen.writeNumber(1); + gen.writeNumber(2); + gen.writeNumber(3); + gen.writeEndArray(); + gen.close(); + + _verifyBytes(out.toByteArray(), (byte) (CBORConstants.PREFIX_TYPE_ARRAY + 3), (byte) 0x01, (byte) 0x02, + (byte) 0x03); + } + + public void testArray_more31elt() throws Exception { + // Test arrays with more of 31 elements + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + + gen.writeStartArray(); + + for (int i = 1; i <= 32; i++) { + gen.writeNumber(i % 16); + } + + gen.writeEndArray(); + gen.close(); + + _verifyBytes(out.toByteArray(), (byte) (CBORConstants.BYTE_ARRAY_INDEFINITE), (byte) 0x01, (byte) 0x02, + (byte) 0x03, (byte) 0x04, (byte) 0x05, (byte) 0x06, (byte) 0x07, (byte) 0x08, (byte) 0x09, (byte) 0x0A, + (byte) 0x0B, (byte) 0x0C, (byte) 0x0D, (byte) 0x0E, (byte) 0x0F, (byte) 0x00, (byte) 0x01, (byte) 0x02, + (byte) 0x03, (byte) 0x04, (byte) 0x05, (byte) 0x06, (byte) 0x07, (byte) 0x08, (byte) 0x09, (byte) 0x0A, + (byte) 0x0B, (byte) 0x0C, (byte) 0x0D, (byte) 0x0E, (byte) 0x0F, (byte) 0x00, CBORConstants.BYTE_BREAK); + } + + public void testMap_less31elt() throws Exception { + // Test map with less of 31 elements + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + + gen.writeStartObject(); + gen.writeFieldName("Fun"); + gen.writeBoolean(true); + gen.writeFieldName("Amt"); + gen.writeNumber(-2); + gen.writeEndObject(); + gen.close(); + + _verifyBytes(out.toByteArray(), (byte) (CBORConstants.PREFIX_TYPE_OBJECT + 2), (byte) 0x63, (byte) 0x46, + (byte) 0x75, (byte) 0x6e, (byte) 0xf5, (byte) 0x63, (byte) 0x41, (byte) 0x6d, (byte) 0x74, (byte) 0x21); + } + + public void testMap_more31elt() throws Exception { + // Test map with more of 31 elements + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + + gen.writeStartObject(); + for (int i = 1; i <= 32; i++) { + gen.writeFieldName(Integer.toString(i % 10)); + gen.writeNumber(i % 10); + } + gen.writeEndObject(); + gen.close(); + + assertEquals((out.toByteArray())[0], CBORConstants.BYTE_OBJECT_INDEFINITE); + assertEquals((out.toByteArray())[out.toByteArray().length - 1], CBORConstants.BYTE_BREAK); + + for (int i = 1; i < (out.toByteArray().length - 2) / 3; i = i + 3) { + assertEquals((char) 0x61, (out.toByteArray())[i]); + assertEquals((out.toByteArray())[i + 1], (Integer.toString(((i / 3) + 1) % 10)).getBytes()[0]); + assertEquals((out.toByteArray())[i + 2], (char) ((i / 3) + 1) % 10); + } + } + + public void testNestedArrays() throws Exception { + // Test nested arrays. + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + + gen.writeStartArray(); + gen.writeNumber(1); + gen.writeNumber(2); + gen.writeStartArray(); + gen.writeNumber(3); + gen.writeNumber(4); + gen.writeEndArray(); + gen.writeNumber(5); + gen.writeEndArray(); + gen.close(); + + _verifyBytes(out.toByteArray(), (byte) (CBORConstants.PREFIX_TYPE_ARRAY + 4), (byte) 0x01, (byte) 0x02, + (byte) (CBORConstants.PREFIX_TYPE_ARRAY + 2), (byte) 0x03, (byte) 0x04, (byte) 0x05); + } + + public void testNestedMap() throws Exception { + // Test nested maps + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + + gen.writeStartObject(); + gen.writeFieldName("F1"); + gen.writeNumber(1); + gen.writeFieldName("F2"); + gen.writeNumber(2); + gen.writeFieldName("Sub"); + gen.writeStartObject(); + gen.writeFieldName("F3"); + gen.writeNumber(3); + gen.writeFieldName("F4"); + gen.writeNumber(4); + gen.writeEndObject(); + gen.writeFieldName("F5"); + gen.writeNumber(5); + gen.writeEndObject(); + gen.close(); + + _verifyBytes(out.toByteArray(), (byte) (CBORConstants.PREFIX_TYPE_OBJECT + 4), + + (byte) 0x62, (byte) 0x46, (byte) 0x31, (byte) 0x01, (byte) 0x62, (byte) 0x46, (byte) 0x32, (byte) 0x02, + (byte) 0x63, (byte) 0x53, (byte) 0x75, (byte) 0x62, (byte) 0xA2, (byte) 0x62, (byte) 0x46, (byte) 0x33, + (byte) 0x03, (byte) 0x62, (byte) 0x46, (byte) 0x34, (byte) 0x04, (byte) 0x62, (byte) 0x46, (byte) 0x35, + (byte) 0x05); + } + + public void testNestedMapAndArray() throws Exception { + // Test map nested in array and reciprocally + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + + gen.writeStartArray(); + gen.writeNumber(1); + gen.writeString("Str"); + gen.writeStartObject(); + gen.writeFieldName("Int"); + gen.writeNumber(2); + gen.writeFieldName("Bool"); + gen.writeBoolean(true); + gen.writeFieldName("Array"); + gen.writeStartArray(); + gen.writeNumber(3); + gen.writeNumber(4); + gen.writeEndArray(); + gen.writeEndObject(); + gen.writeString("Str2"); + gen.writeEndArray(); + gen.close(); + + _verifyBytes(out.toByteArray(), (byte) (CBORConstants.PREFIX_TYPE_ARRAY + 4), + + (byte) 0x01, (byte) 0x63, (byte) 0x53, (byte) 0x74, (byte) 0x72, (byte) 0xa3, (byte) 0x63, (byte) 0x49, + (byte) 0x6e, (byte) 0x74, (byte) 0x02, (byte) 0x64, (byte) 0x42, (byte) 0x6f, (byte) 0x6f, (byte) 0x6c, + (byte) 0xf5, (byte) 0x65, (byte) 0x41, (byte) 0x72, (byte) 0x72, (byte) 0x61, (byte) 0x79, (byte) 0x82, + (byte) 0x03, (byte) 0x04, (byte) 0x64, (byte) 0x53, (byte) 0x74, (byte) 0x72, (byte) 0x32); + } + + public void testStringType_Str_queuingEnabled() throws Exception { + // Test writeString(String text) - queuing enabled + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + + String valueStr = "Test"; + gen.writeStartArray(); + gen.writeString(valueStr); + gen.writeEndArray(); + gen.close(); + + _verifyBytes(out.toByteArray(), (byte) (CBORConstants.PREFIX_TYPE_ARRAY + 1), (byte) 0x64, (byte) 0x54, + (byte) 0x65, (byte) 0x73, (byte) 0x74); + } + + public void testStringType_Str_queuingDisabled() throws Exception { + // Test writeString(String text) - queuing disabled + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + + String valueStr = "Test"; + gen.writeString(valueStr); + gen.close(); + + _verifyBytes(out.toByteArray(), (byte) 0x64, (byte) 0x54, (byte) 0x65, (byte) 0x73, (byte) 0x74); + } + + public void testStringType_CharO_queuingEnabled() throws Exception { + // Test writeString(char[] text, int offset, int len) - queuing enabled + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + + char[] valueChar = { 'T', 'e', 's', 't' }; + gen.writeStartArray(); + gen.writeString(valueChar, 0, 4); + gen.writeEndArray(); + gen.close(); + + _verifyBytes(out.toByteArray(), (byte) (CBORConstants.PREFIX_TYPE_ARRAY + 1), (byte) 0x64, (byte) 0x54, + (byte) 0x65, (byte) 0x73, (byte) 0x74); + } + + public void testStringType_CharO_queuingDisabled() throws Exception { + // Test writeString(char[] text, int offset, int len) - queuing disabled + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + + char[] valueChar = { 'T', 'e', 's', 't' }; + gen.writeString(valueChar, 0, 4); + gen.close(); + + _verifyBytes(out.toByteArray(), (byte) 0x64, (byte) 0x54, (byte) 0x65, (byte) 0x73, (byte) 0x74); + } + + public void testStringType_ByteO_queuingEnabled() throws Exception { + // Test writeRawUTF8String(byte[] text, int offset, int length) - + // queuing enabled + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + + byte[] valueCharUtf8 = "Test".getBytes(); + gen.writeStartArray(); + gen.writeRawUTF8String(valueCharUtf8, 0, 4); + gen.writeEndArray(); + gen.close(); + + _verifyBytes(out.toByteArray(), (byte) (CBORConstants.PREFIX_TYPE_ARRAY + 1), (byte) 0x64, (byte) 0x54, + (byte) 0x65, (byte) 0x73, (byte) 0x74); + } + + public void testStringType_ByteO_queuingDisabled() throws Exception { + // Test writeRawUTF8String(byte[] text, int offset, int length) - + // queuing disabled + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + + byte[] valueCharUtf8 = "Test".getBytes(); + gen.writeRawUTF8String(valueCharUtf8, 0, 4); + gen.close(); + + _verifyBytes(out.toByteArray(), (byte) 0x64, (byte) 0x54, (byte) 0x65, (byte) 0x73, (byte) 0x74); + } + + public void testStringType_ByteArray_queuingEnabled() throws Exception { + // Test writeUTF8String(byte[] text, int offset, int length) - queuing + // enabled + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + + byte[] valueCharUtf8 = "Test".getBytes(); + gen.writeStartArray(); + gen.writeUTF8String(valueCharUtf8, 0, 4); + gen.writeEndArray(); + gen.close(); + + _verifyBytes(out.toByteArray(), (byte) (CBORConstants.PREFIX_TYPE_ARRAY + 1), (byte) 0x64, (byte) 0x54, + (byte) 0x65, (byte) 0x73, (byte) 0x74); + } + + public void testStringType_ByteArray_queuingDisabled() throws Exception { + // Test writeUTF8String(byte[] text, int offset, int length) - queuing + // disabled + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + + byte[] valueCharUtf8 = "Test".getBytes(); + gen.writeUTF8String(valueCharUtf8, 0, 4); + gen.close(); + + _verifyBytes(out.toByteArray(), (byte) 0x64, (byte) 0x54, (byte) 0x65, (byte) 0x73, (byte) 0x74); + } + + public void testNumberType_int_queuingEnabled() throws IOException { + // Test writeNumber(int v) - queuing enabled + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + + gen.writeStartArray(); + gen.writeNumber(Integer.MAX_VALUE); + gen.writeEndArray(); + gen.close(); + + _verifyBytes(out.toByteArray(), (byte) (CBORConstants.PREFIX_TYPE_ARRAY + 1), (byte) 0x1A, (byte) 0x7F, + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF); + } + + public void testNumberType_int_queuingDisabled() throws IOException { + // Test writeNumber(int v) - queuing disabled + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + + gen.writeNumber(Integer.MAX_VALUE); + gen.close(); + + _verifyBytes(out.toByteArray(), (byte) 0x1A, (byte) 0x7F, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF); + } + + public void testNumberType_long_queuingEnabled() throws IOException { + // Test writeNumber(long v) - queuing enabled + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + + gen.writeStartArray(); + gen.writeNumber(Long.MAX_VALUE); + gen.writeEndArray(); + gen.close(); + + _verifyBytes(out.toByteArray(), (byte) (CBORConstants.PREFIX_TYPE_ARRAY + 1), (byte) 0x1B, (byte) 0x7F, + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF); + } + + public void testNumberType_long_queuingDisabled() throws IOException { + // Test writeNumber(long v) - queuing disabled + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + + gen.writeNumber(Long.MAX_VALUE); + gen.close(); + + _verifyBytes(out.toByteArray(), (byte) 0x1B, (byte) 0x7F, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF); + } + + public void testNumberType_BigInteger_queuingEnabled() throws IOException { + // Test writeNumber(BigInteger v) - queuing enabled + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + + gen.writeStartArray(); + gen.writeNumber(BigInteger.ONE); + gen.writeEndArray(); + gen.close(); + + _verifyBytes(out.toByteArray(), (byte) (CBORConstants.PREFIX_TYPE_ARRAY + 1), (byte) 0xC2, (byte) 0x41, + (byte) 0x01); + } + + public void testNumberType_BigInteger_queuingDisabled() throws IOException { + // Test writeNumber(BigInteger v) - queuing enabled + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + + gen.writeNumber(BigInteger.ONE); + gen.close(); + + _verifyBytes(out.toByteArray(), (byte) 0xC2, (byte) 0x41, (byte) 0x01); + } + + public void testNumberType_double_queuingEnabled() throws IOException { + // Test writeNumber(double v) - queuing enabled + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + + gen.writeStartArray(); + gen.writeNumber(Double.MAX_VALUE); + gen.writeEndArray(); + gen.close(); + + _verifyBytes(out.toByteArray(), (byte) (CBORConstants.PREFIX_TYPE_ARRAY + 1), (byte) 0xFB, (byte) 0x7F, + (byte) 0xEF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF); + } + + public void testNumberType_double_queuingDisabled() throws IOException { + // Test writeNumber(double v) - queuing disabled + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + + gen.writeNumber(Double.MAX_VALUE); + gen.close(); + + _verifyBytes(out.toByteArray(), (byte) 0xFB, (byte) 0x7F, (byte) 0xEF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF); + } + + public void testNumberType_float_queuingEnabled() throws IOException { + // Test writeNumber(float v) - queuing enabled + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + + gen.writeStartArray(); + gen.writeNumber(Float.MAX_VALUE); + gen.writeEndArray(); + gen.close(); + + _verifyBytes(out.toByteArray(), (byte) (CBORConstants.PREFIX_TYPE_ARRAY + 1), (byte) 0xFA, (byte) 0x7F, + (byte) 0x7F, (byte) 0xFF, (byte) 0xFF); + } + + public void testNumberType_float_queuingDisabled() throws IOException { + // Test writeNumber(float v) - queuing disabled + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + + gen.writeNumber(Float.MAX_VALUE); + gen.close(); + + _verifyBytes(out.toByteArray(), (byte) 0xFA, (byte) 0x7F, (byte) 0x7F, (byte) 0xFF, (byte) 0xFF); + } + + public void testNumberType_BigDecimal_queuingEnabled() throws IOException { + // Test writeNumber(BigDecimal v) - queuing enabled + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + + gen.writeStartArray(); + gen.writeNumber(BigDecimal.TEN); + gen.writeEndArray(); + gen.close(); + + _verifyBytes(out.toByteArray(), (byte) (CBORConstants.PREFIX_TYPE_ARRAY + 1), (byte) 0xC5, (byte) 0x82, + (byte) 0x00, (byte) 0x0A); + } + + public void testNumberType_BigDecimal_queuingDisabled() throws IOException { + // Test writeNumber(BigDecimal v) - queuing disabled + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + + gen.writeNumber(BigDecimal.TEN); + gen.close(); + + _verifyBytes(out.toByteArray(), (byte) 0xC5, (byte) 0x82, (byte) 0x00, (byte) 0x0A); + } + + public void testNumberType_String_queuingEnabled() throws IOException { + // Test writeNumber(String v) - queuing enabled + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + + gen.writeStartArray(); + gen.writeNumber("42"); + gen.writeEndArray(); + gen.close(); + + _verifyBytes(out.toByteArray(), (byte) (CBORConstants.PREFIX_TYPE_ARRAY + 1), (byte) 0x62, (byte) 0x34, + (byte) 0x32); + } + + public void testNumberType_String_queuingDisabled() throws IOException { + // Test writeNumber(String v) - queuing disabled + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + + gen.writeNumber("42"); + gen.close(); + + _verifyBytes(out.toByteArray(), (byte) 0x62, (byte) 0x34, (byte) 0x32); + } + + public void testBooleanType_queuingEnabled() throws IOException { + // Test writeBoolean(boolean state) - queuing enabled + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + + gen.writeStartArray(); + gen.writeBoolean(true); + gen.writeEndArray(); + gen.close(); + + _verifyBytes(out.toByteArray(), (byte) (CBORConstants.PREFIX_TYPE_ARRAY + 1), (byte) 0xF5); + } + + public void testBooleanType_queuingDisabled() throws IOException { + // Test writeBoolean(boolean state) - queuing disabled + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + + gen.writeBoolean(true); + gen.close(); + + _verifyBytes(out.toByteArray(), (byte) 0xF5); // # true + } + + public void testNull_queuingEnabled() throws IOException { + // Test writeNull() - queuing enabled + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + + gen.writeStartArray(); + gen.writeNull(); + gen.writeEndArray(); + gen.close(); + + _verifyBytes(out.toByteArray(), (byte) (CBORConstants.PREFIX_TYPE_ARRAY + 1), (byte) 0xF6); + } + + public void testNull_queuingDisabled() throws IOException { + // Test writeNull() - queuing disabled + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + + gen.writeNull(); + gen.close(); + + _verifyBytes(out.toByteArray(), (byte) 0xF6); + } + + public void testTag_queuingEnabled() throws IOException { + // Test writeTag(int tagId) - queuing enabled + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + + gen.writeStartArray(); + gen.writeTag(1); + gen.writeEndArray(); + gen.close(); + + _verifyBytes(out.toByteArray(), (byte) (CBORConstants.PREFIX_TYPE_ARRAY + 1), (byte) 0xC1); + } + + public void testTag_queuingDisabled() throws IOException { + // Test writeTag(int tagId) - queuing disabled + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + + gen.writeTag(1); + gen.close(); + + _verifyBytes(out.toByteArray(), (byte) 0xC1); + } + + public void testWrappedMethod() throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + + gen.flush(); + } + + public void testUnsupportedMethods_RawChar_queuingEnabled() throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + + try { + gen.writeStartArray(); + gen.writeRaw('A'); + gen.writeEndArray(); + fail("Should thrown not supported exception"); + } catch (UnsupportedOperationException aExp) { + } + } + + public void testUnsupportedMethods_RawChar_queuingDisabled() throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + + try { + gen.writeRaw('A'); + fail("Should thrown not supported exception"); + } catch (UnsupportedOperationException aExp) { + } + } + + public void testUnsupportedMethods_RawStr_queuingEnabled() throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + + try { + gen.writeStartArray(); + gen.writeRaw((String) "test"); + gen.writeEndArray(); + fail("Should thrown not supported exception"); + } catch (UnsupportedOperationException aExp) { + // Check the method is not supported + } + } + + public void testUnsupportedMethods_RawStr_queuingDisabled() throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + + try { + gen.writeRaw((String) "test"); + fail("Should thrown not supported exception"); + } catch (UnsupportedOperationException aExp) { + // Check the method is not supported + } + } + + public void testUnsupportedMethods_RawStrO_queuingEnabled() throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + + try { + gen.writeStartArray(); + gen.writeRaw((String) "test", 0, 4); + gen.writeEndArray(); + fail("Should thrown not supported exception"); + } catch (UnsupportedOperationException aExp) { + // Check the method is not supported + } + } + + public void testUnsupportedMethods_RawStrO_queuingDisabled() throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + + try { + gen.writeRaw((String) "test", 0, 4); + fail("Should thrown not supported exception"); + } catch (UnsupportedOperationException aExp) { + // Check the method is not supported + } + } + + public void testUnsupportedMethods_RawValueStrO_queuingEnabled() throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + + try { + gen.writeStartArray(); + gen.writeRawValue((String) "test", 0, 4); + gen.writeEndArray(); + fail("Should thrown not supported exception"); + } catch (UnsupportedOperationException aExp) { + // Check the method is not supported + } + } + + public void testUnsupportedMethods_RawValueStrO_queuingDisabled() throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + + try { + gen.writeRawValue((String) "test", 0, 4); + fail("Should thrown not supported exception"); + } catch (UnsupportedOperationException aExp) { + // Check the method is not supported + } + } + + public void testUnsupportedMethods_RawCharArrayO_queuingEnabled() throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + + try { + gen.writeStartArray(); + char[] valueChar = { 'T', 'e', 's', 't' }; + gen.writeRaw(valueChar, 0, 4); + gen.writeEndArray(); + fail("Should thrown not supported exception"); + } catch (UnsupportedOperationException aExp) { + // Check the method is not supported + } + } + + public void testUnsupportedMethods_RawCharArrayO_queuingDisabled() throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + + try { + char[] valueChar = { 'T', 'e', 's', 't' }; + gen.writeRaw(valueChar, 0, 4); + fail("Should thrown not supported exception"); + } catch (UnsupportedOperationException aExp) { + // Check the method is not supported + } + } + +} diff --git a/src/test/java/com/fasterxml/jackson/dataformat/cbor/sizer/ParserSimpleTestSizer.java b/src/test/java/com/fasterxml/jackson/dataformat/cbor/sizer/ParserSimpleTestSizer.java new file mode 100644 index 0000000..b0ddefc --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/dataformat/cbor/sizer/ParserSimpleTestSizer.java @@ -0,0 +1,384 @@ +package com.fasterxml.jackson.dataformat.cbor.sizer; + +import java.io.*; +import java.util.LinkedHashMap; +import java.util.Map; + +import com.fasterxml.jackson.core.*; +import com.fasterxml.jackson.core.JsonParser.NumberType; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.cbor.CBORConstants; +import com.fasterxml.jackson.dataformat.cbor.CBORParser; + +/** + * Unit tests for simple value types. + */ +public class ParserSimpleTestSizer extends CBORTestBaseSizer { + private final ObjectMapper MAPPER = cborMapper(); + + /** + * Test for verifying handling of 'true', 'false' and 'null' literals + */ + public void testSimpleLiterals() throws Exception { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + JsonGenerator gen = cborGeneratorSizer(out); + gen.writeBoolean(true); + gen.close(); + JsonParser p = cborParser(out); + assertEquals(JsonToken.VALUE_TRUE, p.nextToken()); + assertNull(p.nextToken()); + p.close(); + + out = new ByteArrayOutputStream(); + gen = cborGeneratorSizer(out); + gen.writeBoolean(false); + gen.close(); + p = cborParser(out); + assertEquals(JsonToken.VALUE_FALSE, p.nextToken()); + assertNull(p.nextToken()); + p.close(); + + out = new ByteArrayOutputStream(); + gen = cborGeneratorSizer(out); + gen.writeNull(); + gen.close(); + p = cborParser(out); + assertEquals(JsonToken.VALUE_NULL, p.nextToken()); + assertNull(p.nextToken()); + p.close(); + } + + public void testIntValues() throws Exception { + // first, single-byte + CBORFactorySizer f = cborFactorySizer(); + // single byte + _verifyInt(f, 13); + _verifyInt(f, -19); + // two bytes + _verifyInt(f, 255); + _verifyInt(f, -127); + // three + _verifyInt(f, 256); + _verifyInt(f, 0xFFFF); + _verifyInt(f, -300); + _verifyInt(f, -0xFFFF); + // and all 4 bytes + _verifyInt(f, 0x7FFFFFFF); + _verifyInt(f, 0x70000000 << 1); + } + + private void _verifyInt(CBORFactorySizer f, int value) throws Exception { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + JsonGenerator gen = cborGeneratorSizer(f, out); + gen.writeNumber(value); + gen.close(); + JsonParser p = cborParser(f, out.toByteArray()); + assertEquals(JsonToken.VALUE_NUMBER_INT, p.nextToken()); + assertEquals(NumberType.INT, p.getNumberType()); + assertEquals(value, p.getIntValue()); + assertEquals((double) value, p.getDoubleValue()); + assertNull(p.nextToken()); + p.close(); + } + + public void testLongValues() throws Exception { + CBORFactorySizer f = cborFactorySizer(); + _verifyLong(f, 1L + Integer.MAX_VALUE); + _verifyLong(f, -1L + Integer.MIN_VALUE); + } + + private void _verifyLong(CBORFactorySizer f, long value) throws Exception { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + JsonGenerator gen = cborGeneratorSizer(f, out); + gen.writeNumber(value); + gen.close(); + JsonParser p = cborParser(f, out.toByteArray()); + assertEquals(JsonToken.VALUE_NUMBER_INT, p.nextToken()); + assertEquals(value, p.getLongValue()); + assertEquals(NumberType.LONG, p.getNumberType()); + assertEquals((double) value, p.getDoubleValue()); + assertNull(p.nextToken()); + p.close(); + } + + public void testFloatValues() throws Exception { + // first, single-byte + CBORFactorySizer f = cborFactorySizer(); + // single byte + _verifyFloat(f, 0.25); + _verifyFloat(f, 20.5); + + // But then, oddity: 16-bit mini-float + // Examples from + // [https://en.wikipedia.org/wiki/Half_precision_floating-point_format] + _verifyHalfFloat(f, 0, 0.0); + _verifyHalfFloat(f, 0x3C00, 1.0); + _verifyHalfFloat(f, 0xC000, -2.0); + _verifyHalfFloat(f, 0x7BFF, 65504.0); + _verifyHalfFloat(f, 0x7C00, Double.POSITIVE_INFINITY); + _verifyHalfFloat(f, 0xFC00, Double.NEGATIVE_INFINITY); + + // ... can add more, but need bit looser comparison if so + } + + private void _verifyFloat(CBORFactorySizer f, double value) throws Exception { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + JsonGenerator gen = cborGeneratorSizer(f, out); + gen.writeNumber((float) value); + gen.close(); + JsonParser p = cborParser(f, out.toByteArray()); + assertEquals(JsonToken.VALUE_NUMBER_FLOAT, p.nextToken()); + if (NumberType.FLOAT != p.getNumberType()) { + fail("Expected `NumberType.FLOAT`, got " + p.getNumberType() + ": " + p.getText()); + } + assertEquals(value, p.getDoubleValue()); + assertNull(p.nextToken()); + p.close(); + } + + private void _verifyHalfFloat(JsonFactory f, int i16, double value) throws IOException { + JsonParser p = f.createParser( + new byte[] { (byte) (CBORConstants.PREFIX_TYPE_MISC + 25), (byte) (i16 >> 8), (byte) i16 }); + assertEquals(JsonToken.VALUE_NUMBER_FLOAT, p.nextToken()); + assertEquals(NumberType.FLOAT, p.getNumberType()); + assertEquals(value, p.getDoubleValue()); + assertNull(p.nextToken()); + p.close(); + } + + public void testSimpleArray() throws Exception { + byte[] b = MAPPER.writeValueAsBytes(new int[] { 1, 2, 3, 4 }); + int[] output = MAPPER.readValue(b, int[].class); + assertEquals(4, output.length); + for (int i = 1; i <= output.length; ++i) { + assertEquals(i, output[i - 1]); + } + } + + public void testSimpleObject() throws Exception { + Map input = new LinkedHashMap(); + input.put("a", 1); + input.put("bar", "foo"); + final String NON_ASCII_NAME = "Y\\u00F6"; + input.put(NON_ASCII_NAME, -3.25); + input.put("", ""); + byte[] b = MAPPER.writeValueAsBytes(input); + + // First, using streaming API + JsonParser p = cborParser(b); + assertToken(JsonToken.START_OBJECT, p.nextToken()); + + assertToken(JsonToken.FIELD_NAME, p.nextToken()); + assertEquals("a", p.getCurrentName()); + assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken()); + assertEquals(1, p.getIntValue()); + + assertToken(JsonToken.FIELD_NAME, p.nextToken()); + assertEquals("bar", p.getCurrentName()); + assertToken(JsonToken.VALUE_STRING, p.nextToken()); + assertEquals("foo", p.getText()); + + assertToken(JsonToken.FIELD_NAME, p.nextToken()); + assertEquals(NON_ASCII_NAME, p.getCurrentName()); + assertToken(JsonToken.VALUE_NUMBER_FLOAT, p.nextToken()); + assertEquals(-3.25, p.getDoubleValue()); + + assertToken(JsonToken.FIELD_NAME, p.nextToken()); + assertEquals("", p.getCurrentName()); + assertToken(JsonToken.VALUE_STRING, p.nextToken()); + assertEquals("", p.getText()); + + assertToken(JsonToken.END_OBJECT, p.nextToken()); + + p.close(); + + Map output = MAPPER.readValue(b, Map.class); + assertEquals(4, output.size()); + assertEquals(Integer.valueOf(1), output.get("a")); + assertEquals("foo", output.get("bar")); + assertEquals(Double.valueOf(-3.25), output.get(NON_ASCII_NAME)); + assertEquals("", output.get("")); + } + + public void testMediumText() throws Exception { + _testMedium(1100); + _testMedium(1300); + _testMedium(1900); + _testMedium(2300); + _testMedium(3900); + } + + private void _testMedium(int len) throws Exception { + // First, use size that should fit in output buffer, but + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + final String MEDIUM = generateUnicodeString(len); + gen.writeString(MEDIUM); + gen.close(); + + final byte[] b = out.toByteArray(); + + // verify that it is indeed non-chunked still... + assertEquals((byte) (CBORConstants.PREFIX_TYPE_TEXT + 25), b[0]); + + JsonParser p = cborParser(b); + assertToken(JsonToken.VALUE_STRING, p.nextToken()); + assertEquals(MEDIUM, p.getText()); + assertNull(p.nextToken()); + p.close(); + } + + public void testCurrentLocationByteOffset() throws Exception { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + gen.writeString("1234567890"); + gen.writeString("1234567890"); + gen.close(); + + final byte[] b = out.toByteArray(); + + JsonParser p = cborParser(b); + + assertToken(JsonToken.VALUE_STRING, p.nextToken()); + assertEquals(1, p.getCurrentLocation().getByteOffset()); + p.getText(); // fully read token. + assertEquals(11, p.getCurrentLocation().getByteOffset()); + + assertToken(JsonToken.VALUE_STRING, p.nextToken()); + assertEquals(12, p.getCurrentLocation().getByteOffset()); + p.getText(); + assertEquals(22, p.getCurrentLocation().getByteOffset()); + + assertNull(p.nextToken()); + assertEquals(22, p.getCurrentLocation().getByteOffset()); + + p.close(); + assertEquals(22, p.getCurrentLocation().getByteOffset()); + } + + public void testLongNonChunkedText() throws Exception { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + final String LONG = generateUnicodeString(37000); + final byte[] LONG_B = LONG.getBytes("UTF-8"); + final int BYTE_LEN = LONG_B.length; + out.write(CBORConstants.BYTE_ARRAY_INDEFINITE); + out.write((byte) (CBORConstants.PREFIX_TYPE_TEXT + 25)); + out.write((byte) (BYTE_LEN >> 8)); + out.write((byte) BYTE_LEN); + out.write(LONG.getBytes("UTF-8")); + out.write(CBORConstants.BYTE_BREAK); + + final byte[] b = out.toByteArray(); + assertEquals(BYTE_LEN + 5, b.length); + + // Important! Need to construct a stream, to force boundary conditions + JsonParser p = cborParser(new ByteArrayInputStream(b)); + assertToken(JsonToken.START_ARRAY, p.nextToken()); + assertToken(JsonToken.VALUE_STRING, p.nextToken()); + String actual = p.getText(); + + final int end = Math.min(LONG.length(), actual.length()); + for (int i = 0; i < end; ++i) { + if (LONG.charAt(i) != actual.charAt(i)) { + fail("Character #" + i + " (of " + end + ") differs; expected 0x" + Integer.toHexString(LONG.charAt(i)) + + " found 0x" + Integer.toHexString(actual.charAt(i))); + } + } + + assertEquals(LONG.length(), actual.length()); + + assertEquals(LONG, p.getText()); + assertToken(JsonToken.END_ARRAY, p.nextToken()); + assertNull(p.nextToken()); + p.close(); + } + + public void testLongChunkedText() throws Exception { + // First, try with ASCII content + StringBuilder sb = new StringBuilder(21000); + for (int i = 0; i < 21000; ++i) { + sb.append('Z'); + } + _testLongChunkedText(sb.toString()); + // Second, with actual variable byte-length Unicode + _testLongChunkedText(generateUnicodeString(21000)); + } + + public void _testLongChunkedText(String input) throws Exception { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer gen = cborGeneratorSizer(out); + gen.writeString(input); + gen.close(); + + final int textByteCount = input.getBytes("UTF-8").length; + final byte[] b = out.toByteArray(); + assertEquals((byte) (CBORConstants.PREFIX_TYPE_TEXT + 0x1F), b[0]); + assertEquals(CBORConstants.BYTE_BREAK, b[b.length - 1]); + + // First, verify validity by scanning + int i = 1; + int total = 0; + + for (int end = b.length - 1; i < end;) { + int type = b[i++] & 0xFF; + int len = type - CBORConstants.PREFIX_TYPE_TEXT; + + if (len < 24) { // tiny, fine + ; + } else if (len == 24) { // 1-byte + len = (b[i++] & 0xFF); + } else if (len == 25) { // 2-byte + len = ((b[i++] & 0xFF) << 8) + (b[i++] & 0xFF); + } + i += len; + total += len; + } + assertEquals(b.length - 1, i); + assertEquals(textByteCount, total); + + JsonParser p; + + // then skipping + p = cborParser(new ByteArrayInputStream(b)); + assertToken(JsonToken.VALUE_STRING, p.nextToken()); + assertNull(p.nextToken()); + p.close(); + + // and then with actual full parsing/access + p = cborParser(new ByteArrayInputStream(b)); + assertToken(JsonToken.VALUE_STRING, p.nextToken()); + String actual = p.getText(); + assertNull(p.nextToken()); + assertEquals(input.length(), actual.length()); + if (!input.equals(actual)) { + i = 0; + while (i < input.length() && input.charAt(i) == actual.charAt(i)) { + ++i; + } + fail("Strings differ at #" + i + " (length " + input.length() + "); expected 0x" + + Integer.toHexString(input.charAt(i)) + ", got 0x" + Integer.toHexString(actual.charAt(i))); + } + assertEquals(input, actual); + p.close(); + } + + public void testFloatNumberType() throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGeneratorSizer generator = cborGeneratorSizer(out); + generator.writeStartObject(); + generator.writeFieldName("foo"); + generator.writeNumber(3f); + generator.writeEndObject(); + generator.close(); + + CBORParser parser = cborParser(out.toByteArray()); + assertEquals(JsonToken.START_OBJECT, parser.nextToken()); + assertEquals(JsonToken.FIELD_NAME, parser.nextToken()); + assertEquals(JsonToken.VALUE_NUMBER_FLOAT, parser.nextToken()); + // fails with expected but was + assertEquals(NumberType.FLOAT, parser.getNumberType()); + assertEquals(JsonToken.END_OBJECT, parser.nextToken()); + parser.close(); + } +} diff --git a/src/test/java/com/fasterxml/jackson/dataformat/cbor/sizer/TestBiggerDataOnGeneratorSizer.java b/src/test/java/com/fasterxml/jackson/dataformat/cbor/sizer/TestBiggerDataOnGeneratorSizer.java new file mode 100644 index 0000000..7c7a8f9 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/dataformat/cbor/sizer/TestBiggerDataOnGeneratorSizer.java @@ -0,0 +1,67 @@ +package com.fasterxml.jackson.dataformat.cbor.sizer; + +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.dataformat.cbor.TestBiggerData; + +/** + * Bigger test to try to do smoke-testing of overall functionality, using more + * sizable (500k of JSON, 200k of encoded data) dataset. Should tease out at + * least some of boundary conditions. + */ +public class TestBiggerDataOnGeneratorSizer extends TestBiggerData { + final ObjectMapper MAPPER = new ObjectMapper(); + + public void testReadingOnGeneratorSizer() throws Exception { + Citm citm0 = MAPPER.readValue(getClass().getResourceAsStream("/data/citm_catalog.json"), Citm.class); + + ObjectMapper mapper = cborMapper(); + byte[] cbor = mapper.writeValueAsBytes(citm0); + + Citm citm = mapper.readValue(cbor, Citm.class); + + assertNotNull(citm); + assertNotNull(citm.areaNames); + assertEquals(17, citm.areaNames.size()); + assertNotNull(citm.events); + assertEquals(184, citm.events.size()); + + assertNotNull(citm.seatCategoryNames); + assertEquals(64, citm.seatCategoryNames.size()); + assertNotNull(citm.subTopicNames); + assertEquals(19, citm.subTopicNames.size()); + assertNotNull(citm.subjectNames); + assertEquals(0, citm.subjectNames.size()); + assertNotNull(citm.topicNames); + assertEquals(4, citm.topicNames.size()); + assertNotNull(citm.topicSubTopics); + assertEquals(4, citm.topicSubTopics.size()); + assertNotNull(citm.venueNames); + assertEquals(1, citm.venueNames.size()); + } + + public void testRoundTripOnGeneratorSizer() throws Exception { + Citm citm0 = MAPPER.readValue(getClass().getResourceAsStream("/data/citm_catalog.json"), Citm.class); + ObjectMapper mapper = cborMapper(); + byte[] cbor = mapper.writeValueAsBytes(citm0); + + Citm citm = mapper.readValue(cbor, Citm.class); + + byte[] smile1 = mapper.writeValueAsBytes(citm); + Citm citm2 = mapper.readValue(smile1, Citm.class); + byte[] smile2 = mapper.writeValueAsBytes(citm2); + + assertEquals(smile1.length, smile2.length); + + assertNotNull(citm.areaNames); + assertEquals(17, citm.areaNames.size()); + assertNotNull(citm.events); + assertEquals(184, citm.events.size()); + + assertEquals(citm.seatCategoryNames.size(), citm2.seatCategoryNames.size()); + assertEquals(citm.subTopicNames.size(), citm2.subTopicNames.size()); + assertEquals(citm.subjectNames.size(), citm2.subjectNames.size()); + assertEquals(citm.topicNames.size(), citm2.topicNames.size()); + assertEquals(citm.topicSubTopics.size(), citm2.topicSubTopics.size()); + assertEquals(citm.venueNames.size(), citm2.venueNames.size()); + } +}