├── .gitattributes ├── .gitignore ├── .travis.yml ├── README.md ├── pom.xml ├── release-notes ├── CREDITS └── VERSION └── src ├── main ├── java │ └── com │ │ └── fasterxml │ │ └── jackson │ │ └── dataformat │ │ └── protobuf │ │ ├── ByteAccumulator.java │ │ ├── PackageVersion.java.in │ │ ├── ProtobufFactory.java │ │ ├── ProtobufGenerator.java │ │ ├── ProtobufMapper.java │ │ ├── ProtobufParser.java │ │ ├── ProtobufReadContext.java │ │ ├── ProtobufUtil.java │ │ ├── ProtobufWriteContext.java │ │ ├── schema │ │ ├── EnumLookup.java │ │ ├── FieldLookup.java │ │ ├── FieldType.java │ │ ├── FieldTypes.java │ │ ├── NativeProtobufSchema.java │ │ ├── ProtobufEnum.java │ │ ├── ProtobufField.java │ │ ├── ProtobufMessage.java │ │ ├── ProtobufSchema.java │ │ ├── ProtobufSchemaLoader.java │ │ ├── TypeResolver.java │ │ ├── WireType.java │ │ └── package-info.java │ │ └── schemagen │ │ ├── AnnotationBasedTagGenerator.java │ │ ├── DefaultTagGenerator.java │ │ ├── DefinedTypeElementBuilders.java │ │ ├── EnumElementVisitor.java │ │ ├── MessageElementVisitor.java │ │ ├── ProtoBufSchemaVisitor.java │ │ ├── ProtobufSchemaGenerator.java │ │ ├── ProtobuffSchemaHelper.java │ │ ├── TagGenerator.java │ │ ├── TypeElementBuilder.java │ │ └── package-info.java └── resources │ └── META-INF │ └── services │ └── com.fasterxml.jackson.core.JsonFactory └── test └── java └── com └── fasterxml └── jackson └── dataformat └── protobuf ├── BigNumPair.java ├── LimitingInputStream.java ├── ProtobufTestBase.java ├── ReadComplexPojoTest.java ├── ReadSimpleTest.java ├── SchemaParsingTest.java ├── SerDeserLongTest.java ├── WriteArrayTest.java ├── WriteAsMapTest.java ├── WriteBigArrayTest.java ├── WriteBinaryTest.java ├── WriteComplexPojoTest.java ├── WriteErrorsTest.java ├── WriteSimpleTest.java ├── WriteStringsTest.java └── schemagen └── SchemaGenTest.java /.gitattributes: -------------------------------------------------------------------------------- 1 | # Do not merge `pom.xml` from older version, as it will typically conflict 2 | 3 | pom.xml merge=ours 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # use glob syntax. 2 | syntax: glob 3 | *.class 4 | *~ 5 | *.bak 6 | *.off 7 | *.old 8 | .DS_Store 9 | 10 | # building 11 | target 12 | 13 | # Eclipse 14 | .classpath 15 | .project 16 | .settings 17 | 18 | # IDEA 19 | *.iml 20 | *.ipr 21 | *.iws 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | jdk: 4 | - oraclejdk7 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **NOTE**: This module has become part of [Jackson Binary Dataformats](../../../jackson-dataformats-binary) 2 | as of Jackson 2.8 3 | 4 | This repo still exists to allow release of patch versions of older versions; it will be hidden (made private) 5 | in near future. 6 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.fasterxml.jackson 6 | jackson-parent 7 | 2.7 8 | 9 | com.fasterxml.jackson.dataformat 10 | jackson-dataformat-protobuf 11 | Jackson-dataformat-protobuf 12 | 2.7.10-SNAPSHOT 13 | bundle 14 | Support for reading and writing protobuf-encoded data via Jackson 15 | abstractions. 16 | 17 | http://wiki.fasterxml.com/JacksonExtensionProtobuf 18 | 19 | scm:git:git@github.com:FasterXML/jackson-dataformat-protobuf.git 20 | scm:git:git@github.com:FasterXML/jackson-dataformat-protobuf.git 21 | http://github.com/FasterXML/jackson-dataformat-protobuf 22 | HEAD 23 | 24 | 25 | 26 | 2.7.9 27 | 28 | com/fasterxml/jackson/dataformat/protobuf 29 | ${project.groupId}.protobuf 30 | 31 | ${project.groupId}.protobuf.*; version=${project.version} 32 | com.squareup.protoparser.* 33 | 34 | 35 | 36 | 37 | 38 | com.fasterxml.jackson.core 39 | jackson-core 40 | ${jackson.version.core} 41 | 42 | 47 | 48 | com.fasterxml.jackson.core 49 | jackson-databind 50 | ${jackson.version.core} 51 | provided 52 | 53 | 54 | com.squareup 55 | protoparser 56 | 4.0.0 57 | 58 | 59 | 60 | 61 | com.fasterxml.jackson.core 62 | jackson-annotations 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | com.google.code.maven-replacer-plugin 71 | replacer 72 | 73 | 74 | process-packageVersion 75 | generate-sources 76 | 77 | 78 | 79 | 80 | 81 | 82 | org.apache.maven.plugins 83 | maven-shade-plugin 84 | 2.2 85 | 86 | 87 | package 88 | 89 | shade 90 | 91 | 92 | 93 | 94 | 95 | 98 | null:null 99 | 100 | 101 | 102 | 103 | com.squareup 104 | com.fasterxml.jackson.dataformat.protobuf.protoparser 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /release-notes/CREDITS: -------------------------------------------------------------------------------- 1 | Project: jackson-dataformat-protobuf 2 | 3 | Here are people who have contributed to development of this project: 4 | (version numbers in parenthesis indicate release in which the problem was fixed) 5 | 6 | Tatu Saloranta, tatu.saloranta@iki.fi: author 7 | 8 | Damiaan van der Kruk (dvdkruk@github) 9 | * Contributed #11: Add Support for Generating Protobuf Schema From POJO Definition 10 | (2.7.0) 11 | 12 | Charles Swanberg (afterthought@github) 13 | * Reported #14: Problem with larger datasets - error releasing buffers 14 | (2.7.2) 15 | -------------------------------------------------------------------------------- /release-notes/VERSION: -------------------------------------------------------------------------------- 1 | Project: jackson-dataformat-protobuf 2 | 3 | ------------------------------------------------------------------------ 4 | === Releases === 5 | ------------------------------------------------------------------------ 6 | 7 | 2.7.10 (not yet released) 8 | 9 | dataformats-binary#85: _decode32Bits() bug in ProtobufParser 10 | 11 | 2.7.9 (04-Feb-2017) 12 | 2.7.8 (26-Sep-2016) 13 | 2.7.7 (27-Aug-2016) 14 | 15 | dataformats-binary#27: Fixed long deserialization problem for longs of ~13digit length 16 | (contributed by Michael Z) 17 | 18 | 2.7.6 (23-Jul-2016) 19 | 2.7.5 (11-Jun-2016) 20 | 2.7.4 (29-Apr-2016) 21 | 22 | No changes since 2.7.3 23 | 24 | 2.7.3 (16-Mar-2016) 25 | 26 | #14: Problem with larger datasets - error releasing buffers 27 | (reported by Charles S) 28 | 29 | 2.7.2 (27-Feb-2016) 30 | 31 | No changes since 2.7.1 32 | 33 | 2.7.1 (02-Feb-2016) 34 | 35 | - Fix handling of "current value" for ProtobufGenerator 36 | 37 | 2.7.0 (10-Jan-2016) 38 | 39 | #11: Add Support for Generating Protobuf Schema From POJO Definition 40 | (contributed by Damiaan K) 41 | 42 | 2.6.6 (06-Apr-2016) 43 | 2.6.5 (19-Jan-2016) 44 | 2.6.4 (07-Dec-2015) 45 | 2.6.3 (12-Oct-2015) 46 | 2.6.2 (15-Sep-2015) 47 | 2.6.1 (09-Aug-2015) 48 | 49 | No changes since 2.6.0. 50 | 51 | 2.6.0 (20-Jul-2015) 52 | 53 | First public version! 54 | 55 | -------------------------------------------------------------------------------- /src/main/java/com/fasterxml/jackson/dataformat/protobuf/ByteAccumulator.java: -------------------------------------------------------------------------------- 1 | package com.fasterxml.jackson.dataformat.protobuf; 2 | 3 | import java.io.*; 4 | 5 | /** 6 | * Helper object used for buffering content for cases where we need (byte-)length prefixes 7 | * for content like packed arrays, embedded messages, Strings and (perhaps) binary content. 8 | */ 9 | public class ByteAccumulator 10 | { 11 | protected final ByteAccumulator _parent; 12 | 13 | /** 14 | * Caller-provided buffer in which optional type prefix, 15 | * and mandatory length indicator may be added. 16 | * Caller ensures there is enough room for both, i.e. up 17 | * to 10 bytes (if both) or 5 bytes (if just length) 18 | */ 19 | protected final byte[] _prefixBuffer; 20 | 21 | /** 22 | * Offset within {@link #_prefixBuffer} where there is room 23 | * for prefix. 24 | */ 25 | protected final int _prefixOffset; 26 | 27 | protected final int _typedTag; 28 | 29 | protected Segment _firstSegment, _lastSegment; 30 | 31 | protected int _segmentBytes; 32 | 33 | public ByteAccumulator(ByteAccumulator p, int typedTag, 34 | byte[] prefixBuffer, int prefixOffset) 35 | { 36 | _parent = p; 37 | _typedTag = typedTag; 38 | _prefixBuffer = prefixBuffer; 39 | _prefixOffset = prefixOffset; 40 | } 41 | 42 | public ByteAccumulator(ByteAccumulator p, 43 | byte[] prefixBuffer, int prefixOffset) { 44 | _parent = p; 45 | _typedTag = -1; 46 | _prefixBuffer = prefixBuffer; 47 | _prefixOffset = prefixOffset; 48 | } 49 | 50 | public void append(byte[] buf, int offset, int len) { 51 | Segment s = new Segment(buf, offset, len); 52 | if (_lastSegment == null) { 53 | _firstSegment = _lastSegment = s; 54 | } else { 55 | _lastSegment = _lastSegment.linkNext(s); 56 | } 57 | _segmentBytes += len; 58 | } 59 | 60 | public ByteAccumulator finish(OutputStream out, 61 | byte[] input, int offset, int len) throws IOException 62 | { 63 | int start = _prefixOffset; 64 | int ptr; 65 | final byte[] prefix = _prefixBuffer; 66 | 67 | if (_typedTag == -1) { 68 | ptr = start; 69 | } else { 70 | ptr = ProtobufUtil.appendLengthLength(_typedTag, prefix, start); 71 | } 72 | 73 | int plen = _segmentBytes + len; 74 | 75 | ptr = ProtobufUtil.appendLengthLength(plen, prefix, ptr); 76 | 77 | // root? Just output it all 78 | if (_parent == null) { 79 | out.write(prefix, start, ptr-start); 80 | for (Segment s = _firstSegment; s != null; s = s.next()) { 81 | s.writeTo(out); 82 | } 83 | if (len > 0) { 84 | out.write(input, offset, len); 85 | } 86 | } else { 87 | _parent.append(prefix, start, ptr-start); 88 | if (_firstSegment != null) { 89 | _parent.appendAll(_firstSegment, _lastSegment, _segmentBytes); 90 | } 91 | if (len > 0) { 92 | _parent.append(input, offset, len); 93 | } 94 | } 95 | return _parent; 96 | } 97 | 98 | public ByteAccumulator finish(OutputStream out) throws IOException 99 | { 100 | int start = _prefixOffset; 101 | int ptr; 102 | final byte[] prefix = _prefixBuffer; 103 | 104 | if (_typedTag == -1) { 105 | ptr = start; 106 | } else { 107 | ptr = ProtobufUtil.appendLengthLength(_typedTag, prefix, start); 108 | } 109 | int plen = _segmentBytes; 110 | ptr = ProtobufUtil.appendLengthLength(plen, prefix, ptr); 111 | 112 | // root? Just output it all 113 | if (_parent == null) { 114 | out.write(prefix, start, ptr-start); 115 | for (Segment s = _firstSegment; s != null; s = s.next()) { 116 | s.writeTo(out); 117 | } 118 | } else { 119 | _parent.append(prefix, start, ptr-start); 120 | if (_firstSegment != null) { 121 | _parent.appendAll(_firstSegment, _lastSegment, _segmentBytes); 122 | } 123 | } 124 | return _parent; 125 | } 126 | 127 | private void appendAll(Segment first, Segment last, int segmentBytes) 128 | { 129 | _segmentBytes += segmentBytes; 130 | 131 | if (_firstSegment == null) { 132 | _firstSegment = first; 133 | _lastSegment = last; 134 | } else { 135 | _lastSegment.linkNext(first); 136 | _lastSegment = last; 137 | } 138 | } 139 | 140 | /* 141 | /********************************************************** 142 | /* Helper classes 143 | /********************************************************** 144 | */ 145 | 146 | private final static class Segment 147 | { 148 | private final byte[] _buffer; 149 | private final int _start, _length; 150 | 151 | private Segment _next; 152 | 153 | public Segment(byte[] buffer, int start, int length) { 154 | _buffer = buffer; 155 | _start = start; 156 | _length = length; 157 | } 158 | 159 | public Segment linkNext(Segment next) { 160 | _next = next; 161 | return next; 162 | } 163 | 164 | public Segment next() { 165 | return _next; 166 | } 167 | public void writeTo(OutputStream out) throws IOException { 168 | out.write(_buffer, _start, _length); 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/main/java/com/fasterxml/jackson/dataformat/protobuf/PackageVersion.java.in: -------------------------------------------------------------------------------- 1 | package @package@; 2 | 3 | import com.fasterxml.jackson.core.Version; 4 | import com.fasterxml.jackson.core.Versioned; 5 | import com.fasterxml.jackson.core.util.VersionUtil; 6 | 7 | /** 8 | * Automatically generated from PackageVersion.java.in during 9 | * packageVersion-generate execution of maven-replacer-plugin in 10 | * pom.xml. 11 | */ 12 | public final class PackageVersion implements Versioned { 13 | public final static Version VERSION = VersionUtil.parseVersion( 14 | "@projectversion@", "@projectgroupid@", "@projectartifactid@"); 15 | 16 | @Override 17 | public Version version() { 18 | return VERSION; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/fasterxml/jackson/dataformat/protobuf/ProtobufFactory.java: -------------------------------------------------------------------------------- 1 | package com.fasterxml.jackson.dataformat.protobuf; 2 | 3 | import java.io.*; 4 | import java.net.URL; 5 | 6 | import com.fasterxml.jackson.core.*; 7 | import com.fasterxml.jackson.core.format.InputAccessor; 8 | import com.fasterxml.jackson.core.format.MatchStrength; 9 | import com.fasterxml.jackson.core.io.IOContext; 10 | import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufSchema; 11 | 12 | public class ProtobufFactory extends JsonFactory 13 | { 14 | private static final long serialVersionUID = 1; 15 | 16 | /* 17 | /********************************************************** 18 | /* Factory construction, configuration 19 | /********************************************************** 20 | */ 21 | 22 | public ProtobufFactory() { } 23 | 24 | public ProtobufFactory(ObjectCodec codec) { 25 | super(codec); 26 | } 27 | 28 | protected ProtobufFactory(ProtobufFactory src, ObjectCodec oc) 29 | { 30 | super(src, oc); 31 | } 32 | 33 | @Override 34 | public ProtobufFactory copy() 35 | { 36 | _checkInvalidCopy(ProtobufFactory.class); 37 | return new ProtobufFactory(this, null); 38 | } 39 | 40 | /* 41 | /********************************************************** 42 | /* Serializable overrides 43 | /********************************************************** 44 | */ 45 | 46 | /** 47 | * Method that we need to override to actually make restoration go 48 | * through constructors etc. 49 | * Also: must be overridden by sub-classes as well. 50 | */ 51 | @Override 52 | protected Object readResolve() { 53 | return new ProtobufFactory(this, _objectCodec); 54 | } 55 | 56 | /* 57 | /********************************************************** 58 | /* Versioned 59 | /********************************************************** 60 | */ 61 | 62 | @Override 63 | public Version version() { 64 | return PackageVersion.VERSION; 65 | } 66 | 67 | /* 68 | /********************************************************** 69 | /* Format detection functionality 70 | /********************************************************** 71 | */ 72 | 73 | @Override 74 | public String getFormatName() { 75 | return ProtobufSchema.FORMAT_NAME_PROTOBUF; 76 | } 77 | 78 | /** 79 | * Sub-classes need to override this method 80 | */ 81 | @Override 82 | public MatchStrength hasFormat(InputAccessor acc) throws IOException 83 | { 84 | // TODO, if possible... probably isn't? 85 | return MatchStrength.INCONCLUSIVE; 86 | } 87 | 88 | /* 89 | /********************************************************** 90 | /* Capability introspection 91 | /********************************************************** 92 | */ 93 | 94 | // Protobuf is not positional 95 | @Override 96 | public boolean requiresPropertyOrdering() { 97 | return false; 98 | } 99 | 100 | // Protobuf can embed raw binary data natively 101 | @Override 102 | public boolean canHandleBinaryNatively() { 103 | return true; 104 | } 105 | 106 | // No format-specific configuration, yet: 107 | /* 108 | @Override 109 | public Class getFormatReadFeatureType() { 110 | return null; 111 | } 112 | 113 | @Override 114 | public Class getFormatWriteFeatureType() { 115 | return null; 116 | } 117 | */ 118 | 119 | /* 120 | /********************************************************** 121 | /* Overridden parser factory methods 122 | /********************************************************** 123 | */ 124 | 125 | @Override 126 | public ProtobufParser createParser(File f) throws IOException { 127 | return _createParser(new FileInputStream(f), _createContext(f, true)); 128 | } 129 | 130 | @Override 131 | public ProtobufParser createParser(URL url) throws IOException { 132 | return _createParser(_optimizedStreamFromURL(url), _createContext(url, true)); 133 | } 134 | 135 | @Override 136 | public ProtobufParser createParser(InputStream in) throws IOException { 137 | return _createParser(in, _createContext(in, false)); 138 | } 139 | 140 | @Override 141 | public ProtobufParser createParser(byte[] data) throws IOException { 142 | return _createParser(data, 0, data.length, _createContext(data, true)); 143 | } 144 | 145 | @Override 146 | public ProtobufParser createParser(byte[] data, int offset, int len) throws IOException { 147 | return _createParser(data, offset, len, _createContext(data, true)); 148 | } 149 | 150 | /* 151 | /********************************************************** 152 | /* Overridden generator factory methods 153 | /********************************************************** 154 | */ 155 | 156 | @Override 157 | public ProtobufGenerator createGenerator(OutputStream out, JsonEncoding enc) throws IOException { 158 | IOContext ctxt = _createContext(out, false); 159 | ctxt.setEncoding(enc); 160 | out = _decorate(out, ctxt); 161 | return _createProtobufGenerator(ctxt, _generatorFeatures, _objectCodec, out); 162 | } 163 | 164 | /** 165 | * Method for constructing {@link JsonGenerator} for generating 166 | * protobuf-encoded output. 167 | *

168 | * Since protobuf format always uses UTF-8 internally, no encoding need 169 | * to be passed to this method. 170 | */ 171 | @Override 172 | public ProtobufGenerator createGenerator(OutputStream out) throws IOException { 173 | IOContext ctxt = _createContext(out, false); 174 | out = _decorate(out, ctxt); 175 | return _createProtobufGenerator(ctxt, _generatorFeatures, _objectCodec, out); 176 | } 177 | 178 | /* 179 | /****************************************************** 180 | /* Overridden internal factory methods 181 | /****************************************************** 182 | */ 183 | 184 | @Override 185 | protected IOContext _createContext(Object srcRef, boolean resourceManaged) { 186 | return super._createContext(srcRef, resourceManaged); 187 | } 188 | 189 | @Override 190 | protected ProtobufParser _createParser(InputStream in, IOContext ctxt) throws IOException 191 | { 192 | byte[] buf = ctxt.allocReadIOBuffer(); 193 | return new ProtobufParser(ctxt, _parserFeatures, 194 | _objectCodec, in, buf, 0, 0, true); 195 | } 196 | 197 | @Override 198 | protected JsonParser _createParser(Reader r, IOContext ctxt) throws IOException { 199 | return _nonByteSource(); 200 | } 201 | 202 | @Override 203 | protected JsonParser _createParser(char[] data, int offset, int len, IOContext ctxt, 204 | boolean recyclable) throws IOException { 205 | return _nonByteSource(); 206 | } 207 | 208 | @Override 209 | protected ProtobufParser _createParser(byte[] data, int offset, int len, IOContext ctxt) throws IOException 210 | { 211 | return new ProtobufParser(ctxt, _parserFeatures, 212 | _objectCodec, null, data, offset, len, false); 213 | } 214 | 215 | @Override 216 | protected ProtobufGenerator _createGenerator(Writer out, IOContext ctxt) throws IOException { 217 | return _nonByteTarget(); 218 | } 219 | 220 | @Override 221 | protected ProtobufGenerator _createUTF8Generator(OutputStream out, IOContext ctxt) throws IOException { 222 | return _createProtobufGenerator(ctxt, _generatorFeatures, _objectCodec, out); 223 | } 224 | 225 | @Override 226 | protected Writer _createWriter(OutputStream out, JsonEncoding enc, IOContext ctxt) throws IOException { 227 | return _nonByteTarget(); 228 | } 229 | 230 | private final ProtobufGenerator _createProtobufGenerator(IOContext ctxt, 231 | int stdFeat, ObjectCodec codec, OutputStream out) throws IOException 232 | { 233 | return new ProtobufGenerator(ctxt, stdFeat, _objectCodec, out); 234 | } 235 | 236 | protected T _nonByteTarget() { 237 | throw new UnsupportedOperationException("Can not create generator for non-byte-based target"); 238 | } 239 | 240 | protected T _nonByteSource() { 241 | throw new UnsupportedOperationException("Can not create generator for non-byte-based source"); 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /src/main/java/com/fasterxml/jackson/dataformat/protobuf/ProtobufMapper.java: -------------------------------------------------------------------------------- 1 | package com.fasterxml.jackson.dataformat.protobuf; 2 | 3 | import com.fasterxml.jackson.core.Version; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufSchemaLoader; 6 | 7 | public class ProtobufMapper extends ObjectMapper 8 | { 9 | private static final long serialVersionUID = 1L; 10 | 11 | protected ProtobufSchemaLoader _schemaLoader = ProtobufSchemaLoader.std; 12 | 13 | /* 14 | /********************************************************** 15 | /* Life-cycle 16 | /********************************************************** 17 | */ 18 | 19 | public ProtobufMapper() { 20 | this(new ProtobufFactory()); 21 | } 22 | 23 | public ProtobufMapper(ProtobufFactory f) { 24 | super(f); 25 | } 26 | 27 | protected ProtobufMapper(ProtobufMapper src) { 28 | super(src); 29 | } 30 | 31 | @Override 32 | public ProtobufMapper copy() 33 | { 34 | _checkInvalidCopy(ProtobufMapper.class); 35 | return new ProtobufMapper(this); 36 | } 37 | 38 | @Override 39 | public Version version() { 40 | return PackageVersion.VERSION; 41 | } 42 | 43 | @Override 44 | public ProtobufFactory getFactory() { 45 | return (ProtobufFactory) _jsonFactory; 46 | } 47 | 48 | /* 49 | /********************************************************** 50 | /* Schema access 51 | /********************************************************** 52 | */ 53 | 54 | public ProtobufSchemaLoader schemaLoader() { 55 | return _schemaLoader; 56 | } 57 | 58 | public void setSchemaLoader(ProtobufSchemaLoader l) { 59 | _schemaLoader = l; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/fasterxml/jackson/dataformat/protobuf/ProtobufReadContext.java: -------------------------------------------------------------------------------- 1 | package com.fasterxml.jackson.dataformat.protobuf; 2 | 3 | import com.fasterxml.jackson.core.*; 4 | import com.fasterxml.jackson.core.io.CharTypes; 5 | import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufField; 6 | import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufMessage; 7 | 8 | /** 9 | * Replacement of {@link com.fasterxml.jackson.core.json.JsonReadContext} 10 | * to support features needed to decode nested Protobuf messages. 11 | */ 12 | public final class ProtobufReadContext 13 | extends JsonStreamContext 14 | { 15 | /** 16 | * Parent context for this context; null for root context. 17 | */ 18 | protected final ProtobufReadContext _parent; 19 | 20 | /** 21 | * Type of current context. 22 | */ 23 | protected ProtobufMessage _messageType; 24 | 25 | /** 26 | * For array contexts: field that defines type of array values. 27 | */ 28 | protected ProtobufField _field; 29 | 30 | protected String _currentName; 31 | 32 | /** 33 | * Offset within input buffer where the message represented 34 | * by this context (if message context) ends. 35 | */ 36 | protected int _endOffset; 37 | 38 | /* 39 | /********************************************************** 40 | /* Simple instance reuse slots 41 | /********************************************************** 42 | */ 43 | 44 | protected ProtobufReadContext _child = null; 45 | 46 | /* 47 | /********************************************************** 48 | /* Instance construction, reuse 49 | /********************************************************** 50 | */ 51 | 52 | public ProtobufReadContext(ProtobufReadContext parent, 53 | ProtobufMessage messageType, int type, int endOffset) 54 | { 55 | super(); 56 | _parent = parent; 57 | _messageType = messageType; 58 | _type = type; 59 | _endOffset = endOffset; 60 | _index = -1; 61 | } 62 | 63 | protected void reset(ProtobufMessage messageType, int type, int endOffset) 64 | { 65 | _messageType = messageType; 66 | _type = type; 67 | _index = -1; 68 | _currentName = null; 69 | _endOffset = endOffset; 70 | } 71 | 72 | // // // Factory methods 73 | 74 | public static ProtobufReadContext createRootContext() { 75 | return new ProtobufReadContext(null, null, TYPE_ROOT, Integer.MAX_VALUE); 76 | } 77 | 78 | public ProtobufReadContext createChildArrayContext(ProtobufField f) 79 | { 80 | _field = f; 81 | ProtobufReadContext ctxt = _child; 82 | if (ctxt == null) { 83 | _child = ctxt = new ProtobufReadContext(this, _messageType, 84 | TYPE_ARRAY, _endOffset); 85 | } else { 86 | ctxt.reset(_messageType, TYPE_ARRAY, _endOffset); 87 | } 88 | return ctxt; 89 | } 90 | 91 | public ProtobufReadContext createChildArrayContext(ProtobufField f, int endOffset) 92 | { 93 | _field = f; 94 | ProtobufReadContext ctxt = _child; 95 | if (ctxt == null) { 96 | _child = ctxt = new ProtobufReadContext(this, _messageType, 97 | TYPE_ARRAY, 0); 98 | } else { 99 | ctxt.reset(_messageType, TYPE_ARRAY, endOffset); 100 | } 101 | ctxt._field = f; 102 | return ctxt; 103 | } 104 | 105 | public ProtobufReadContext createChildObjectContext(ProtobufMessage messageType, 106 | ProtobufField f, int endOffset) 107 | { 108 | _field = f; 109 | ProtobufReadContext ctxt = _child; 110 | if (ctxt == null) { 111 | _child = ctxt = new ProtobufReadContext(this, messageType, 112 | TYPE_OBJECT, endOffset); 113 | return ctxt; 114 | } 115 | ctxt.reset(messageType, TYPE_OBJECT, endOffset); 116 | return ctxt; 117 | } 118 | 119 | /* 120 | /********************************************************** 121 | /* Abstract method implementations 122 | /********************************************************** 123 | */ 124 | 125 | @Override 126 | public String getCurrentName() { return _currentName; } 127 | 128 | @Override 129 | public ProtobufReadContext getParent() { return _parent; } 130 | 131 | /* 132 | /********************************************************** 133 | /* Extended API 134 | /********************************************************** 135 | */ 136 | 137 | /** 138 | * Method called when loading more input, or moving existing data; 139 | * this requires adjusting relative end offset as well, except for 140 | * root context. 141 | */ 142 | public int adjustEnd(int bytesConsumed) { 143 | if (_type == TYPE_ROOT) { 144 | return _endOffset; 145 | } 146 | int newOffset = _endOffset - bytesConsumed; 147 | 148 | _endOffset = newOffset; 149 | 150 | for (ProtobufReadContext ctxt = _parent; ctxt != null; ctxt = ctxt.getParent()) { 151 | ctxt._adjustEnd(bytesConsumed); 152 | } 153 | 154 | // could do sanity check here; but caller should catch it 155 | return newOffset; 156 | } 157 | 158 | private void _adjustEnd(int bytesConsumed) { 159 | if (_type != TYPE_ROOT) { 160 | _endOffset -= bytesConsumed; 161 | } 162 | } 163 | 164 | public int getEndOffset() { return _endOffset; } 165 | 166 | public ProtobufMessage getMessageType() { return _messageType; } 167 | 168 | public ProtobufField getField() { return _field; } 169 | 170 | public void setMessageType(ProtobufMessage mt) { _messageType = mt; } 171 | 172 | /** 173 | * @return Location pointing to the point where the context 174 | * start marker was found 175 | */ 176 | public JsonLocation getStartLocation(Object srcRef, long byteOffset) { 177 | // not much we can tell 178 | return new JsonLocation(srcRef, byteOffset, -1, -1); 179 | } 180 | 181 | /* 182 | /********************************************************** 183 | /* State changes 184 | /********************************************************** 185 | */ 186 | 187 | public void setCurrentName(String name) { 188 | _currentName = name; 189 | } 190 | 191 | /* 192 | /********************************************************** 193 | /* Overridden standard methods 194 | /********************************************************** 195 | */ 196 | 197 | /** 198 | * Overridden to provide developer readable "JsonPath" representation 199 | * of the context. 200 | */ 201 | @Override 202 | public String toString() 203 | { 204 | StringBuilder sb = new StringBuilder(64); 205 | switch (_type) { 206 | case TYPE_ROOT: 207 | sb.append("/"); 208 | break; 209 | case TYPE_ARRAY: 210 | sb.append('['); 211 | sb.append(getCurrentIndex()); 212 | sb.append(']'); 213 | break; 214 | case TYPE_OBJECT: 215 | sb.append('{'); 216 | if (_currentName != null) { 217 | sb.append('"'); 218 | CharTypes.appendQuoted(sb, _currentName); 219 | sb.append('"'); 220 | } else { 221 | sb.append('?'); 222 | } 223 | sb.append('}'); 224 | break; 225 | } 226 | return sb.toString(); 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /src/main/java/com/fasterxml/jackson/dataformat/protobuf/ProtobufUtil.java: -------------------------------------------------------------------------------- 1 | package com.fasterxml.jackson.dataformat.protobuf; 2 | 3 | public class ProtobufUtil 4 | { 5 | public final static int SECONDARY_BUFFER_LENGTH = 64000; 6 | 7 | public final static int[] sUtf8UnitLengths; 8 | static { 9 | int[] table = new int[256]; 10 | for (int c = 128; c < 256; ++c) { 11 | int code; 12 | 13 | // We'll add number of bytes needed for decoding 14 | if ((c & 0xE0) == 0xC0) { // 2 bytes (0x0080 - 0x07FF) 15 | code = 1; 16 | } else if ((c & 0xF0) == 0xE0) { // 3 bytes (0x0800 - 0xFFFF) 17 | code = 2; 18 | } else if ((c & 0xF8) == 0xF0) { 19 | // 4 bytes; double-char with surrogates and all... 20 | code = 3; 21 | } else { 22 | // And -1 seems like a good "universal" error marker... 23 | code = -1; 24 | } 25 | table[c] = code; 26 | } 27 | sUtf8UnitLengths = table; 28 | } 29 | 30 | /** 31 | * While we could get all fancy on allocating secondary buffer (after 32 | * initial one), let's start with very simple strategy of medium-length 33 | * buffer. 34 | */ 35 | public static byte[] allocSecondary(byte[] curr) { 36 | return new byte[SECONDARY_BUFFER_LENGTH]; 37 | } 38 | 39 | // NOTE: no negative values accepted 40 | public static int lengthLength(int len) { 41 | if (len <= 0x7F) { // 7 bytes 42 | // if negatives were allowed, would need another check here 43 | return 1; 44 | } 45 | if (len <= 0x3FFF) { // 14 bytes 46 | return 2; 47 | } 48 | if (len <= 0x1FFFFF) { // 21 bytes 49 | return 3; 50 | } 51 | if (len <= 0x1FFFFF) { // 21 bytes 52 | return 3; 53 | } 54 | if (len <= 0x0FFFFFFF) { // 28 bytes 55 | return 4; 56 | } 57 | return 5; 58 | } 59 | 60 | /** 61 | * NOTE: caller MUST ensure buffer has room for at least 5 bytes 62 | */ 63 | public static int appendLengthLength(int len, byte[] buffer, int ptr) 64 | { 65 | // first a quick check for common case 66 | if (len <= 0x7F) { 67 | // if negatives were allowed, would need another check here 68 | buffer[ptr++] = (byte) len; 69 | return ptr; 70 | } 71 | // but loop for longer content 72 | do { 73 | buffer[ptr++] = (byte) (0x80 + (len & 0x7F)); 74 | len = len >> 7; 75 | } while (len > 0x7F); 76 | buffer[ptr++] = (byte) len; 77 | return ptr; 78 | } 79 | 80 | // NOTE: no negative values accepted 81 | public static byte[] lengthAsBytes(int len) { 82 | int bytes = lengthLength(len); 83 | byte[] result = new byte[bytes]; 84 | int last = bytes-1; 85 | 86 | for (int i = 0; i < last; ++i) { 87 | result[i] = (byte) (0x80 + (len & 0x7F)); 88 | len >>= 7; 89 | } 90 | result[last] = (byte) len; 91 | return result; 92 | } 93 | 94 | public static int zigzagEncode(int input) { 95 | // Canonical version: 96 | //return (input << 1) ^ (input >> 31); 97 | // but this is even better 98 | if (input < 0) { 99 | return (input << 1) ^ -1; 100 | } 101 | return (input << 1); 102 | } 103 | 104 | public static int zigzagDecode(int encoded) { 105 | // canonical: 106 | //return (encoded >>> 1) ^ (-(encoded & 1)); 107 | if ((encoded & 1) == 0) { // positive 108 | return (encoded >>> 1); 109 | } 110 | // negative 111 | return (encoded >>> 1) ^ -1; 112 | } 113 | 114 | public static long zigzagEncode(long input) { 115 | // Canonical version 116 | //return (input << 1) ^ (input >> 63); 117 | if (input < 0L) { 118 | return (input << 1) ^ -1L; 119 | } 120 | return (input << 1); 121 | } 122 | 123 | public static long zigzagDecode(long encoded) { 124 | // canonical: 125 | //return (encoded >>> 1) ^ (-(encoded & 1)); 126 | if ((encoded & 1) == 0) { // positive 127 | return (encoded >>> 1); 128 | } 129 | // negative 130 | return (encoded >>> 1) ^ -1L; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/main/java/com/fasterxml/jackson/dataformat/protobuf/ProtobufWriteContext.java: -------------------------------------------------------------------------------- 1 | package com.fasterxml.jackson.dataformat.protobuf; 2 | 3 | import com.fasterxml.jackson.core.JsonStreamContext; 4 | import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufField; 5 | import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufMessage; 6 | 7 | public class ProtobufWriteContext 8 | extends JsonStreamContext 9 | { 10 | protected final ProtobufWriteContext _parent; 11 | 12 | /** 13 | * Definition of the closest Object that this context relates to; 14 | * either object for the field (for Message/Object types), or its 15 | * parent (for Array types) 16 | */ 17 | protected ProtobufMessage _message; 18 | 19 | /** 20 | * Field within either current object (for Object context); or, parent 21 | * field (for Array) 22 | */ 23 | protected ProtobufField _field; 24 | 25 | /** 26 | * @since 2.5 27 | */ 28 | protected Object _currentValue; 29 | 30 | /* 31 | /********************************************************** 32 | /* Simple instance reuse slots; speed up things 33 | /* a bit (10-15%) for docs with lots of small 34 | /* arrays/objects 35 | /********************************************************** 36 | */ 37 | 38 | protected ProtobufWriteContext _child = null; 39 | 40 | /* 41 | /********************************************************** 42 | /* Life-cycle 43 | /********************************************************** 44 | */ 45 | 46 | protected ProtobufWriteContext(int type, ProtobufWriteContext parent, 47 | ProtobufMessage msg) 48 | { 49 | super(); 50 | _type = type; 51 | _parent = parent; 52 | _message = msg; 53 | } 54 | 55 | private void reset(int type, ProtobufMessage msg, ProtobufField f) { 56 | _type = type; 57 | _message = msg; 58 | _field = f; 59 | } 60 | 61 | // // // Factory methods 62 | 63 | public static ProtobufWriteContext createRootContext(ProtobufMessage msg) { 64 | return new ProtobufWriteContext(TYPE_ROOT, null, msg); 65 | } 66 | 67 | /** 68 | * Factory method called to get a placeholder context that is only 69 | * in place until actual schema is handed. 70 | */ 71 | public static ProtobufWriteContext createNullContext() { 72 | return null; 73 | } 74 | 75 | public ProtobufWriteContext createChildArrayContext() { 76 | ProtobufWriteContext ctxt = _child; 77 | if (ctxt == null) { 78 | _child = ctxt = new ProtobufWriteContext(TYPE_ARRAY, this, _message); 79 | ctxt._field = _field; 80 | return ctxt; 81 | } 82 | ctxt.reset(TYPE_ARRAY, _message, _field); 83 | return ctxt; 84 | } 85 | 86 | public ProtobufWriteContext createChildObjectContext(ProtobufMessage type) { 87 | ProtobufWriteContext ctxt = _child; 88 | if (ctxt == null) { 89 | _child = ctxt = new ProtobufWriteContext(TYPE_OBJECT, this, type); 90 | return ctxt; 91 | } 92 | ctxt.reset(TYPE_OBJECT, type, null); 93 | return ctxt; 94 | } 95 | 96 | /* 97 | /********************************************************** 98 | /* Simple accessors, mutators 99 | /********************************************************** 100 | */ 101 | 102 | @Override 103 | public final ProtobufWriteContext getParent() { return _parent; } 104 | 105 | @Override 106 | public String getCurrentName() { 107 | return ((_type == TYPE_OBJECT) && (_field != null)) ? _field.name : null; 108 | } 109 | 110 | @Override 111 | public Object getCurrentValue() { 112 | return _currentValue; 113 | } 114 | 115 | @Override 116 | public void setCurrentValue(Object v) { 117 | _currentValue = v; 118 | } 119 | 120 | public void setField(ProtobufField f) { 121 | _field = f; 122 | } 123 | 124 | public ProtobufField getField() { 125 | return _field; 126 | } 127 | 128 | public ProtobufMessage getMessageType() { 129 | return _message; 130 | } 131 | 132 | public boolean notArray() { return _type != TYPE_ARRAY; } 133 | 134 | public StringBuilder appendDesc(StringBuilder sb) { 135 | if (_parent != null) { 136 | sb = _parent.appendDesc(sb); 137 | } 138 | sb.append('/'); 139 | switch (_type) { 140 | case TYPE_OBJECT: 141 | if (_field != null) { 142 | sb.append(_field.name); 143 | } 144 | break; 145 | case TYPE_ARRAY: 146 | sb.append(getCurrentIndex()); 147 | break; 148 | case TYPE_ROOT: 149 | } 150 | return sb; 151 | } 152 | 153 | // // // Overridden standard methods 154 | 155 | /** 156 | * Overridden to provide developer JsonPointer representation 157 | * of the context. 158 | */ 159 | @Override 160 | public final String toString() { 161 | return appendDesc(new StringBuilder(64)).toString(); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/main/java/com/fasterxml/jackson/dataformat/protobuf/schema/EnumLookup.java: -------------------------------------------------------------------------------- 1 | package com.fasterxml.jackson.dataformat.protobuf.schema; 2 | 3 | import java.util.*; 4 | 5 | import com.fasterxml.jackson.core.SerializableString; 6 | 7 | /** 8 | * Helper class used for doing efficient lookups of protoc enums 9 | * given enum name caller provides. Ideally this would be avoided, 10 | * but at this point translation is unfortunately necessary. 11 | */ 12 | public abstract class EnumLookup 13 | { 14 | public static EnumLookup empty() { 15 | return Empty.instance; 16 | } 17 | 18 | public static EnumLookup construct(ProtobufEnum enumDef) 19 | { 20 | Map enumEntries = enumDef.valueMapping(); 21 | if (enumEntries.isEmpty()) { // can this occur? 22 | return Empty.instance; 23 | } 24 | List> l = new ArrayList>(); 25 | for (Map.Entry entry : enumDef.valueMapping().entrySet()) { 26 | l.add(entry); 27 | } 28 | switch (l.size()) { 29 | case 1: 30 | return new Small1(l.get(0).getKey(), l.get(0).getValue()); 31 | case 2: 32 | return new Small2(l.get(0).getKey(), l.get(0).getValue(), 33 | l.get(1).getKey(), l.get(1).getValue()); 34 | case 3: 35 | return new Small3(l.get(0).getKey(), l.get(0).getValue(), 36 | l.get(1).getKey(), l.get(1).getValue(), 37 | l.get(2).getKey(), l.get(2).getValue()); 38 | } 39 | 40 | // General-purpose "big" one needed: 41 | return Big.construct(l); 42 | } 43 | 44 | public abstract String findEnumByIndex(int index); 45 | 46 | public abstract int findEnumIndex(SerializableString key); 47 | 48 | public abstract int findEnumIndex(String key); 49 | 50 | public abstract Collection getEnumValues(); 51 | 52 | static class Empty extends EnumLookup { 53 | public final static Empty instance = new Empty(); 54 | 55 | private Empty() { 56 | super(); 57 | } 58 | 59 | @Override 60 | public int findEnumIndex(SerializableString key) { 61 | return -1; 62 | } 63 | 64 | @Override 65 | public int findEnumIndex(String key) { 66 | return -1; 67 | } 68 | 69 | @Override 70 | public Collection getEnumValues() { 71 | return Collections.emptySet(); 72 | } 73 | 74 | @Override 75 | public String findEnumByIndex(int index) { 76 | return null; 77 | } 78 | } 79 | 80 | static class Small1 extends EnumLookup 81 | { 82 | protected final String key1; 83 | protected final int index1; 84 | 85 | private Small1(String key, int index) { 86 | key1 = key; 87 | index1 = index; 88 | } 89 | 90 | @Override 91 | public String findEnumByIndex(int index) { 92 | if (index == index1) { 93 | return key1; 94 | } 95 | return null; 96 | } 97 | 98 | @Override 99 | public int findEnumIndex(SerializableString key) { 100 | if (key1.equals(key.getValue())) { 101 | return index1; 102 | } 103 | return -1; 104 | } 105 | 106 | @Override 107 | public int findEnumIndex(String key) { 108 | if (key1.equals(key)) { 109 | return index1; 110 | } 111 | return -1; 112 | } 113 | 114 | @Override 115 | public Collection getEnumValues() { 116 | return Collections.singletonList(key1); 117 | } 118 | } 119 | 120 | final static class Small2 extends EnumLookup 121 | { 122 | protected final String key1, key2; 123 | protected final int index1, index2; 124 | 125 | private Small2(String k1, int i1, String k2, int i2) { 126 | key1 = k1; 127 | index1 = i1; 128 | key2 = k2; 129 | index2 = i2; 130 | } 131 | 132 | @Override 133 | public String findEnumByIndex(int index) { 134 | if (index == index1) { 135 | return key1; 136 | } 137 | if (index == index2) { 138 | return key2; 139 | } 140 | return null; 141 | } 142 | 143 | @Override 144 | public int findEnumIndex(SerializableString key0) { 145 | String key = key0.getValue(); 146 | // should be canonical so check equals first 147 | if (key1 == key) { 148 | return index1; 149 | } 150 | if (key2 == key) { 151 | return index2; 152 | } 153 | if (key1.equals(key)) { 154 | return index1; 155 | } 156 | if (key2.equals(key)) { 157 | return index2; 158 | } 159 | return -1; 160 | } 161 | 162 | @Override 163 | public int findEnumIndex(String key) { 164 | // should be canonical so check equals first 165 | if (key1 == key) { 166 | return index1; 167 | } 168 | if (key2 == key) { 169 | return index2; 170 | } 171 | if (key1.equals(key)) { 172 | return index1; 173 | } 174 | if (key2.equals(key)) { 175 | return index2; 176 | } 177 | return -1; 178 | } 179 | 180 | @Override 181 | public Collection getEnumValues() { 182 | return Arrays.asList(key1, key2); 183 | } 184 | } 185 | 186 | final static class Small3 extends EnumLookup 187 | { 188 | protected final String key1, key2, key3; 189 | protected final int index1, index2, index3; 190 | 191 | private Small3(String k1, int i1, String k2, int i2, String k3, int i3) { 192 | key1 = k1; 193 | index1 = i1; 194 | key2 = k2; 195 | index2 = i2; 196 | key3 = k3; 197 | index3 = i3; 198 | } 199 | 200 | @Override 201 | public String findEnumByIndex(int index) { 202 | if (index == index1) { 203 | return key1; 204 | } 205 | if (index == index2) { 206 | return key2; 207 | } 208 | if (index == index3) { 209 | return key3; 210 | } 211 | return null; 212 | } 213 | 214 | @Override 215 | public int findEnumIndex(SerializableString key0) { 216 | String key = key0.getValue(); 217 | // should be canonical so check equals first 218 | if (key1 == key) { 219 | return index1; 220 | } 221 | if (key2 == key) { 222 | return index2; 223 | } 224 | if (key3 == key) { 225 | return index3; 226 | } 227 | return _findIndex2(key); 228 | } 229 | 230 | @Override 231 | public int findEnumIndex(String key) { 232 | if (key1 == key) { 233 | return index1; 234 | } 235 | if (key2 == key) { 236 | return index2; 237 | } 238 | if (key3 == key) { 239 | return index3; 240 | } 241 | return _findIndex2(key); 242 | } 243 | 244 | @Override 245 | public Collection getEnumValues() { 246 | return Arrays.asList(key1, key2, key3); 247 | } 248 | 249 | private int _findIndex2(String key) { 250 | if (key1.equals(key)) { 251 | return index1; 252 | } 253 | if (key2.equals(key)) { 254 | return index2; 255 | } 256 | if (key3.equals(key)) { 257 | return index3; 258 | } 259 | return -1; 260 | } 261 | } 262 | 263 | /** 264 | * Raw mapping from keys to indices, optimized for fast access via 265 | * better memory efficiency. Hash area divide in three; main hash, 266 | * half-size secondary, followed by as-big-as-needed spillover. 267 | */ 268 | final static class Big extends EnumLookup 269 | { 270 | private final int _hashMask, _spillCount; 271 | 272 | private final String[] _keys; 273 | private final int[] _indices; 274 | 275 | /** 276 | * For fields of type {@link FieldType#ENUM} with non-standard indexing, 277 | * mapping back from tag ids to enum names. 278 | */ 279 | protected final LinkedHashMap _enumsById; 280 | 281 | private Big(LinkedHashMap byId, 282 | int hashMask, int spillCount, String[] keys, int[] indices) 283 | { 284 | _enumsById = byId; 285 | _hashMask = hashMask; 286 | _spillCount = spillCount; 287 | _keys = keys; 288 | _indices = indices; 289 | } 290 | 291 | public static Big construct(List> entries) 292 | { 293 | LinkedHashMap byId = new LinkedHashMap(); 294 | 295 | // First: calculate size of primary hash area 296 | final int size = findSize(byId.size()); 297 | final int mask = size-1; 298 | // and allocate enough to contain primary/secondary, expand for spillovers as need be 299 | int alloc = size + (size>>1); 300 | String[] keys = new String[alloc]; 301 | int[] indices = new int[alloc]; 302 | int spills = 0; 303 | 304 | for (Map.Entry entry : entries) { 305 | String key = entry.getKey(); 306 | int index = entry.getValue().intValue(); 307 | 308 | byId.put(entry.getValue(), key); 309 | 310 | int slot = key.hashCode() & mask; 311 | 312 | // primary slot not free? 313 | if (keys[slot] != null) { 314 | // secondary? 315 | slot = size + (slot >> 1); 316 | if (keys[slot] != null) { 317 | // ok, spill over. 318 | slot = size + (size >> 1) + spills; 319 | ++spills; 320 | if (slot >= keys.length) { 321 | keys = Arrays.copyOf(keys, keys.length + 4); 322 | indices = Arrays.copyOf(indices, indices.length + 4); 323 | } 324 | } 325 | } 326 | keys[slot] = key; 327 | indices[slot] = index; 328 | } 329 | return new Big(byId, mask, spills, keys, indices); 330 | } 331 | 332 | @Override 333 | public String findEnumByIndex(int index) { 334 | return _enumsById.get(index); 335 | } 336 | 337 | @Override 338 | public int findEnumIndex(SerializableString key0) { 339 | final String key = key0.getValue(); 340 | int slot = key.hashCode() & _hashMask; 341 | String match = _keys[slot]; 342 | if ((match == key) || key.equals(match)) { 343 | return _indices[slot]; 344 | } 345 | if (match == null) { 346 | return -1; 347 | } 348 | // no? secondary? 349 | slot = (_hashMask+1) + (slot>>1); 350 | match = _keys[slot]; 351 | if ((match == key) || key.equals(match)) { 352 | return _indices[slot]; 353 | } 354 | // or spill? 355 | if (_spillCount > 0) { 356 | return _findFromSpill(key); 357 | } 358 | return -1; 359 | } 360 | 361 | @Override 362 | public int findEnumIndex(String key) { 363 | int slot = key.hashCode() & _hashMask; 364 | String match = _keys[slot]; 365 | if ((match == key) || key.equals(match)) { 366 | return _indices[slot]; 367 | } 368 | if (match == null) { 369 | return -1; 370 | } 371 | // no? secondary? 372 | slot = (_hashMask+1) + (slot>>1); 373 | match = _keys[slot]; 374 | if ((match == key) || key.equals(match)) { 375 | return _indices[slot]; 376 | } 377 | // or spill? 378 | if (_spillCount > 0) { 379 | return _findFromSpill(key); 380 | } 381 | return -1; 382 | } 383 | 384 | @Override 385 | public Collection getEnumValues() { 386 | List result = new ArrayList(_hashMask+1); 387 | for (String str : _keys) { 388 | if (str != null) { 389 | result.add(str); 390 | } 391 | } 392 | return result; 393 | } 394 | 395 | private final static int findSize(int size) 396 | { 397 | if (size <= 5) { 398 | return 8; 399 | } 400 | if (size <= 12) { 401 | return 16; 402 | } 403 | int needed = size + (size >> 2); // at most 80% full 404 | int result = 32; 405 | while (result < needed) { 406 | result += result; 407 | } 408 | return result; 409 | } 410 | 411 | private int _findFromSpill(String key) { 412 | int hashSize = _hashMask+1; 413 | int i = hashSize + (hashSize>>1); 414 | for (int end = i + _spillCount; i < end; ++i) { 415 | if (key.equals(_keys[i])) { 416 | return _indices[i]; 417 | } 418 | } 419 | return -1; 420 | } 421 | } 422 | } 423 | -------------------------------------------------------------------------------- /src/main/java/com/fasterxml/jackson/dataformat/protobuf/schema/FieldLookup.java: -------------------------------------------------------------------------------- 1 | package com.fasterxml.jackson.dataformat.protobuf.schema; 2 | 3 | import java.util.*; 4 | 5 | /** 6 | * Helper class used for cases where {@link ProtobufField} instances 7 | * need to be looked up by name. Basically a more specialized Map 8 | * implementation. 9 | */ 10 | public abstract class FieldLookup 11 | { 12 | public static FieldLookup empty() { 13 | return Empty.instance; 14 | } 15 | 16 | public static FieldLookup construct(ProtobufField[] fields) 17 | { 18 | if (fields.length == 0) { // can this occur? 19 | return Empty.instance; 20 | } 21 | switch (fields.length) { 22 | case 1: 23 | return new Small1(fields[0]); 24 | case 2: 25 | return new Small2(fields[0], fields[1]); 26 | case 3: 27 | return new Small3(fields[0], fields[1], fields[2]); 28 | } 29 | // General-purpose "big" one needed: 30 | return Big.construct(fields); 31 | } 32 | 33 | public abstract ProtobufField findField(String key); 34 | 35 | static class Empty extends FieldLookup { 36 | public final static Empty instance = new Empty(); 37 | 38 | private Empty() { 39 | super(); 40 | } 41 | 42 | @Override 43 | public ProtobufField findField(String key) { 44 | return null; 45 | } 46 | } 47 | 48 | static class Small1 extends FieldLookup 49 | { 50 | protected final String key1; 51 | protected final ProtobufField field1; 52 | 53 | private Small1(ProtobufField f) { 54 | key1 = f.name; 55 | field1 = f; 56 | } 57 | 58 | @Override 59 | public ProtobufField findField(String key) { 60 | if (key == key1 || key.equals(key1)) { 61 | return field1; 62 | } 63 | return null; 64 | } 65 | } 66 | 67 | final static class Small2 extends FieldLookup 68 | { 69 | protected final String key1, key2; 70 | protected final ProtobufField field1, field2; 71 | 72 | private Small2(ProtobufField f1, ProtobufField f2) { 73 | key1 = f1.name; 74 | field1 = f1; 75 | key2 = f2.name; 76 | field2 = f2; 77 | } 78 | 79 | @Override 80 | public ProtobufField findField(String key) { 81 | if (key == key1) { 82 | return field1; 83 | } 84 | if (key == key2) { 85 | return field2; 86 | } 87 | if (key.equals(key1)) { 88 | return field1; 89 | } 90 | if (key.equals(key2)) { 91 | return field2; 92 | } 93 | return null; 94 | } 95 | } 96 | 97 | final static class Small3 extends FieldLookup 98 | { 99 | protected final String key1, key2, key3; 100 | protected final ProtobufField field1, field2, field3; 101 | 102 | private Small3(ProtobufField f1, ProtobufField f2, ProtobufField f3) { 103 | key1 = f1.name; 104 | field1 = f1; 105 | key2 = f2.name; 106 | field2 = f2; 107 | key3 = f3.name; 108 | field3 = f3; 109 | } 110 | 111 | @Override 112 | public ProtobufField findField(String key) { 113 | if (key == key1) { 114 | return field1; 115 | } 116 | if (key == key2) { 117 | return field2; 118 | } 119 | if (key == key3) { 120 | return field3; 121 | } 122 | // usually should get interned key, but if not, try equals: 123 | return _find2(key); 124 | } 125 | 126 | private final ProtobufField _find2(String key) { 127 | if (key.equals(key1)) { 128 | return field1; 129 | } 130 | if (key.equals(key2)) { 131 | return field2; 132 | } 133 | if (key.equals(key3)) { 134 | return field3; 135 | } 136 | return null; 137 | } 138 | } 139 | 140 | /** 141 | * Raw mapping from keys to indices, optimized for fast access via 142 | * better memory efficiency. Hash area divide in three; main hash, 143 | * half-size secondary, followed by as-big-as-needed spillover. 144 | */ 145 | final static class Big extends FieldLookup 146 | { 147 | private final int _hashMask, _spillCount; 148 | 149 | private final String[] _keys; 150 | private final ProtobufField[] _fields; 151 | 152 | private Big(int hashMask, int spillCount, String[] keys, ProtobufField[] fields) 153 | { 154 | _hashMask = hashMask; 155 | _spillCount = spillCount; 156 | _keys = keys; 157 | _fields = fields; 158 | } 159 | 160 | public static Big construct(ProtobufField[] allFields) 161 | { 162 | // First: calculate size of primary hash area 163 | final int size = findSize(allFields.length); 164 | final int mask = size-1; 165 | // and allocate enough to contain primary/secondary, expand for spillovers as need be 166 | int alloc = size + (size>>1); 167 | String[] keys = new String[alloc]; 168 | ProtobufField[] fieldHash = new ProtobufField[alloc]; 169 | int spills = 0; 170 | 171 | for (ProtobufField field : allFields) { 172 | String key = field.name; 173 | 174 | int slot = key.hashCode() & mask; 175 | 176 | // primary slot not free? 177 | if (keys[slot] != null) { 178 | // secondary? 179 | slot = size + (slot >> 1); 180 | if (keys[slot] != null) { 181 | // ok, spill over. 182 | slot = size + (size >> 1) + spills; 183 | ++spills; 184 | if (slot >= keys.length) { 185 | keys = Arrays.copyOf(keys, keys.length + 4); 186 | fieldHash = Arrays.copyOf(fieldHash, fieldHash.length + 4); 187 | } 188 | } 189 | } 190 | keys[slot] = key; 191 | fieldHash[slot] = field; 192 | } 193 | return new Big(mask, spills, keys, fieldHash); 194 | } 195 | 196 | @Override 197 | public ProtobufField findField(String key) { 198 | int slot = key.hashCode() & _hashMask; 199 | String match = _keys[slot]; 200 | if ((match == key) || key.equals(match)) { 201 | return _fields[slot]; 202 | } 203 | if (match == null) { 204 | return null; 205 | } 206 | // no? secondary? 207 | slot = (_hashMask+1) + (slot>>1); 208 | match = _keys[slot]; 209 | if ((match == key) || key.equals(match)) { 210 | return _fields[slot]; 211 | } 212 | // or spill? 213 | return _findFromSpill(key); 214 | } 215 | 216 | private final static int findSize(int size) 217 | { 218 | if (size <= 5) { 219 | return 8; 220 | } 221 | if (size <= 12) { 222 | return 16; 223 | } 224 | int needed = size + (size >> 2); // at most 80% full 225 | int result = 32; 226 | while (result < needed) { 227 | result += result; 228 | } 229 | return result; 230 | } 231 | 232 | private ProtobufField _findFromSpill(String key) { 233 | int hashSize = _hashMask+1; 234 | int i = hashSize + (hashSize>>1); 235 | for (int end = i + _spillCount; i < end; ++i) { 236 | if (key.equals(_keys[i])) { 237 | return _fields[i]; 238 | } 239 | } 240 | return null; 241 | } 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /src/main/java/com/fasterxml/jackson/dataformat/protobuf/schema/FieldType.java: -------------------------------------------------------------------------------- 1 | package com.fasterxml.jackson.dataformat.protobuf.schema; 2 | 3 | import java.util.Arrays; 4 | 5 | import com.squareup.protoparser.DataType; 6 | import com.squareup.protoparser.DataType.ScalarType; 7 | 8 | /** 9 | * Set of distinct types parsed from protoc, as unified considering 10 | * that Java makes no distinction between signed and unsigned types. 11 | */ 12 | public enum FieldType 13 | { 14 | /* 15 | enum ScalarType implements DataType { 16 | ANY, 17 | BOOL, 18 | BYTES, 19 | DOUBLE, 20 | FLOAT, 21 | FIXED32, 22 | FIXED64, 23 | INT32, 24 | INT64, 25 | SFIXED32, 26 | SFIXED64, 27 | SINT32, 28 | SINT64, 29 | STRING, 30 | UINT32, 31 | UINT64; 32 | } 33 | */ 34 | 35 | DOUBLE(WireType.FIXED_64BIT, ScalarType.DOUBLE), // fixed-length 64-bit double 36 | FLOAT(WireType.FIXED_32BIT, ScalarType.FLOAT), // fixed-length, 32-bit single precision 37 | VINT32_Z(WireType.VINT, ScalarType.SINT32), // variable length w/ ZigZag, intended as 32-bit 38 | VINT64_Z(WireType.VINT, ScalarType.SINT64), // variable length w/ ZigZag, intended as 64-bit 39 | VINT32_STD(WireType.VINT, ScalarType.INT32, ScalarType.UINT32), // variable length, intended as 32-bit 40 | VINT64_STD(WireType.VINT, ScalarType.INT64, ScalarType.UINT64), // variable length, intended as 64-bit 41 | 42 | FIXINT32(WireType.FIXED_32BIT, ScalarType.FIXED32, ScalarType.SFIXED32), // fixed length, 32-bit int 43 | FIXINT64(WireType.FIXED_64BIT, ScalarType.FIXED64, ScalarType.SFIXED64), // fixed length, 64-bit int 44 | BOOLEAN(WireType.VINT, ScalarType.BOOL), 45 | STRING(WireType.LENGTH_PREFIXED, ScalarType.STRING), 46 | BYTES(WireType.LENGTH_PREFIXED, ScalarType.BYTES), // byte array 47 | ENUM(WireType.VINT), // encoded as vint 48 | MESSAGE(WireType.LENGTH_PREFIXED) // object 49 | ; 50 | 51 | private final int _wireType; 52 | 53 | private final DataType.ScalarType[] _aliases; 54 | 55 | private FieldType(int wt, DataType.ScalarType... aliases) { 56 | _wireType = wt; 57 | _aliases = aliases; 58 | } 59 | 60 | public int getWireType() { return _wireType; } 61 | 62 | public boolean usesZigZag() { 63 | return (this == VINT32_Z) || (this == VINT64_Z); 64 | } 65 | 66 | public Iterable< DataType.ScalarType> aliases() { 67 | return Arrays.asList(_aliases); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/fasterxml/jackson/dataformat/protobuf/schema/FieldTypes.java: -------------------------------------------------------------------------------- 1 | package com.fasterxml.jackson.dataformat.protobuf.schema; 2 | 3 | import java.util.*; 4 | 5 | import com.squareup.protoparser.DataType; 6 | 7 | public class FieldTypes 8 | { 9 | private final static FieldTypes instance = new FieldTypes(); 10 | 11 | private final EnumMap _types; 12 | 13 | private FieldTypes() 14 | { 15 | _types = new EnumMap(DataType.ScalarType.class); 16 | // Note: since ENUM and MESSAGE have no aliases, they won't be mapped here 17 | for (FieldType type : FieldType.values()) { 18 | for (DataType.ScalarType id : type.aliases()) { 19 | _types.put(id, type); 20 | } 21 | } 22 | } 23 | 24 | public static FieldType findType(DataType rawType) { 25 | return instance._findType(rawType); 26 | } 27 | 28 | private FieldType _findType(DataType rawType) { 29 | if (rawType instanceof DataType.ScalarType) { 30 | return instance._types.get(rawType); 31 | } 32 | return null; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/fasterxml/jackson/dataformat/protobuf/schema/NativeProtobufSchema.java: -------------------------------------------------------------------------------- 1 | package com.fasterxml.jackson.dataformat.protobuf.schema; 2 | 3 | import java.util.*; 4 | 5 | import com.squareup.protoparser.*; 6 | 7 | /** 8 | * Helper class used for wrapping a "raw" protobuf schema (as read by 9 | * "protoparser" library); and used 10 | * as input for creating specific {@link ProtobufSchema} to use for 11 | * reading/writing protobuf encoded content 12 | */ 13 | public class NativeProtobufSchema 14 | { 15 | protected final String _name; 16 | protected final Collection _nativeTypes; 17 | 18 | protected volatile String[] _messageNames; 19 | 20 | protected NativeProtobufSchema(ProtoFile input) 21 | { 22 | this(input.filePath(), input.typeElements()); 23 | } 24 | 25 | protected NativeProtobufSchema(String name, Collection types) 26 | { 27 | _name = name; 28 | _nativeTypes = types; 29 | } 30 | 31 | public static NativeProtobufSchema construct(ProtoFile input) { 32 | return new NativeProtobufSchema(input); 33 | } 34 | 35 | public static NativeProtobufSchema construct(String name, Collection types) { 36 | return new NativeProtobufSchema(name, types); 37 | } 38 | 39 | /** 40 | * Method for checking whether specified message type is defined by 41 | * the native schema 42 | */ 43 | public boolean hasMessageType(String messageTypeName) 44 | { 45 | for (TypeElement type : _nativeTypes) { 46 | if (messageTypeName.equals(type.name())) { 47 | if (type instanceof MessageElement) { 48 | return true; 49 | } 50 | } 51 | } 52 | return false; 53 | } 54 | 55 | /** 56 | * Factory method for constructing Jackson-digestible schema using specified Message type 57 | * from native protobuf schema. 58 | */ 59 | public ProtobufSchema forType(String messageTypeName) 60 | { 61 | MessageElement msg = _messageType(messageTypeName); 62 | if (msg == null) { 63 | throw new IllegalArgumentException("Protobuf schema definition (name '"+_name 64 | +"') has no message type with name '"+messageTypeName+"': known types: " 65 | +getMessageNames()); 66 | } 67 | return new ProtobufSchema(this, TypeResolver.construct(_nativeTypes).resolve(msg)); 68 | } 69 | 70 | /** 71 | * Factory method for constructing Jackson-digestible schema using the first 72 | * Message type defined in the underlying native protobuf schema. 73 | */ 74 | public ProtobufSchema forFirstType() 75 | { 76 | MessageElement msg = _firstMessageType(); 77 | if (msg == null) { 78 | throw new IllegalArgumentException("Protobuf schema definition (name '"+_name 79 | +"') contains no message type definitions"); 80 | } 81 | return new ProtobufSchema(this, TypeResolver.construct(_nativeTypes).resolve(msg)); 82 | } 83 | 84 | public List getMessageNames() { 85 | if (_messageNames == null) { 86 | _messageNames = _getMessageNames(); 87 | } 88 | return Arrays.asList(_messageNames); 89 | } 90 | 91 | @Override 92 | public String toString() { 93 | return toString(_name); 94 | } 95 | 96 | public String toString(String name) { 97 | ProtoFile.Builder builder = ProtoFile.builder(name); 98 | builder.addTypes(_nativeTypes); 99 | return builder.build().toSchema(); 100 | } 101 | 102 | /* 103 | /********************************************************** 104 | /* Helper methods 105 | /********************************************************** 106 | */ 107 | 108 | protected MessageElement _firstMessageType() { 109 | for (TypeElement type : _nativeTypes) { 110 | if (type instanceof MessageElement) { 111 | return (MessageElement) type; 112 | } 113 | } 114 | return null; 115 | } 116 | 117 | protected MessageElement _messageType(String name) { 118 | for (TypeElement type : _nativeTypes) { 119 | if ((type instanceof MessageElement) 120 | && name.equals(type.name())) { 121 | return (MessageElement) type; 122 | } 123 | } 124 | return null; 125 | } 126 | 127 | private String[] _getMessageNames() { 128 | ArrayList names = new ArrayList(); 129 | for (TypeElement type : _nativeTypes) { 130 | if (type instanceof MessageElement) { 131 | names.add(type.name()); 132 | } 133 | } 134 | return names.toArray(new String[names.size()]); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/main/java/com/fasterxml/jackson/dataformat/protobuf/schema/ProtobufEnum.java: -------------------------------------------------------------------------------- 1 | package com.fasterxml.jackson.dataformat.protobuf.schema; 2 | 3 | import java.util.Map; 4 | 5 | public class ProtobufEnum 6 | { 7 | protected final String _name; 8 | 9 | protected final Map _valuesByName; 10 | 11 | /** 12 | * Flag that indicates whether mapping from enum value and id is standard or not; 13 | * standard means that first enum has value 0, and all following enums have value 14 | * one bigger than preceding one. 15 | */ 16 | protected final boolean _standardIndexing; 17 | 18 | public ProtobufEnum(String name, Map valuesByName, boolean standardIndexing) 19 | { 20 | _name = name; 21 | _valuesByName = valuesByName; 22 | _standardIndexing = standardIndexing; 23 | } 24 | 25 | public Integer findEnum(String name) { 26 | return _valuesByName.get(name); 27 | } 28 | 29 | public Map valueMapping() { 30 | return _valuesByName; 31 | } 32 | 33 | public boolean usesStandardIndexing() { 34 | return _standardIndexing; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/fasterxml/jackson/dataformat/protobuf/schema/ProtobufField.java: -------------------------------------------------------------------------------- 1 | package com.fasterxml.jackson.dataformat.protobuf.schema; 2 | 3 | import java.util.*; 4 | 5 | import com.fasterxml.jackson.core.SerializableString; 6 | import com.squareup.protoparser.FieldElement; 7 | import com.squareup.protoparser.OptionElement; 8 | 9 | public class ProtobufField 10 | // sorted in increasing order 11 | implements Comparable 12 | { 13 | /** 14 | * Numeric tag, unshifted 15 | */ 16 | public final int id; 17 | 18 | /** 19 | * Combination of numeric tag and 3-bit wire type. 20 | */ 21 | public final int typedTag; 22 | 23 | /** 24 | * Name of field in protoc definition 25 | */ 26 | public final String name; 27 | 28 | public final FieldType type; 29 | 30 | /** 31 | * 3-bit id used on determining details of how values are serialized. 32 | */ 33 | public final int wireType; 34 | 35 | public final boolean required, repeated, packed, deprecated; 36 | public final boolean usesZigZag; 37 | 38 | /** 39 | * For main type of {@link FieldType#MESSAGE}, reference to actual 40 | * message type definition. 41 | */ 42 | protected ProtobufMessage messageType; 43 | 44 | /** 45 | * For fields of type {@link FieldType#ENUM}, mapping from names to ids. 46 | */ 47 | protected final EnumLookup enumValues; 48 | 49 | /** 50 | * Link to next field within message definition; used for efficient traversal. 51 | * Due to inverse construction order need to be assigned after construction; 52 | * but functionally immutable. 53 | */ 54 | public ProtobufField next; 55 | 56 | public final boolean isObject; 57 | 58 | public final boolean isStdEnum; 59 | 60 | public ProtobufField(FieldElement nativeField, FieldType type) { 61 | this(nativeField, type, null, null); 62 | } 63 | 64 | public ProtobufField(FieldElement nativeField, ProtobufMessage msg) { 65 | this(nativeField, FieldType.MESSAGE, msg, null); 66 | } 67 | 68 | public ProtobufField(FieldElement nativeField, ProtobufEnum et) { 69 | this(nativeField, FieldType.ENUM, null, et); 70 | } 71 | 72 | public static ProtobufField unknownField() { 73 | return new ProtobufField(null, FieldType.MESSAGE, null, null); 74 | } 75 | 76 | protected ProtobufField(FieldElement nativeField, FieldType type, 77 | ProtobufMessage msg, ProtobufEnum et) 78 | { 79 | this.type = type; 80 | wireType = type.getWireType(); 81 | usesZigZag = type.usesZigZag(); 82 | if (et == null) { 83 | enumValues = EnumLookup.empty(); 84 | isStdEnum = false; 85 | } else { 86 | enumValues = EnumLookup.construct(et); 87 | isStdEnum = et.usesStandardIndexing(); 88 | } 89 | messageType = msg; 90 | 91 | if (nativeField == null) { // for "unknown" field 92 | typedTag = id = 0; 93 | repeated = required = deprecated = packed = false; 94 | name = "UNKNOWN"; 95 | } else { 96 | id = nativeField.tag(); 97 | typedTag = (id << 3) + wireType; 98 | name = nativeField.name(); 99 | switch (nativeField.label()) { 100 | case REPEATED: 101 | required = false; 102 | repeated = true; 103 | break; 104 | case REQUIRED: 105 | required = true; 106 | repeated = false; 107 | break; 108 | default: 109 | required = repeated = false; 110 | break; 111 | } 112 | /* 08-Apr-2015, tatu: Due to [https://github.com/square/protoparser/issues/90] 113 | * we can't use 'isPacked()' in 3.1.5 (and probably deprecated has same issue); 114 | * let's add a temporary workaround. 115 | */ 116 | packed = _findBooleanOption(nativeField, "packed"); 117 | deprecated = _findBooleanOption(nativeField, "deprecated"); 118 | } 119 | isObject = (type == FieldType.MESSAGE); 120 | } 121 | 122 | private static boolean _findBooleanOption(FieldElement f, String key) 123 | { 124 | for (OptionElement opt : f.options()) { 125 | if (key.equals(opt.name())) { 126 | Object val = opt.value(); 127 | if (val instanceof Boolean) { 128 | return ((Boolean) val).booleanValue(); 129 | } 130 | return "true".equals(String.valueOf(val).trim()); 131 | } 132 | } 133 | return false; 134 | } 135 | 136 | public void assignMessageType(ProtobufMessage msgType) { 137 | if (type != FieldType.MESSAGE) { 138 | throw new IllegalStateException("Can not assign message type for non-message field '"+name+"'"); 139 | } 140 | messageType = msgType; 141 | } 142 | 143 | public void assignNext(ProtobufField n) { 144 | if (this.next != null) { 145 | throw new IllegalStateException("Can not overwrite 'next' after being set"); 146 | } 147 | this.next = n; 148 | } 149 | 150 | public final ProtobufMessage getMessageType() { 151 | return messageType; 152 | } 153 | 154 | public final ProtobufField nextOrThisIf(int idToMatch) { 155 | if ((next != null) && (next.id == idToMatch)) { 156 | return next; 157 | } 158 | // or maybe we actually have the id? 159 | if (idToMatch == id) { 160 | return this; 161 | } 162 | return null; 163 | } 164 | 165 | public final ProtobufField nextIf(String nameToMatch) { 166 | if (next != null) { 167 | if ((nameToMatch == next.name) || nameToMatch.equals(next.name)) { 168 | return next; 169 | } 170 | } 171 | return null; 172 | } 173 | 174 | public final int findEnumIndex(SerializableString key) { 175 | return enumValues.findEnumIndex(key); 176 | } 177 | 178 | public final int findEnumIndex(String key) { 179 | return enumValues.findEnumIndex(key); 180 | } 181 | public final String findEnumByIndex(int index) { 182 | return enumValues.findEnumByIndex(index); 183 | } 184 | 185 | public Collection getEnumValues() { 186 | return enumValues.getEnumValues(); 187 | } 188 | 189 | public final boolean isArray() { 190 | return repeated; 191 | } 192 | 193 | public final boolean isValidFor(int typeTag) { 194 | return (typeTag == type.getWireType()); 195 | } 196 | 197 | @Override 198 | public String toString() // for debugging 199 | { 200 | return "Field '"+name+"', tag="+typedTag+", wireType="+wireType+", fieldType="+type; 201 | } 202 | 203 | @Override 204 | public int compareTo(ProtobufField other) { 205 | return id - other.id; 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /src/main/java/com/fasterxml/jackson/dataformat/protobuf/schema/ProtobufMessage.java: -------------------------------------------------------------------------------- 1 | package com.fasterxml.jackson.dataformat.protobuf.schema; 2 | 3 | import java.util.*; 4 | 5 | import com.fasterxml.jackson.core.SerializableString; 6 | 7 | public class ProtobufMessage 8 | { 9 | private final static ProtobufField[] NO_FIELDS = new ProtobufField[0]; 10 | 11 | // Let's allow reasonable sized lookup arrays 12 | private final static int MAX_FIELD_INDEX_SIZE = 200; 13 | // private final static int[] NO_INTS = new int[0]; 14 | 15 | protected final String _name; 16 | 17 | /** 18 | * Array that contains actual fields, in declaration order. 19 | * Note that while array is assigned in constructor, the contents 20 | * may be lazily added within, but they must be completed 21 | * before {@link #init(ProtobufField)} is called. 22 | */ 23 | protected final ProtobufField[] _fields; 24 | 25 | // note: assigned on init() 26 | protected FieldLookup _fieldsByName; 27 | 28 | /** 29 | * Arrays of fields indexed by id (offset by _idOffset), if 30 | * fields ids are in contiguous (enough) range. 31 | */ 32 | protected ProtobufField[] _fieldsById; 33 | 34 | protected ProtobufField _firstField; 35 | 36 | protected int _idOffset = -1; 37 | 38 | public ProtobufMessage(String name, ProtobufField[] fields) 39 | { 40 | _name = name; 41 | _fields = fields; 42 | } 43 | 44 | /** 45 | * Method called right after finishing actual construction of this 46 | * message definition. Needed because assignment to fields is dynamic, 47 | * and setup is NOT complete when constructor exits. 48 | */ 49 | public void init(ProtobufField first) 50 | { 51 | _firstField = first; 52 | _fieldsByName = FieldLookup.construct(_fields); 53 | 54 | // Let's see, as well, whether we can create a direct lookup index. 55 | // Note that fields have been sorted by caller already. 56 | int len = _fields.length; 57 | 58 | if (len > 0) { 59 | int firstId = _fields[0].id; 60 | int lastId = _fields[len-1].id; 61 | if (firstId > lastId) { 62 | throw new IllegalStateException("Internal error: first id ("+firstId+") > last id (" 63 | +lastId+")"); 64 | } 65 | int size = lastId - firstId + 1; 66 | if (size <= MAX_FIELD_INDEX_SIZE) { 67 | _idOffset = firstId; 68 | _fieldsById = new ProtobufField[size]; 69 | for (ProtobufField f : _fields) { 70 | // another sanity check for fun 71 | int index = f.id - _idOffset; 72 | if (_fieldsById[index] != null) { 73 | throw new IllegalStateException("Internal error: collision for message of type '" 74 | +_name+"' for id "+f.id); 75 | } 76 | _fieldsById[index] = f; 77 | } 78 | } 79 | } 80 | } 81 | 82 | public static ProtobufMessage bogusMessage(String desc) { 83 | ProtobufMessage bogus = new ProtobufMessage(desc, NO_FIELDS); 84 | bogus.init(null); 85 | return bogus; 86 | } 87 | 88 | public ProtobufField firstField() { return _firstField; } 89 | 90 | public ProtobufField firstIf(String name) { 91 | ProtobufField f = _firstField; 92 | if (f != null && name.equals(f.name)) { 93 | return f; 94 | } 95 | // regardless, find the field 96 | return _fieldsByName.findField(name); 97 | } 98 | 99 | public int getFieldCount() { return _fields.length; } 100 | 101 | public String getName() { return _name; } 102 | 103 | public ProtobufField field(String name) { 104 | return _fieldsByName.findField(name); 105 | } 106 | 107 | // !!! TODO: optimize? 108 | public ProtobufField field(int id) 109 | { 110 | // Can we just index it? 111 | int idOffset = _idOffset; 112 | if (idOffset >= 0) { 113 | return _fieldsById[id - idOffset]; 114 | } 115 | // if not, brute force works 116 | for (int i = 0, len = _fields.length; i < len; ++i) { 117 | ProtobufField f = _fields[i]; 118 | if (f.id == id) { 119 | return f; 120 | } 121 | } 122 | // not found? that's ok with us, but caller may mind 123 | return null; 124 | } 125 | 126 | public ProtobufField field(SerializableString name) { 127 | return _fieldsByName.findField(name.getValue()); 128 | } 129 | 130 | public String fieldsAsString() { 131 | return Arrays.asList(_fields).toString(); 132 | } 133 | 134 | public Iterable fields() { 135 | return Arrays.asList(_fields); 136 | } 137 | } -------------------------------------------------------------------------------- /src/main/java/com/fasterxml/jackson/dataformat/protobuf/schema/ProtobufSchema.java: -------------------------------------------------------------------------------- 1 | package com.fasterxml.jackson.dataformat.protobuf.schema; 2 | 3 | import java.util.List; 4 | 5 | import com.fasterxml.jackson.core.FormatSchema; 6 | 7 | /** 8 | * A {@link FormatSchema} implementation for protobuf, bound to specific root-level 9 | * {@link ProtobufMessage}, and useful for reading/writing protobuf content 10 | * that encodes instance of that message. 11 | */ 12 | public class ProtobufSchema implements FormatSchema 13 | { 14 | public final static String FORMAT_NAME_PROTOBUF = "protobuf"; 15 | 16 | /** 17 | * In case we want to use a different root type, we'll also hold 18 | * a reference to the native definition, if one is available. 19 | * Note that it may be possible to construct instances directly, 20 | * in which case this would be `null`. 21 | */ 22 | protected final NativeProtobufSchema _source; 23 | 24 | protected final ProtobufMessage _rootType; 25 | 26 | /* 27 | /********************************************************** 28 | /* Construction 29 | /********************************************************** 30 | */ 31 | 32 | public ProtobufSchema(NativeProtobufSchema src, ProtobufMessage rootType) { 33 | _source = src; 34 | _rootType = rootType; 35 | } 36 | 37 | /** 38 | * Method that can be called to choose different root type (of types 39 | * defined in protoc); a new schema instance will be constructed 40 | * if type is different from current root type. 41 | *

42 | * Note that cost of changing root type is non-trivial in that traversal 43 | * of types defined is needed -- but exact cost depends on number of types 44 | * defined. Since schema instances are immutable, it makes sense to try to 45 | * reuse instances if possible. 46 | * 47 | * @throws IllegalArgumentException If no type with specified name is found 48 | * from within this schema. 49 | */ 50 | public ProtobufSchema withRootType(String typeName) 51 | throws IllegalArgumentException 52 | { 53 | if (_rootType.getName().equals(typeName)) { 54 | return this; 55 | } 56 | return _source.forType(typeName); 57 | } 58 | 59 | /* 60 | /********************************************************** 61 | /* API 62 | /********************************************************** 63 | */ 64 | 65 | /** 66 | * Accessor for native representation of the protoc. 67 | * Mostly useful for debugging; application code should not need to 68 | * access this representation during normal operation. 69 | */ 70 | public NativeProtobufSchema getSource() { 71 | return _source; 72 | } 73 | 74 | /** 75 | * Accessor for getting the default {@link ProtobufMessage} type that 76 | * is usually the root type for this schema. 77 | */ 78 | public ProtobufMessage getRootType() { 79 | return _rootType; 80 | } 81 | 82 | /** 83 | * Accessor for listing names of all root-level messages defined in the 84 | * original protoc. 85 | */ 86 | public List getMessageTypes() { 87 | return _source.getMessageNames(); 88 | } 89 | 90 | /** 91 | * Accessor to get type id for this {@link FormatSchema}, used by code Jackson 92 | * databinding functionality. Not usually needed by application developers. 93 | */ 94 | @Override 95 | public String getSchemaType() { 96 | return FORMAT_NAME_PROTOBUF; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/fasterxml/jackson/dataformat/protobuf/schema/ProtobufSchemaLoader.java: -------------------------------------------------------------------------------- 1 | package com.fasterxml.jackson.dataformat.protobuf.schema; 2 | 3 | import java.io.*; 4 | import java.net.URL; 5 | import java.nio.charset.Charset; 6 | 7 | import com.squareup.protoparser.ProtoFile; 8 | import com.squareup.protoparser.ProtoParser; 9 | 10 | /** 11 | * Class used for loading protobuf definitions (from .proto files 12 | * or equivalent sources), to construct schema needed for reading 13 | * or writing content. 14 | *

15 | * Note that message name argument is optional if (and only if) desired 16 | * root type is the first Message type in definition; otherwise an 17 | * exception will be thrown. 18 | */ 19 | public class ProtobufSchemaLoader 20 | implements java.io.Serializable // since mapper has a reference 21 | { 22 | private static final long serialVersionUID = 1L; 23 | 24 | private final static Charset UTF8 = Charset.forName("UTF-8"); 25 | 26 | public final static String DEFAULT_SCHEMA_NAME = "Unnamed-protobuf-schema"; 27 | 28 | /** 29 | * Standard loader instance that is usually used for loading protoc 30 | * schemas. 31 | */ 32 | public final static ProtobufSchemaLoader std = new ProtobufSchemaLoader(); 33 | 34 | public ProtobufSchemaLoader() { } 35 | 36 | /* 37 | /********************************************************** 38 | /* Public API 39 | /********************************************************** 40 | */ 41 | 42 | public ProtobufSchema load(URL url) throws IOException { 43 | return loadNative(url).forFirstType(); 44 | } 45 | 46 | /** 47 | * @param rootTypeName Name of message type in schema definition that is 48 | * the root value to read/write 49 | */ 50 | public ProtobufSchema load(URL url, String rootTypeName) throws IOException { 51 | return loadNative(url).forType(rootTypeName); 52 | } 53 | 54 | public ProtobufSchema load(File f) throws IOException { 55 | return loadNative(f).forFirstType(); 56 | } 57 | 58 | /** 59 | * @param rootTypeName Name of message type in schema definition that is 60 | * the root value to read/write 61 | */ 62 | public ProtobufSchema load(File f, String rootTypeName) throws IOException { 63 | return loadNative(f).forType(rootTypeName); 64 | } 65 | 66 | /** 67 | * Method for loading and parsing a protoc definition from given 68 | * stream, assuming UTF-8 encoding. 69 | * Note that given {@link InputStream} will be closed before method returns. 70 | */ 71 | public ProtobufSchema load(InputStream in) throws IOException { 72 | return loadNative(in, true).forFirstType(); 73 | } 74 | 75 | /** 76 | * @param rootTypeName Name of message type in schema definition that is 77 | * the root value to read/write 78 | */ 79 | public ProtobufSchema load(InputStream in, String rootTypeName) throws IOException { 80 | return loadNative(in, true).forType(rootTypeName); 81 | } 82 | 83 | /** 84 | * Method for loading and parsing a protoc definition from given 85 | * stream, assuming UTF-8 encoding. 86 | * Note that given {@link Reader} will be closed before method returns. 87 | */ 88 | public ProtobufSchema load(Reader r) throws IOException { 89 | return loadNative(r, true).forFirstType(); 90 | } 91 | 92 | /** 93 | * @param rootTypeName Name of message type in schema definition that is 94 | * the root value to read/write 95 | */ 96 | public ProtobufSchema load(Reader r, String rootTypeName) throws IOException { 97 | return loadNative(r, true).forType(rootTypeName); 98 | } 99 | 100 | /** 101 | * Method for parsing given protoc schema definition, constructing 102 | * schema object Jackson can use. 103 | */ 104 | public ProtobufSchema parse(String schemaAsString) throws IOException { 105 | return parseNative(schemaAsString).forFirstType(); 106 | } 107 | 108 | /** 109 | * @param rootTypeName Name of message type in schema definition that is 110 | * the root value to read/write 111 | */ 112 | public ProtobufSchema parse(String schemaAsString, String rootTypeName) throws IOException { 113 | return parseNative(schemaAsString).forType(rootTypeName); 114 | } 115 | 116 | /* 117 | /********************************************************** 118 | /* Loading native schema instances 119 | /********************************************************** 120 | */ 121 | 122 | public NativeProtobufSchema loadNative(File f) throws IOException { 123 | return NativeProtobufSchema.construct(_loadNative(f)); 124 | } 125 | 126 | public NativeProtobufSchema loadNative(URL url) throws IOException { 127 | return NativeProtobufSchema.construct(_loadNative(url)); 128 | } 129 | 130 | public NativeProtobufSchema parseNative(String schema) throws IOException { 131 | return NativeProtobufSchema.construct(_loadNative(schema)); 132 | } 133 | 134 | public NativeProtobufSchema loadNative(InputStream in, boolean close) throws IOException { 135 | return NativeProtobufSchema.construct(_loadNative(in, close)); 136 | } 137 | 138 | protected NativeProtobufSchema loadNative(Reader r, boolean close) throws IOException { 139 | return NativeProtobufSchema.construct(_loadNative(r, close)); 140 | } 141 | 142 | /* 143 | /********************************************************** 144 | /* Helper methods 145 | /********************************************************** 146 | */ 147 | 148 | public ProtoFile _loadNative(File f) throws IOException { 149 | return ProtoParser.parseUtf8(f); 150 | } 151 | 152 | public ProtoFile _loadNative(URL url) throws IOException { 153 | return _loadNative(url.openStream(), true); 154 | } 155 | 156 | public ProtoFile _loadNative(String schemaAsString) throws IOException { 157 | return ProtoParser.parse(DEFAULT_SCHEMA_NAME, schemaAsString); 158 | } 159 | 160 | public ProtoFile _loadNative(InputStream in, boolean close) throws IOException { 161 | return _loadNative(new InputStreamReader(in, UTF8), close); 162 | } 163 | 164 | protected ProtoFile _loadNative(Reader r, boolean close) throws IOException 165 | { 166 | try { 167 | return ProtoParser.parse(DEFAULT_SCHEMA_NAME, _readAll(r)); 168 | } finally { 169 | if (close) { 170 | try { r.close(); } catch (IOException e) { } 171 | } 172 | } 173 | } 174 | 175 | protected String _readAll(Reader r) throws IOException 176 | { 177 | StringBuilder sb = new StringBuilder(1000); 178 | char[] buffer = new char[1000]; 179 | int count; 180 | 181 | while ((count = r.read(buffer)) > 0) { 182 | sb.append(buffer, 0, count); 183 | } 184 | return sb.toString(); 185 | } 186 | 187 | public void _throw(Exception e0) throws IOException 188 | { 189 | // First, peel it 190 | Throwable e = e0; 191 | while (e.getCause() != null) { 192 | e = e.getCause(); 193 | } 194 | if (e instanceof RuntimeException) { 195 | throw (RuntimeException) e; 196 | } 197 | if (e instanceof IOException){ 198 | throw (IOException) e; 199 | } 200 | throw new IOException(e.getMessage(), e); 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /src/main/java/com/fasterxml/jackson/dataformat/protobuf/schema/TypeResolver.java: -------------------------------------------------------------------------------- 1 | package com.fasterxml.jackson.dataformat.protobuf.schema; 2 | 3 | import java.util.*; 4 | 5 | import com.fasterxml.jackson.core.util.InternCache; 6 | import com.squareup.protoparser.*; 7 | 8 | /** 9 | * Stateful class needed to properly resolve type definitions of 10 | * protobuf message (and related types); some complexity coming 11 | * from possible nested nature of definitions. 12 | */ 13 | public class TypeResolver 14 | { 15 | private final TypeResolver _parent; 16 | 17 | private Map _nativeMessageTypes; 18 | 19 | private Map _enumTypes; 20 | 21 | private Map _resolvedMessageTypes; 22 | 23 | protected TypeResolver(TypeResolver p, Map nativeMsgs, 24 | Map enums) 25 | { 26 | _parent = p; 27 | if (enums == null) { 28 | enums = Collections.emptyMap(); 29 | } 30 | _enumTypes = enums; 31 | if (nativeMsgs == null) { 32 | nativeMsgs = Collections.emptyMap(); 33 | } 34 | _nativeMessageTypes = nativeMsgs; 35 | _resolvedMessageTypes = Collections.emptyMap(); 36 | } 37 | 38 | public static TypeResolver construct(Collection nativeTypes) { 39 | return construct(null, nativeTypes); 40 | } 41 | 42 | protected static TypeResolver construct(TypeResolver parent, Collection nativeTypes) 43 | { 44 | Map nativeMessages = null; 45 | Map enumTypes = null; 46 | 47 | for (TypeElement nt : nativeTypes) { 48 | if (nt instanceof MessageElement) { 49 | if (nativeMessages == null) { 50 | nativeMessages = new LinkedHashMap(); 51 | } 52 | nativeMessages.put(nt.name(), (MessageElement) nt); 53 | } else if (nt instanceof EnumElement) { 54 | if (enumTypes == null) { 55 | enumTypes = new LinkedHashMap(); 56 | } 57 | enumTypes.put(nt.name(), _constructEnum((EnumElement) nt)); 58 | } // no other known types? 59 | } 60 | return new TypeResolver(parent, nativeMessages, enumTypes); 61 | } 62 | 63 | protected static ProtobufEnum _constructEnum(EnumElement nativeEnum) 64 | { 65 | final Map valuesByName = new LinkedHashMap(); 66 | boolean standard = true; 67 | int exp = 0; 68 | 69 | for (EnumConstantElement v : nativeEnum.constants()) { 70 | int id = v.tag(); 71 | if (standard && (id != exp)) { 72 | standard = false; 73 | } 74 | valuesByName.put(v.name(), id); 75 | ++exp; 76 | } 77 | // 17-Mar-2015, tatu: Number of intern()s here should be nominal; 78 | // but intern()ing itself helps in keeping name/id enum translation fast 79 | String name = InternCache.instance.intern(nativeEnum.name()); 80 | return new ProtobufEnum(name, valuesByName, standard); 81 | } 82 | 83 | public ProtobufMessage resolve(MessageElement rawType) 84 | { 85 | ProtobufMessage msg = _findResolvedMessage(rawType.name()); 86 | if (msg != null) { 87 | return msg; 88 | } 89 | /* Since MessageTypes can contain other type definitions, it is 90 | * important that we actually create a new context, that is, 91 | * new TypeResolver instance, and call resolution on that. 92 | */ 93 | return TypeResolver.construct(this, rawType.nestedElements()) 94 | ._resolve(rawType); 95 | } 96 | 97 | protected ProtobufMessage _resolve(MessageElement rawType) 98 | { 99 | List rawFields = rawType.fields(); 100 | ProtobufField[] resolvedFields = new ProtobufField[rawFields.size()]; 101 | 102 | ProtobufMessage message = new ProtobufMessage(rawType.name(), resolvedFields); 103 | // Important: add type itself as (being) resolved, to allow for self-refs: 104 | if (_resolvedMessageTypes.isEmpty()) { 105 | _resolvedMessageTypes = new HashMap(); 106 | } 107 | _resolvedMessageTypes.put(rawType.name(), message); 108 | 109 | // and then resolve fields 110 | int ix = 0; 111 | for (FieldElement f : rawFields) { 112 | final DataType fieldType = f.type(); 113 | // First: could it be we have a simple scalar type 114 | FieldType type = FieldTypes.findType(fieldType); 115 | ProtobufField pbf; 116 | 117 | if (type != null) { // simple type 118 | pbf = new ProtobufField(f, type); 119 | } else if (fieldType instanceof DataType.NamedType) { 120 | final String typeStr = ((DataType.NamedType) fieldType).name(); 121 | 122 | // If not, a resolved local definition? 123 | ProtobufField resolvedF = _findLocalResolved(f, typeStr); 124 | if (resolvedF != null) { 125 | pbf = resolvedF; 126 | } else { 127 | // or, barring that local but as of yet unresolved message? 128 | MessageElement nativeMt = _nativeMessageTypes.get(typeStr); 129 | if (nativeMt != null) { 130 | pbf = new ProtobufField(f, 131 | TypeResolver.construct(this, nativeMt.nestedElements())._resolve(nativeMt)); 132 | } else { 133 | // If not, perhaps parent might have an answer? 134 | resolvedF = _parent._findAnyResolved(f, typeStr); 135 | if (resolvedF != null) { 136 | pbf = resolvedF; 137 | } else { 138 | // Ok, we are out of options here... 139 | StringBuilder enumStr = _knownEnums(new StringBuilder()); 140 | StringBuilder msgStr = _knownMsgs(new StringBuilder()); 141 | throw new IllegalArgumentException(String.format( 142 | "Unknown protobuf field type '%s' for field '%s' of MessageType '%s" 143 | +"' (known enum types: %s; known message types: %s)", 144 | typeStr, f.name(), rawType.name(), enumStr, msgStr)); 145 | } 146 | } 147 | } 148 | } else { 149 | throw new IllegalArgumentException(String.format( 150 | "Unrecognized DataType '%s' for field '%s'", fieldType.getClass().getName(), f.name())); 151 | } 152 | resolvedFields[ix++] = pbf; 153 | } 154 | ProtobufField first = (resolvedFields.length == 0) ? null : resolvedFields[0]; 155 | 156 | // sort field array by index 157 | Arrays.sort(resolvedFields); 158 | 159 | // And then link the fields, to speed up iteration 160 | for (int i = 0, end = resolvedFields.length-1; i < end; ++i) { 161 | resolvedFields[i].assignNext(resolvedFields[i+1]); 162 | } 163 | message.init(first); 164 | return message; 165 | } 166 | 167 | private ProtobufMessage _findResolvedMessage(String typeStr) 168 | { 169 | ProtobufMessage msg = _resolvedMessageTypes.get(typeStr); 170 | if ((msg == null) && (_parent !=null)) { 171 | return _parent._findResolvedMessage(typeStr); 172 | } 173 | return msg; 174 | } 175 | 176 | private ProtobufField _findAnyResolved(FieldElement nativeField, String typeStr) 177 | { 178 | ProtobufField f = _findLocalResolved(nativeField, typeStr); 179 | if (f == null) { 180 | MessageElement nativeMt = _nativeMessageTypes.get(typeStr); 181 | if (nativeMt != null) { 182 | return new ProtobufField(nativeField, 183 | TypeResolver.construct(this, nativeMt.nestedElements())._resolve(nativeMt)); 184 | } 185 | if (_parent != null) { 186 | return _parent._findAnyResolved(nativeField, typeStr); 187 | } 188 | } 189 | return f; 190 | } 191 | 192 | private StringBuilder _knownEnums(StringBuilder sb) { 193 | if (_parent != null) { 194 | sb = _parent._knownEnums(sb); 195 | } 196 | for (String name : _enumTypes.keySet()) { 197 | if (sb.length() > 0) { 198 | sb.append(", "); 199 | } 200 | sb.append(name); 201 | } 202 | return sb; 203 | } 204 | 205 | private StringBuilder _knownMsgs(StringBuilder sb) { 206 | if (_parent != null) { 207 | sb = _parent._knownMsgs(sb); 208 | } 209 | for (String name : _nativeMessageTypes.keySet()) { 210 | if (sb.length() > 0) { 211 | sb.append(", "); 212 | } 213 | sb.append(name); 214 | } 215 | return sb; 216 | } 217 | 218 | private ProtobufField _findLocalResolved(FieldElement nativeField, String typeStr) 219 | { 220 | ProtobufMessage msg = _resolvedMessageTypes.get(typeStr); 221 | if (msg != null) { 222 | return new ProtobufField(nativeField, msg); 223 | } 224 | ProtobufEnum et = _enumTypes.get(typeStr); 225 | if (et != null) { 226 | return new ProtobufField(nativeField, et); 227 | } 228 | return null; 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /src/main/java/com/fasterxml/jackson/dataformat/protobuf/schema/WireType.java: -------------------------------------------------------------------------------- 1 | package com.fasterxml.jackson.dataformat.protobuf.schema; 2 | 3 | /** 4 | * Enumeration of wire types that protobuf specification defines 5 | */ 6 | public interface WireType 7 | { 8 | public final static int VINT = 0; 9 | public final static int FIXED_64BIT = 1; 10 | public final static int LENGTH_PREFIXED = 2; 11 | public final static int GROUP_START = 3; 12 | public final static int GROUP_END = 4; 13 | public final static int FIXED_32BIT = 5; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/fasterxml/jackson/dataformat/protobuf/schema/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains functionality for generating internal Protobuf schema instances 3 | * from external protoc resources. This is necessary for encoding and decoding 4 | * protobuf content which require schema for operation. 5 | */ 6 | package com.fasterxml.jackson.dataformat.protobuf.schema; 7 | -------------------------------------------------------------------------------- /src/main/java/com/fasterxml/jackson/dataformat/protobuf/schemagen/AnnotationBasedTagGenerator.java: -------------------------------------------------------------------------------- 1 | package com.fasterxml.jackson.dataformat.protobuf.schemagen; 2 | 3 | import com.fasterxml.jackson.databind.BeanProperty; 4 | 5 | public class AnnotationBasedTagGenerator implements TagGenerator 6 | { 7 | @Override 8 | public int nextTag(BeanProperty writer) { 9 | Integer ix = writer.getMetadata().getIndex(); 10 | if (ix != null) { 11 | return ix.intValue(); 12 | } 13 | throw new IllegalStateException("No index metadata found for " + writer.getFullName() 14 | + " (usually annotated with @JsonProperty.index): either annotate all properties of type " + writer.getWrapperName().getSimpleName() + " with indexes or none at all"); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/fasterxml/jackson/dataformat/protobuf/schemagen/DefaultTagGenerator.java: -------------------------------------------------------------------------------- 1 | package com.fasterxml.jackson.dataformat.protobuf.schemagen; 2 | 3 | import com.fasterxml.jackson.databind.BeanProperty; 4 | 5 | public class DefaultTagGenerator implements TagGenerator { 6 | 7 | protected int _tagCounter; 8 | 9 | public DefaultTagGenerator() { 10 | this(1); 11 | } 12 | 13 | public DefaultTagGenerator(int startingTag) { 14 | _tagCounter = startingTag; 15 | } 16 | 17 | @Override 18 | public int nextTag(BeanProperty writer) { 19 | if (ProtobuffSchemaHelper.hasIndex(writer)) { 20 | throw new IllegalStateException(writer.getFullName() 21 | + " is annotated with 'JsonProperty.index', however not all properties of type " 22 | + writer.getWrapperName().getSimpleName() 23 | + " are annotated. Either annotate all properties or none at all."); 24 | } 25 | 26 | return nextTag(); 27 | } 28 | 29 | public int nextTag() { 30 | return _tagCounter++; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/fasterxml/jackson/dataformat/protobuf/schemagen/DefinedTypeElementBuilders.java: -------------------------------------------------------------------------------- 1 | package com.fasterxml.jackson.dataformat.protobuf.schemagen; 2 | 3 | import java.util.Collection; 4 | import java.util.HashSet; 5 | import java.util.LinkedHashMap; 6 | import java.util.Map; 7 | import java.util.Set; 8 | 9 | import com.fasterxml.jackson.databind.JavaType; 10 | 11 | public class DefinedTypeElementBuilders { 12 | 13 | protected Map _definedTypeElementBuilders = new LinkedHashMap<>(); 14 | 15 | protected Set _isNestedType = new HashSet<>(); 16 | 17 | public DefinedTypeElementBuilders() { 18 | } 19 | 20 | public void AddTypeElement(JavaType type, TypeElementBuilder builder, boolean isNested) { 21 | if (_definedTypeElementBuilders.containsKey(type)) { //Type element builder already defined 22 | if (_definedTypeElementBuilders.get(type) != builder) { //Not expect this. 23 | throw new IllegalStateException("Trying to redefine TypeElementBuilder for type " + type); 24 | } 25 | } else { //new builder 26 | _definedTypeElementBuilders.put(type, builder); 27 | } 28 | 29 | if(isNested) { 30 | _isNestedType.add(type); 31 | } 32 | } 33 | 34 | public boolean containsBuilderFor(JavaType type) { 35 | return _definedTypeElementBuilders.containsKey(type); 36 | } 37 | 38 | public TypeElementBuilder getBuilderFor(JavaType type) { 39 | return _definedTypeElementBuilders.get(type); 40 | } 41 | 42 | public Set getAllBuilders() { 43 | return new HashSet(_definedTypeElementBuilders.values()); 44 | } 45 | 46 | public Set getAllNestedBuilders() { 47 | return getAllBuildersFor(_isNestedType); 48 | } 49 | 50 | public Set getDependencyBuilders() { 51 | return getNonNestedBuilders(true); 52 | } 53 | 54 | public Set getNonNestedBuilders() { 55 | return getNonNestedBuilders(false); 56 | } 57 | 58 | public Set getNonNestedBuilders(boolean excludeRoot) { 59 | Set types = _definedTypeElementBuilders.keySet(); //all keys 60 | types.removeAll(_isNestedType); //exclude nested type 61 | 62 | if(excludeRoot) { //exclude root 63 | if(_definedTypeElementBuilders.isEmpty()) { 64 | throw new IllegalStateException("DefinedTypeElementBuilders._definedTypeElementBuilders is empty"); 65 | } 66 | types.remove(_definedTypeElementBuilders.keySet().iterator().next()); //expect the first element is root 67 | } 68 | 69 | return getAllBuildersFor(types); 70 | } 71 | 72 | protected HashSet getAllBuildersFor(Collection types) { 73 | HashSet nestedBuilder = new HashSet<>(); 74 | for (JavaType type : types) { 75 | nestedBuilder.add(getBuilderFor(type)); 76 | } 77 | return nestedBuilder; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/com/fasterxml/jackson/dataformat/protobuf/schemagen/EnumElementVisitor.java: -------------------------------------------------------------------------------- 1 | package com.fasterxml.jackson.dataformat.protobuf.schemagen; 2 | 3 | import java.util.Set; 4 | 5 | import com.fasterxml.jackson.databind.JavaType; 6 | import com.fasterxml.jackson.databind.SerializerProvider; 7 | import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonStringFormatVisitor.Base; 8 | import com.squareup.protoparser.EnumConstantElement; 9 | import com.squareup.protoparser.EnumElement; 10 | import com.squareup.protoparser.TypeElement; 11 | 12 | public class EnumElementVisitor extends Base implements TypeElementBuilder { 13 | 14 | EnumElement.Builder _builder; 15 | 16 | DefaultTagGenerator _tagGenerator = new DefaultTagGenerator(0); 17 | 18 | public EnumElementVisitor(SerializerProvider provider, JavaType type, 19 | DefinedTypeElementBuilders definedTypeElementBuilders, boolean isNested) { 20 | 21 | if (!type.isEnumType()) { 22 | throw new IllegalArgumentException("Expected an enum, however given type is " + type); 23 | } 24 | 25 | _builder = EnumElement.builder(); 26 | _builder.name(type.getRawClass().getSimpleName()); 27 | _builder.documentation("Enum for " + type.toCanonical()); 28 | 29 | definedTypeElementBuilders.AddTypeElement(type, this, isNested); 30 | } 31 | 32 | @Override 33 | public TypeElement build() { 34 | return _builder.build(); 35 | } 36 | 37 | @Override 38 | public void enumTypes(Set enums) { 39 | for (String eName : enums) { 40 | _builder.addConstant(buildEnumConstant(eName)); 41 | } 42 | } 43 | 44 | protected EnumConstantElement buildEnumConstant(String name) { 45 | EnumConstantElement.Builder builder = EnumConstantElement.builder(); 46 | builder.name(name); 47 | builder.tag(_tagGenerator.nextTag()); 48 | return builder.build(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/fasterxml/jackson/dataformat/protobuf/schemagen/MessageElementVisitor.java: -------------------------------------------------------------------------------- 1 | package com.fasterxml.jackson.dataformat.protobuf.schemagen; 2 | 3 | import java.util.Arrays; 4 | import java.util.HashSet; 5 | import java.util.Set; 6 | 7 | import com.fasterxml.jackson.databind.BeanProperty; 8 | import com.fasterxml.jackson.databind.JavaType; 9 | import com.fasterxml.jackson.databind.JsonMappingException; 10 | import com.fasterxml.jackson.databind.SerializerProvider; 11 | import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitable; 12 | import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonObjectFormatVisitor; 13 | import com.squareup.protoparser.DataType; 14 | import com.squareup.protoparser.DataType.NamedType; 15 | import com.squareup.protoparser.DataType.ScalarType; 16 | import com.squareup.protoparser.FieldElement; 17 | import com.squareup.protoparser.FieldElement.Label; 18 | import com.squareup.protoparser.MessageElement; 19 | import com.squareup.protoparser.TypeElement; 20 | 21 | public class MessageElementVisitor extends JsonObjectFormatVisitor.Base implements TypeElementBuilder 22 | { 23 | protected MessageElement.Builder _builder; 24 | 25 | protected TagGenerator _tagGenerator; 26 | 27 | protected JavaType _type; 28 | 29 | protected Set _nestedTypes = new HashSet<>(); 30 | 31 | protected DefinedTypeElementBuilders _definedTypeElementBuilders; 32 | 33 | public MessageElementVisitor(SerializerProvider provider, JavaType type, 34 | DefinedTypeElementBuilders definedTypeElementBuilders, boolean isNested) 35 | { 36 | super(provider); 37 | 38 | _definedTypeElementBuilders = definedTypeElementBuilders; 39 | 40 | _type = type; 41 | 42 | _builder = MessageElement.builder(); 43 | _builder.name(type.getRawClass().getSimpleName()); 44 | _builder.documentation("Message for " + type.toCanonical()); 45 | 46 | _definedTypeElementBuilders.AddTypeElement(type, this, isNested); 47 | } 48 | 49 | @Override 50 | public TypeElement build() { 51 | return _builder.build(); 52 | } 53 | 54 | @Override 55 | public void property(BeanProperty writer) throws JsonMappingException { 56 | FieldElement fElement = buildFieldElement(writer, Label.REQUIRED); 57 | _builder.addField(fElement); 58 | } 59 | 60 | @Override 61 | public void property(String name, JsonFormatVisitable handler, JavaType propertyTypeHint) { } 62 | 63 | @Override 64 | public void optionalProperty(BeanProperty writer) throws JsonMappingException { 65 | FieldElement fElement = buildFieldElement(writer, Label.OPTIONAL); 66 | _builder.addField(fElement); 67 | } 68 | 69 | @Override 70 | public void optionalProperty(String name, JsonFormatVisitable handler, JavaType propertyTypeHint) { } 71 | 72 | protected FieldElement buildFieldElement(BeanProperty writer, Label label) throws JsonMappingException 73 | { 74 | FieldElement.Builder fBuilder = FieldElement.builder(); 75 | 76 | fBuilder.name(writer.getName()); 77 | fBuilder.tag(nextTag(writer)); 78 | 79 | JavaType type = writer.getType(); 80 | 81 | if (type.isArrayType() || type.isCollectionLikeType()) { 82 | fBuilder.label(Label.REPEATED); 83 | fBuilder.type(getDataType(type.getContentType())); 84 | } else { 85 | fBuilder.label(label); 86 | fBuilder.type(getDataType(type)); 87 | } 88 | return fBuilder.build(); 89 | } 90 | 91 | protected int nextTag(BeanProperty writer) { 92 | getTagGenerator(writer); 93 | return _tagGenerator.nextTag(writer); 94 | } 95 | 96 | protected void getTagGenerator(BeanProperty writer) { 97 | if (_tagGenerator == null) { 98 | if (ProtobuffSchemaHelper.hasIndex(writer)) { 99 | _tagGenerator = new AnnotationBasedTagGenerator(); 100 | } else { 101 | _tagGenerator = new DefaultTagGenerator(); 102 | } 103 | } 104 | } 105 | 106 | protected DataType getDataType(JavaType type) throws JsonMappingException { 107 | ScalarType sType = ProtobuffSchemaHelper.getScalarType(type); 108 | if (sType != null) { // Is scalar type ref 109 | return sType; 110 | } 111 | 112 | if (!_definedTypeElementBuilders.containsBuilderFor(type)) { // No self ref 113 | if (Arrays.asList(_type.getRawClass().getDeclaredClasses()).contains(type.getRawClass())) { // nested 114 | if (!_nestedTypes.contains(type)) { // create nested type 115 | _nestedTypes.add(type); 116 | TypeElementBuilder nestedTypeBuilder = ProtobuffSchemaHelper.acceptTypeElement(_provider, type, 117 | _definedTypeElementBuilders, true); 118 | 119 | _builder.addType(nestedTypeBuilder.build()); 120 | } 121 | } else { // tracking non-nested types to generate them later 122 | ProtobuffSchemaHelper.acceptTypeElement(_provider, type, _definedTypeElementBuilders, false); 123 | } 124 | } 125 | return NamedType.create(type.getRawClass().getSimpleName()); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/main/java/com/fasterxml/jackson/dataformat/protobuf/schemagen/ProtoBufSchemaVisitor.java: -------------------------------------------------------------------------------- 1 | package com.fasterxml.jackson.dataformat.protobuf.schemagen; 2 | 3 | import java.util.LinkedHashSet; 4 | import java.util.Set; 5 | 6 | import com.fasterxml.jackson.databind.JavaType; 7 | import com.fasterxml.jackson.databind.SerializerProvider; 8 | import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonAnyFormatVisitor; 9 | import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonArrayFormatVisitor; 10 | import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonBooleanFormatVisitor; 11 | import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper; 12 | import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonIntegerFormatVisitor; 13 | import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonMapFormatVisitor; 14 | import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonNullFormatVisitor; 15 | import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonNumberFormatVisitor; 16 | import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonObjectFormatVisitor; 17 | import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonStringFormatVisitor; 18 | import com.squareup.protoparser.TypeElement; 19 | 20 | public class ProtoBufSchemaVisitor extends JsonFormatVisitorWrapper.Base 21 | implements TypeElementBuilder 22 | { 23 | protected DefinedTypeElementBuilders _definedTypeElementBuilders; 24 | 25 | protected TypeElementBuilder _builder; 26 | 27 | protected boolean _isNested; 28 | 29 | /* 30 | * /********************************************************************** 31 | * /* Construction 32 | * /********************************************************************** 33 | */ 34 | 35 | public ProtoBufSchemaVisitor(SerializerProvider provider) { 36 | this(provider, null, false); 37 | } 38 | 39 | public ProtoBufSchemaVisitor(SerializerProvider provider, DefinedTypeElementBuilders definedTypeElementBuilders, 40 | boolean isNested) { 41 | super(provider); 42 | 43 | _definedTypeElementBuilders = (definedTypeElementBuilders != null) ? definedTypeElementBuilders 44 | : new DefinedTypeElementBuilders(); 45 | 46 | _isNested = isNested; 47 | } 48 | 49 | /* 50 | * /********************************************************************** 51 | * /* Extended API 52 | * /********************************************************************** 53 | */ 54 | 55 | @Override 56 | public TypeElement build() { 57 | return _builder.build(); 58 | } 59 | 60 | public Set buildWithDependencies() { 61 | Set allTypeElements = new LinkedHashSet<>(); 62 | allTypeElements.add(build()); 63 | 64 | for(TypeElementBuilder builder : _definedTypeElementBuilders.getDependencyBuilders()) { 65 | allTypeElements.add(builder.build()); 66 | } 67 | return allTypeElements; 68 | } 69 | 70 | /* 71 | /********************************************************************** 72 | /* Callbacks 73 | /********************************************************************** 74 | */ 75 | 76 | @Override 77 | public JsonObjectFormatVisitor expectObjectFormat(JavaType type) { 78 | MessageElementVisitor visitor = new MessageElementVisitor(_provider, type, _definedTypeElementBuilders, 79 | _isNested); 80 | _builder = visitor; 81 | return visitor; 82 | } 83 | 84 | @Override 85 | public JsonMapFormatVisitor expectMapFormat(JavaType mapType) { 86 | return _throwUnsupported("'Map' type not supported as root type by protobuf"); 87 | } 88 | 89 | @Override 90 | public JsonArrayFormatVisitor expectArrayFormat(JavaType convertedType) { 91 | return _throwUnsupported("'Array' type not supported as root type by protobuf"); 92 | } 93 | 94 | @Override 95 | public JsonStringFormatVisitor expectStringFormat(JavaType type) { 96 | if (!type.isEnumType()) { 97 | return _throwUnsupported("'String' type not supported as root type by protobuf"); 98 | } 99 | 100 | EnumElementVisitor visitor = new EnumElementVisitor(_provider, type, _definedTypeElementBuilders, _isNested); 101 | _builder = visitor; 102 | return visitor; 103 | } 104 | 105 | @Override 106 | public JsonNumberFormatVisitor expectNumberFormat(JavaType convertedType) { 107 | return _throwUnsupported("'Number' type not supported as root type by protobuf"); 108 | } 109 | 110 | @Override 111 | public JsonIntegerFormatVisitor expectIntegerFormat(JavaType type) { 112 | return _throwUnsupported("'Integer' type not supported as root type by protobuf"); 113 | } 114 | 115 | @Override 116 | public JsonBooleanFormatVisitor expectBooleanFormat(JavaType convertedType) { 117 | return _throwUnsupported("'Boolean' type not supported as root type by protobuf"); 118 | } 119 | 120 | @Override 121 | public JsonNullFormatVisitor expectNullFormat(JavaType convertedType) { 122 | return _throwUnsupported("'Null' type not supported as root type by protobuf"); 123 | } 124 | 125 | @Override 126 | public JsonAnyFormatVisitor expectAnyFormat(JavaType convertedType) { 127 | return _throwUnsupported("'Any' type not supported as root type by protobuf"); 128 | } 129 | 130 | /* 131 | /********************************************************************** 132 | /* Internal methods 133 | /********************************************************************** 134 | */ 135 | 136 | protected T _throwUnsupported() { 137 | return _throwUnsupported("Format variation not supported"); 138 | } 139 | 140 | protected T _throwUnsupported(String msg) { 141 | throw new UnsupportedOperationException(msg); 142 | } 143 | 144 | } 145 | -------------------------------------------------------------------------------- /src/main/java/com/fasterxml/jackson/dataformat/protobuf/schemagen/ProtobufSchemaGenerator.java: -------------------------------------------------------------------------------- 1 | package com.fasterxml.jackson.dataformat.protobuf.schemagen; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collection; 5 | import java.util.HashSet; 6 | 7 | import com.fasterxml.jackson.databind.JavaType; 8 | import com.fasterxml.jackson.databind.JsonMappingException; 9 | import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonObjectFormatVisitor; 10 | import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonStringFormatVisitor; 11 | import com.fasterxml.jackson.dataformat.protobuf.schema.NativeProtobufSchema; 12 | import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufSchema; 13 | import com.squareup.protoparser.TypeElement; 14 | 15 | /** 16 | * Class that can generate a {@link ProtobufSchema} for a given Java POJO, using 17 | * definitions Jackson would use for serialization. An instance is typically 18 | * given to 19 | * {@link com.fasterxml.jackson.databind.ObjectMapper#acceptJsonFormatVisitor} 20 | * which will invoke necessary callbacks. 21 | */ 22 | public class ProtobufSchemaGenerator extends ProtoBufSchemaVisitor 23 | { 24 | protected HashSet _generated; 25 | 26 | protected JavaType _rootType; 27 | 28 | public ProtobufSchemaGenerator() { 29 | // NOTE: null is fine here, as provider links itself after construction 30 | super(null); 31 | } 32 | 33 | public ProtobufSchema getGeneratedSchema() throws JsonMappingException { 34 | return getGeneratedSchema(true); 35 | } 36 | 37 | public ProtobufSchema getGeneratedSchema(boolean appendDependencies) throws JsonMappingException { 38 | if (_rootType == null || _builder == null) { 39 | throw new IllegalStateException( 40 | "No visit methods called on " + getClass().getName() + ": no schema generated"); 41 | } 42 | 43 | Collection types; 44 | 45 | if (appendDependencies) { 46 | types = this.buildWithDependencies(); 47 | } else { 48 | types = new ArrayList<>(); 49 | types.add(this.build()); 50 | } 51 | 52 | return NativeProtobufSchema.construct(_rootType.getRawClass().getName(), types).forFirstType(); 53 | } 54 | 55 | @Override 56 | public JsonObjectFormatVisitor expectObjectFormat(JavaType type) { 57 | _rootType = type; 58 | return super.expectObjectFormat(type); 59 | } 60 | 61 | @Override 62 | public JsonStringFormatVisitor expectStringFormat(JavaType type) { 63 | return _throwUnsupported("'String' type not supported as root type by protobuf"); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/fasterxml/jackson/dataformat/protobuf/schemagen/ProtobuffSchemaHelper.java: -------------------------------------------------------------------------------- 1 | package com.fasterxml.jackson.dataformat.protobuf.schemagen; 2 | 3 | import java.math.BigDecimal; 4 | import java.math.BigInteger; 5 | import java.nio.ByteBuffer; 6 | 7 | import com.fasterxml.jackson.databind.*; 8 | 9 | import com.squareup.protoparser.DataType; 10 | import com.squareup.protoparser.DataType.ScalarType; 11 | 12 | public class ProtobuffSchemaHelper 13 | { 14 | private ProtobuffSchemaHelper(){} 15 | 16 | public static String getNamespace(JavaType type) { 17 | Class cls = type.getRawClass(); 18 | Package pkg = cls.getPackage(); 19 | return (pkg == null) ? "" : pkg.getName(); 20 | } 21 | 22 | public static ScalarType getScalarType(JavaType type) { 23 | if (type.hasRawClass(int.class)) { 24 | return DataType.ScalarType.INT32; 25 | } else if (type.hasRawClass(long.class) || type.hasRawClass(BigInteger.class)) { 26 | return DataType.ScalarType.INT64; 27 | } else if (type.hasRawClass(String.class)) { 28 | return DataType.ScalarType.STRING; 29 | } else if (type.hasRawClass(float.class)) { 30 | return DataType.ScalarType.FLOAT; 31 | } else if (type.hasRawClass(boolean.class)) { 32 | return DataType.ScalarType.BOOL; 33 | } else if (type.hasRawClass(byte.class) || type.hasRawClass(ByteBuffer.class)) { 34 | return DataType.ScalarType.BYTES; 35 | } else if (type.hasRawClass(double.class) || type.hasRawClass(BigDecimal.class)) { 36 | return DataType.ScalarType.DOUBLE; 37 | } 38 | return null; 39 | } 40 | 41 | public static boolean hasIndex(BeanProperty writer) { 42 | return writer.getMetadata().hasIndex(); 43 | } 44 | 45 | public static TypeElementBuilder acceptTypeElement(SerializerProvider provider, JavaType type, 46 | DefinedTypeElementBuilders definedTypeElementBuilders, boolean isNested) throws JsonMappingException { 47 | JsonSerializer serializer = provider.findValueSerializer(type, null); 48 | ProtoBufSchemaVisitor visitor = new ProtoBufSchemaVisitor(provider, definedTypeElementBuilders, isNested); 49 | serializer.acceptJsonFormatVisitor(visitor, type); 50 | return visitor; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/fasterxml/jackson/dataformat/protobuf/schemagen/TagGenerator.java: -------------------------------------------------------------------------------- 1 | package com.fasterxml.jackson.dataformat.protobuf.schemagen; 2 | 3 | import com.fasterxml.jackson.databind.BeanProperty; 4 | 5 | interface TagGenerator { 6 | int nextTag(BeanProperty writer); 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/fasterxml/jackson/dataformat/protobuf/schemagen/TypeElementBuilder.java: -------------------------------------------------------------------------------- 1 | package com.fasterxml.jackson.dataformat.protobuf.schemagen; 2 | 3 | import com.squareup.protoparser.TypeElement; 4 | 5 | public interface TypeElementBuilder { 6 | 7 | TypeElement build(); 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/fasterxml/jackson/dataformat/protobuf/schemagen/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains functionality for generating Protobuf schema instances 3 | * from POJO definitions, instead of reading external protoc definitions. 4 | * This may be useful as-is, or for actually generating protoc definitions 5 | * with code-first approach. 6 | */ 7 | package com.fasterxml.jackson.dataformat.protobuf.schemagen; 8 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/com.fasterxml.jackson.core.JsonFactory: -------------------------------------------------------------------------------- 1 | # Copyright 2012 FasterXML.com 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | com.fasterxml.jackson.dataformat.protobuf.ProtobufFactory 16 | -------------------------------------------------------------------------------- /src/test/java/com/fasterxml/jackson/dataformat/protobuf/BigNumPair.java: -------------------------------------------------------------------------------- 1 | package com.fasterxml.jackson.dataformat.protobuf; 2 | 3 | public class BigNumPair { 4 | public static final String protobuf_str = 5 | "message BigNumPair {\n" 6 | + " required int64 long1 = 1;\n" 7 | + " required int64 long2 = 2;\n" 8 | + "}\n"; 9 | 10 | public long long1; 11 | public long long2; 12 | } 13 | -------------------------------------------------------------------------------- /src/test/java/com/fasterxml/jackson/dataformat/protobuf/LimitingInputStream.java: -------------------------------------------------------------------------------- 1 | package com.fasterxml.jackson.dataformat.protobuf; 2 | 3 | import java.io.*; 4 | import java.util.Random; 5 | 6 | public class LimitingInputStream 7 | extends FilterInputStream 8 | { 9 | protected final InputStream _in; 10 | 11 | protected final Random _rnd; 12 | 13 | public LimitingInputStream(InputStream in, int seed) { 14 | super(in); 15 | _in = in; 16 | _rnd = new Random(seed); 17 | } 18 | 19 | @Override 20 | public int read() throws IOException { 21 | return _in.read(); 22 | } 23 | 24 | @Override 25 | public int read(byte[] buffer) throws IOException { 26 | return read(buffer, 0, buffer.length); 27 | } 28 | 29 | @Override 30 | public int read(byte[] buffer, int offset, int len0) throws IOException { 31 | int len = Math.min(len0, 1 + _rnd.nextInt(6)); 32 | return _in.read(buffer, offset, len); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/com/fasterxml/jackson/dataformat/protobuf/ProtobufTestBase.java: -------------------------------------------------------------------------------- 1 | package com.fasterxml.jackson.dataformat.protobuf; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | 7 | import com.fasterxml.jackson.core.*; 8 | import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufField; 9 | import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufMessage; 10 | 11 | import junit.framework.TestCase; 12 | 13 | public abstract class ProtobufTestBase extends TestCase 14 | { 15 | /* 16 | /********************************************************** 17 | /* Basic protoc definitions 18 | /********************************************************** 19 | */ 20 | 21 | enum Corpus { 22 | UNIVERSAL, 23 | WEB; 24 | } 25 | 26 | static class SearchRequest { 27 | public String query; 28 | public int page_number, result_per_page; 29 | public Corpus corpus; 30 | } 31 | 32 | final protected static String PROTOC_SEARCH_REQUEST = "message SearchRequest {\n" 33 | +" required string query = 1;\n" 34 | +" optional int32 page_number = 2;\n" 35 | +" optional int32 result_per_page = 3;\n" 36 | +" enum Corpus {\n" 37 | +" UNIVERSAL = 10;\n" 38 | +" WEB = 20;\n" 39 | +" }\n" 40 | +" optional Corpus corpus = 4 [default = UNIVERSAL];\n" 41 | +"}\n" 42 | ; 43 | 44 | final protected static String PROTOC_POINT = 45 | "message Point {\n" 46 | +" required int32 x = 1;\n" 47 | +" required sint32 y = 2;\n" 48 | +"}\n"; 49 | 50 | final protected static String PROTOC_POINT_L = 51 | "message Point {\n" 52 | +" required int64 x = 1;\n" 53 | +" required sint64 y = 2;\n" 54 | +"}\n"; 55 | 56 | final protected static String PROTOC_POINT_FL = 57 | "message Point {\n" 58 | +" required fixed64 x = 1;\n" 59 | +" required fixed64 y = 2;\n" 60 | +"}\n"; 61 | 62 | final protected static String PROTOC_POINT_D = 63 | "message Point {\n" 64 | +" required double x = 1;\n" 65 | +" required float y = 2;\n" 66 | +"}\n"; 67 | 68 | final protected static String PROTOC_BOX = 69 | "message Box {\n" 70 | +" required Point topLeft = 3;\n" 71 | +" required Point bottomRight = 5;\n" 72 | +"}\n" 73 | +PROTOC_POINT; 74 | ; 75 | 76 | final protected static String PROTOC_NODE = 77 | "message Node {\n" 78 | +" required int32 id = 1;\n" 79 | +" optional Node left = 2;\n" 80 | +" optional Node right = 3;\n" 81 | +"}\n" 82 | ; 83 | 84 | final protected static String PROTOC_NAME = 85 | "message Name {\n" 86 | +" optional string first = 2;\n" 87 | +" required string last = 7;\n" 88 | +"}\n" 89 | ; 90 | 91 | final protected static String PROTOC_OPTIONAL_VALUE = 92 | "message Value {\n" 93 | +" required bool present = 1;\n" 94 | +" optional string value = 3;\n" 95 | +"}\n" 96 | ; 97 | 98 | // protoc definition from 'jvm-serializers' project: 99 | final protected static String PROTOC_MEDIA_ITEM = 100 | "package serializers.protobuf.media;\n"+ 101 | "option java_package = \"serializers.protobuf.media\";\n"+ 102 | "option java_outer_classname = \"MediaContentHolder\";\n"+ 103 | "option optimize_for = SPEED;\n"+ 104 | "\n"+ 105 | "message MediaItem {\n"+ 106 | " required Media media = 1;\n"+ 107 | " repeated Image images = 2;\n"+ 108 | "}\n"+ 109 | "message Image {\n"+ 110 | " required string uri = 1;\n"+ 111 | " optional string title = 4;\n"+ 112 | " required int32 width = 5;\n"+ 113 | " required int32 height = 6;\n"+ 114 | " enum Size {\n"+ 115 | " SMALL = 0;\n"+ 116 | " LARGE = 1;\n"+ 117 | " }\n"+ 118 | " required Size size = 7;\n"+ 119 | "}\n"+ 120 | "message Media {\n"+ 121 | " required string uri = 1;\n"+ 122 | " optional string title = 11;\n"+ 123 | " required int32 width = 12;\n"+ 124 | " required int32 height = 13;\n"+ 125 | " required string format = 14;\n"+ 126 | " required int64 duration = 15;\n"+ 127 | " required int64 size = 16;\n"+ 128 | " optional int32 bitrate = 17;\n"+ 129 | " repeated string persons = 18;\n"+ 130 | " enum Player {\n"+ 131 | " JAVA = 4;\n"+ 132 | " FLASH = 5;\n"+ 133 | "}\n"+ 134 | "required Player player = 20;\n"+ 135 | "optional string copyright = 21;\n"+ 136 | "}\n" 137 | ; 138 | 139 | /* 140 | /********************************************************** 141 | /* POJO classes to use with protoc definitions 142 | /********************************************************** 143 | */ 144 | 145 | static class Point { 146 | public int x; 147 | public int y; 148 | 149 | protected Point() { } 150 | 151 | public Point(int x, int y) { 152 | this.x = x; 153 | this.y = y; 154 | } 155 | 156 | @Override 157 | public String toString() { 158 | return "[x="+x+",y="+y+"]"; 159 | } 160 | 161 | @Override 162 | public boolean equals(Object o) { 163 | if (o == this) return true; 164 | if (o.getClass() != getClass()) return false; 165 | Point other = (Point) o; 166 | return (other.x == x) && (other.y == y); 167 | } 168 | } 169 | 170 | static class PointL { 171 | public long x; 172 | public long y; 173 | 174 | protected PointL() { } 175 | 176 | public PointL(long x, long y) { 177 | this.x = x; 178 | this.y = y; 179 | } 180 | 181 | @Override 182 | public String toString() { 183 | return "[x="+x+",y="+y+"]"; 184 | } 185 | 186 | @Override 187 | public boolean equals(Object o) { 188 | if (o == this) return true; 189 | if (o.getClass() != getClass()) return false; 190 | PointL other = (PointL) o; 191 | return (other.x == x) && (other.y == y); 192 | } 193 | } 194 | 195 | static class OptionalValue { 196 | public boolean present; 197 | public String value; 198 | 199 | protected OptionalValue() { } 200 | public OptionalValue(boolean p, String v) { 201 | present = p; 202 | value = v; 203 | } 204 | 205 | @Override 206 | public String toString() { 207 | return String.format("[present=%s, value=%s]", present, value); 208 | } 209 | 210 | @Override 211 | public boolean equals(Object o) { 212 | if (o == this) return true; 213 | if (o.getClass() != getClass()) return false; 214 | OptionalValue other = (OptionalValue) o; 215 | if (other.present != present) { 216 | return false; 217 | } 218 | if (value == other.value) return true; 219 | return (value != null) && value.equals(other.value); 220 | } 221 | } 222 | 223 | static class PointD { 224 | public double x; 225 | public double y; 226 | 227 | protected PointD() { } 228 | 229 | public PointD(double x, double y) { 230 | this.x = x; 231 | this.y = y; 232 | } 233 | 234 | @Override 235 | public String toString() { 236 | return "[x="+x+",y="+y+"]"; 237 | } 238 | 239 | @Override 240 | public boolean equals(Object o) { 241 | if (o == this) return true; 242 | if (o.getClass() != getClass()) return false; 243 | PointD other = (PointD) o; 244 | return (other.x == x) && (other.y == y); 245 | } 246 | } 247 | 248 | static class Box { 249 | public Point topLeft, bottomRight; 250 | 251 | public Box() { } 252 | public Box(Point tl, Point br) { 253 | topLeft = tl; 254 | bottomRight = br; 255 | } 256 | 257 | public Box(int x1, int y1, int x2, int y2) { 258 | this(new Point(x1, y1), new Point(x2, y2)); 259 | } 260 | 261 | @Override 262 | public String toString() { 263 | return "[topLeft="+topLeft+",bottomRight="+bottomRight+"]"; 264 | } 265 | } 266 | 267 | static class Name { 268 | public String first, last; 269 | 270 | public Name() { } 271 | public Name(String f, String l) { 272 | first = f; 273 | last = l; 274 | } 275 | 276 | @Override 277 | public String toString() { 278 | return "[first="+first+", last="+last+"]"; 279 | } 280 | } 281 | 282 | // // // POJOs for "JVM-serializers" case 283 | 284 | protected static class MediaItem 285 | { 286 | public Media media; 287 | public List images; 288 | 289 | public MediaItem() { } 290 | 291 | public MediaItem addPhoto(Image i) { 292 | if (images == null) { 293 | images = new ArrayList(); 294 | } 295 | images.add(i); 296 | return this; 297 | } 298 | 299 | public static MediaItem buildItem() 300 | { 301 | Media content = new Media(); 302 | content.player = Player.JAVA; 303 | content.uri = "http://javaone.com/keynote.mpg"; 304 | content.title = "Javaone Keynote"; 305 | content.width = 640; 306 | content.height = 480; 307 | content.format = "video/mpeg4"; 308 | content.duration = 18000000L; 309 | content.size = 58982400L; 310 | content.bitrate = 262144; 311 | content.copyright = "None"; 312 | content.addPerson("Bill Gates"); 313 | content.addPerson("Steve Jobs"); 314 | 315 | MediaItem item = new MediaItem(); 316 | item.media = content; 317 | 318 | item.addPhoto(new Image("http://javaone.com/keynote_large.jpg", "Javaone Keynote", 1024, 768, Size.LARGE)); 319 | item.addPhoto(new Image("http://javaone.com/keynote_small.jpg", "Javaone Keynote", 320, 240, Size.SMALL)); 320 | 321 | return item; 322 | } 323 | 324 | @Override 325 | public String toString() { 326 | return "{MediaItem: media="+media+", images="+images+"}"; 327 | } 328 | 329 | @Override 330 | public boolean equals(Object o) { 331 | if (o == this) return true; 332 | if (o == null) return false; 333 | if (o.getClass() != getClass()) return false; 334 | 335 | MediaItem other = (MediaItem) o; 336 | 337 | return _equals(media, other.media) && _equals(images, other.images); 338 | } 339 | } 340 | 341 | enum Size { SMALL, LARGE }; 342 | 343 | static class Image 344 | { 345 | public Image() { } 346 | public Image(String uri, String title, int w, int h, Size s) { 347 | this.uri = uri; 348 | this.title = title; 349 | width = w; 350 | height = h; 351 | size = s; 352 | } 353 | 354 | public String uri; 355 | public String title; 356 | public int width, height; 357 | public Size size; 358 | 359 | @Override 360 | public boolean equals(Object o) { 361 | if (o == this) return true; 362 | if (o == null) return false; 363 | if (o.getClass() != getClass()) return false; 364 | 365 | Image other = (Image) o; 366 | 367 | return _equals(uri, other.uri) 368 | && _equals(title, other.title) 369 | && _equals(size, other.size) 370 | && (width == other.width) 371 | && (height == other.height) 372 | ; 373 | } 374 | } 375 | 376 | enum Player { JAVA, FLASH; } 377 | 378 | static class Media { 379 | 380 | public String uri; 381 | public String title; // Can be unset. 382 | public int width; 383 | public int height; 384 | public String format; 385 | public long duration; 386 | public long size; 387 | public int bitrate; // Can be unset. 388 | 389 | public List persons; 390 | 391 | public Player player; 392 | 393 | public String copyright; // Can be unset. 394 | 395 | public Media addPerson(String p) { 396 | if (persons == null) { 397 | persons = new ArrayList(); 398 | } 399 | persons.add(p); 400 | return this; 401 | } 402 | 403 | @Override 404 | public boolean equals(Object o) { 405 | if (o == this) return true; 406 | if (o == null) return false; 407 | if (o.getClass() != getClass()) return false; 408 | 409 | Media other = (Media) o; 410 | 411 | return _equals(uri, other.uri) 412 | && _equals(title, other.title) 413 | && (width == other.width) 414 | && (height == other.height) 415 | && _equals(format, other.format) 416 | && (duration == other.duration) 417 | && (size == other.size) 418 | && _equals(persons, other.persons) 419 | && _equals(player, other.player) 420 | && _equals(copyright, other.copyright) 421 | ; 422 | } 423 | } 424 | 425 | /* 426 | /********************************************************** 427 | /* Additional assertion methods 428 | /********************************************************** 429 | */ 430 | 431 | protected void _verifyMessageFieldLinking(ProtobufMessage msg) 432 | { 433 | ProtobufField prev = null; 434 | for (ProtobufField curr : msg.fields()) { 435 | if (prev != null) { 436 | if (prev.next != curr) { 437 | fail("Linking broken for type '"+msg.getName()+", field '"+curr.name+"'; points to "+prev.next); 438 | } 439 | } 440 | prev = curr; 441 | } 442 | // also, verify that the last field not linked 443 | if (prev.next != null) { 444 | fail("Linking broken for type '"+msg.getName()+", last field '"+prev.name 445 | +"' should be null, points to "+prev.next); 446 | } 447 | } 448 | 449 | protected void assertToken(JsonToken expToken, JsonToken actToken) 450 | { 451 | if (actToken != expToken) { 452 | fail("Expected token "+expToken+", current token "+actToken); 453 | } 454 | } 455 | 456 | protected void assertToken(JsonToken expToken, JsonParser p) 457 | { 458 | assertToken(expToken, p.getCurrentToken()); 459 | } 460 | 461 | protected void assertType(Object ob, Class expType) 462 | { 463 | if (ob == null) { 464 | fail("Expected an object of type "+expType.getName()+", got null"); 465 | } 466 | Class cls = ob.getClass(); 467 | if (!expType.isAssignableFrom(cls)) { 468 | fail("Expected type "+expType.getName()+", got "+cls.getName()); 469 | } 470 | } 471 | 472 | protected void verifyException(Throwable e, String... matches) 473 | { 474 | String msg = e.getMessage(); 475 | String lmsg = (msg == null) ? "" : msg.toLowerCase(); 476 | for (String match : matches) { 477 | String lmatch = match.toLowerCase(); 478 | if (lmsg.indexOf(lmatch) >= 0) { 479 | return; 480 | } 481 | } 482 | fail("Expected an exception with one of substrings ("+Arrays.asList(matches)+"): got one with message \""+msg+"\""); 483 | } 484 | 485 | static boolean _equals(T ob1, T ob2) { 486 | if (ob1 == null) { 487 | return (ob2 == null); 488 | } 489 | return ob1.equals(ob2); 490 | } 491 | } 492 | -------------------------------------------------------------------------------- /src/test/java/com/fasterxml/jackson/dataformat/protobuf/ReadComplexPojoTest.java: -------------------------------------------------------------------------------- 1 | package com.fasterxml.jackson.dataformat.protobuf; 2 | 3 | import java.io.ByteArrayInputStream; 4 | import java.io.InputStream; 5 | 6 | import org.junit.Assert; 7 | 8 | import com.fasterxml.jackson.databind.*; 9 | import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufSchema; 10 | import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufSchemaLoader; 11 | 12 | public class ReadComplexPojoTest extends ProtobufTestBase 13 | { 14 | final ObjectMapper MAPPER = new ObjectMapper(new ProtobufFactory()); 15 | 16 | /* 17 | /********************************************************** 18 | /* Test methods 19 | /********************************************************** 20 | */ 21 | 22 | public void testMediaItemSimple() throws Exception 23 | { 24 | _testMediaItem(false); 25 | } 26 | 27 | 28 | public void testMediaItemWithSmallReads() throws Exception 29 | { 30 | _testMediaItem(true); 31 | } 32 | 33 | @SuppressWarnings("resource") 34 | private void _testMediaItem(boolean smallReads) throws Exception 35 | { 36 | ProtobufSchema schema = ProtobufSchemaLoader.std.parse(PROTOC_MEDIA_ITEM); 37 | final ObjectWriter w = MAPPER.writer(schema); 38 | MediaItem input = MediaItem.buildItem(); 39 | byte[] bytes = w.writeValueAsBytes(input); 40 | 41 | assertNotNull(bytes); 42 | assertEquals(252, bytes.length); 43 | 44 | ObjectReader r = MAPPER.readerFor(MediaItem.class).with(schema); 45 | MediaItem result; 46 | InputStream in = new ByteArrayInputStream(bytes); 47 | 48 | if (smallReads) { 49 | in = new LimitingInputStream(in, 123); 50 | } 51 | result = r.readValue(in); 52 | 53 | assertNotNull(result); 54 | byte[] b2 = w.writeValueAsBytes(result); 55 | assertEquals(bytes.length, b2.length); 56 | 57 | Assert.assertArrayEquals(bytes, b2); 58 | 59 | assertEquals(input, result); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/test/java/com/fasterxml/jackson/dataformat/protobuf/ReadSimpleTest.java: -------------------------------------------------------------------------------- 1 | package com.fasterxml.jackson.dataformat.protobuf; 2 | 3 | import com.fasterxml.jackson.core.JsonParser; 4 | import com.fasterxml.jackson.core.JsonToken; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import com.fasterxml.jackson.databind.ObjectWriter; 7 | import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufSchema; 8 | import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufSchemaLoader; 9 | 10 | public class ReadSimpleTest extends ProtobufTestBase 11 | { 12 | final protected static String PROTOC_STRINGS = 13 | "message Strings {\n" 14 | +" repeated string values = 3;\n" 15 | +"}\n" 16 | ; 17 | 18 | final protected static String PROTOC_STRINGS_PACKED = 19 | "message Strings {\n" 20 | +" repeated string values = 2 [packed=true];\n" 21 | +"}\n" 22 | ; 23 | 24 | final protected static String PROTOC_NAMED_STRINGS = 25 | "message NamedStrings {\n" 26 | +" required string name = 2;\n" 27 | +" repeated string values = 7;\n" 28 | +"}\n" 29 | ; 30 | 31 | static class Strings { 32 | public String[] values; 33 | 34 | public Strings() { } 35 | public Strings(String... v) { values = v; } 36 | } 37 | 38 | static class NamedStrings { 39 | public String name; 40 | public String[] values; 41 | 42 | public NamedStrings() { } 43 | public NamedStrings(String n, String... v) { 44 | name = n; 45 | values = v; 46 | } 47 | } 48 | 49 | final ObjectMapper MAPPER = new ObjectMapper(new ProtobufFactory()); 50 | 51 | /* 52 | /********************************************************** 53 | /* Test methods 54 | /********************************************************** 55 | */ 56 | 57 | public void testReadPointInt() throws Exception 58 | { 59 | ProtobufSchema schema = ProtobufSchemaLoader.std.parse(PROTOC_BOX, "Point"); 60 | final ObjectWriter w = MAPPER.writerFor(Point.class) 61 | .with(schema); 62 | Point input = new Point(151, -444); 63 | byte[] bytes = w.writeValueAsBytes(input); 64 | assertNotNull(bytes); 65 | 66 | // 6 bytes: 1 byte tags, 2 byte values 67 | assertEquals(6, bytes.length); 68 | 69 | // but more importantly, try to parse 70 | Point result = MAPPER.readerFor(Point.class).with(schema).readValue(bytes); 71 | assertNotNull(result); 72 | assertEquals(input.x, result.x); 73 | assertEquals(input.y, result.y); 74 | 75 | // actually let's also try via streaming parser 76 | JsonParser p = MAPPER.getFactory().createParser(bytes); 77 | p.setSchema(schema); 78 | assertToken(JsonToken.START_OBJECT, p.nextToken()); 79 | assertToken(JsonToken.FIELD_NAME, p.nextToken()); 80 | assertEquals("x", p.getCurrentName()); 81 | assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken()); 82 | assertEquals(input.x, p.getIntValue()); 83 | assertToken(JsonToken.FIELD_NAME, p.nextToken()); 84 | assertEquals("y", p.getCurrentName()); 85 | assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken()); 86 | assertEquals(input.y, p.getIntValue()); 87 | assertToken(JsonToken.END_OBJECT, p.nextToken()); 88 | p.close(); 89 | } 90 | 91 | public void testReadPointLong() throws Exception 92 | { 93 | ProtobufSchema schema = ProtobufSchemaLoader.std.parse(PROTOC_POINT_L); 94 | final ObjectWriter w = MAPPER.writerFor(Point.class) 95 | .with(schema); 96 | Point input = new Point(Integer.MAX_VALUE, Integer.MIN_VALUE); 97 | byte[] bytes = w.writeValueAsBytes(input); 98 | assertNotNull(bytes); 99 | 100 | assertEquals(12, bytes.length); 101 | 102 | // but more importantly, try to parse 103 | Point result = MAPPER.readerFor(Point.class).with(schema).readValue(bytes); 104 | assertNotNull(result); 105 | assertEquals(input.x, result.x); 106 | assertEquals(input.y, result.y); 107 | 108 | // actually let's also try via streaming parser 109 | JsonParser p = MAPPER.getFactory().createParser(bytes); 110 | p.setSchema(schema); 111 | assertToken(JsonToken.START_OBJECT, p.nextToken()); 112 | assertToken(JsonToken.FIELD_NAME, p.nextToken()); 113 | assertEquals("x", p.getCurrentName()); 114 | assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken()); 115 | assertEquals(input.x, p.getIntValue()); 116 | assertToken(JsonToken.FIELD_NAME, p.nextToken()); 117 | assertEquals("y", p.getCurrentName()); 118 | assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken()); 119 | assertEquals(input.y, p.getIntValue()); 120 | assertToken(JsonToken.END_OBJECT, p.nextToken()); 121 | p.close(); 122 | } 123 | 124 | public void testReadName() throws Exception 125 | { 126 | ProtobufSchema schema = ProtobufSchemaLoader.std.parse(PROTOC_NAME); 127 | final ObjectWriter w = MAPPER.writerFor(Name.class) 128 | .with(schema); 129 | // make sure to use at least one non-ascii char in there: 130 | Name input = new Name("Billy", "Baco\u00F1"); 131 | 132 | byte[] bytes = w.writeValueAsBytes(input); 133 | assertNotNull(bytes); 134 | 135 | assertEquals(15, bytes.length); 136 | 137 | Name result = MAPPER.readerFor(Name.class).with(schema).readValue(bytes); 138 | assertNotNull(result); 139 | assertEquals(input.first, result.first); 140 | assertEquals(input.last, result.last); 141 | } 142 | 143 | public void testReadBox() throws Exception 144 | { 145 | ProtobufSchema schema = ProtobufSchemaLoader.std.parse(PROTOC_BOX); 146 | final ObjectWriter w = MAPPER.writerFor(Box.class) 147 | .with(schema); 148 | Point topLeft = new Point(100, 150); 149 | Point bottomRight = new Point(500, 1090); 150 | Box input = new Box(topLeft, bottomRight); 151 | 152 | byte[] bytes = w.writeValueAsBytes(input); 153 | assertNotNull(bytes); 154 | 155 | assertEquals(15, bytes.length); 156 | 157 | Box result = MAPPER.readerFor(Box.class).with(schema).readValue(bytes); 158 | assertNotNull(result); 159 | assertNotNull(result.topLeft); 160 | assertNotNull(result.bottomRight); 161 | assertEquals(input.topLeft, result.topLeft); 162 | assertEquals(input.bottomRight, result.bottomRight); 163 | } 164 | 165 | public void testStringArraySimple() throws Exception 166 | { 167 | ProtobufSchema schema = ProtobufSchemaLoader.std.parse(PROTOC_STRINGS); 168 | final ObjectWriter w = MAPPER.writerFor(Strings.class) 169 | .with(schema); 170 | Strings input = new Strings("Dogs", "like", "Baco\u00F1"); 171 | byte[] bytes = w.writeValueAsBytes(input); 172 | assertNotNull(bytes); 173 | assertEquals(20, bytes.length); 174 | 175 | Strings result = MAPPER.readerFor(Strings.class).with(schema).readValue(bytes); 176 | assertNotNull(result); 177 | assertNotNull(result.values); 178 | assertEquals(input.values.length, result.values.length); 179 | for (int i = 0; i < result.values.length; ++i) { 180 | assertEquals(input.values[i], result.values[i]); 181 | } 182 | 183 | // and also verify via streaming 184 | JsonParser p = MAPPER.getFactory().createParser(bytes); 185 | p.setSchema(schema); 186 | assertToken(JsonToken.START_OBJECT, p.nextToken()); 187 | assertToken(JsonToken.FIELD_NAME, p.nextToken()); 188 | assertEquals("values", p.getCurrentName()); 189 | assertToken(JsonToken.START_ARRAY, p.nextToken()); 190 | 191 | assertToken(JsonToken.VALUE_STRING, p.nextToken()); 192 | assertEquals(input.values[0], p.getText()); 193 | assertToken(JsonToken.VALUE_STRING, p.nextToken()); 194 | assertEquals(input.values[1], p.getText()); 195 | assertToken(JsonToken.VALUE_STRING, p.nextToken()); 196 | assertEquals(input.values[2], p.getText()); 197 | 198 | assertToken(JsonToken.END_ARRAY, p.nextToken()); 199 | assertToken(JsonToken.END_OBJECT, p.nextToken()); 200 | p.close(); 201 | } 202 | 203 | public void testStringArrayPacked() throws Exception 204 | { 205 | ProtobufSchema schema = ProtobufSchemaLoader.std.parse(PROTOC_STRINGS_PACKED); 206 | final ObjectWriter w = MAPPER.writerFor(Strings.class) 207 | .with(schema); 208 | Strings input = new Strings("Dogs", "like", "Baco\u00F1"); 209 | byte[] bytes = w.writeValueAsBytes(input); 210 | assertNotNull(bytes); 211 | // one byte less, due to length prefix 212 | assertEquals(19, bytes.length); 213 | 214 | Strings result = MAPPER.readerFor(Strings.class).with(schema).readValue(bytes); 215 | assertNotNull(result); 216 | assertNotNull(result.values); 217 | assertEquals(input.values.length, result.values.length); 218 | for (int i = 0; i < result.values.length; ++i) { 219 | assertEquals(input.values[i], result.values[i]); 220 | } 221 | } 222 | 223 | public void testStringArrayWithName() throws Exception 224 | { 225 | ProtobufSchema schema = ProtobufSchemaLoader.std.parse(PROTOC_NAMED_STRINGS); 226 | final ObjectWriter w = MAPPER.writerFor(NamedStrings.class) 227 | .with(schema); 228 | NamedStrings input = new NamedStrings("abc123", "a", "b", "", "d"); 229 | byte[] bytes = w.writeValueAsBytes(input); 230 | assertNotNull(bytes); 231 | assertEquals(19, bytes.length); 232 | 233 | NamedStrings result = MAPPER.readerFor(NamedStrings.class).with(schema).readValue(bytes); 234 | assertNotNull(result); 235 | assertEquals(input.name, result.name); 236 | assertNotNull(result.values); 237 | assertEquals(input.values.length, result.values.length); 238 | for (int i = 0; i < result.values.length; ++i) { 239 | assertEquals(input.values[i], result.values[i]); 240 | } 241 | 242 | // and also verify via streaming 243 | JsonParser p = MAPPER.getFactory().createParser(bytes); 244 | p.setSchema(schema); 245 | assertToken(JsonToken.START_OBJECT, p.nextToken()); 246 | 247 | assertToken(JsonToken.FIELD_NAME, p.nextToken()); 248 | assertEquals("name", p.getCurrentName()); 249 | assertToken(JsonToken.VALUE_STRING, p.nextToken()); 250 | assertEquals(input.name, p.getText()); 251 | 252 | assertToken(JsonToken.FIELD_NAME, p.nextToken()); 253 | assertEquals("values", p.getCurrentName()); 254 | assertToken(JsonToken.START_ARRAY, p.nextToken()); 255 | 256 | assertToken(JsonToken.VALUE_STRING, p.nextToken()); 257 | assertEquals(input.values[0], p.getText()); 258 | assertToken(JsonToken.VALUE_STRING, p.nextToken()); 259 | assertEquals(input.values[1], p.getText()); 260 | assertToken(JsonToken.VALUE_STRING, p.nextToken()); 261 | assertEquals(input.values[2], p.getText()); 262 | assertToken(JsonToken.VALUE_STRING, p.nextToken()); 263 | assertEquals(input.values[3], p.getText()); 264 | 265 | assertToken(JsonToken.END_ARRAY, p.nextToken()); 266 | assertToken(JsonToken.END_OBJECT, p.nextToken()); 267 | p.close(); 268 | } 269 | 270 | public void testSearchMessage() throws Exception 271 | { 272 | ProtobufSchema schema = ProtobufSchemaLoader.std.parse(PROTOC_SEARCH_REQUEST); 273 | final ObjectWriter w = MAPPER.writerFor(SearchRequest.class) 274 | .with(schema); 275 | SearchRequest input = new SearchRequest(); 276 | input.corpus = Corpus.WEB; 277 | input.page_number = 3; 278 | input.result_per_page = 200; 279 | input.query = "get all"; 280 | 281 | byte[] bytes = w.writeValueAsBytes(input); 282 | assertNotNull(bytes); 283 | assertEquals(16, bytes.length); 284 | 285 | SearchRequest result = MAPPER.readerFor(SearchRequest.class).with(schema).readValue(bytes); 286 | assertNotNull(result); 287 | 288 | assertEquals(input.page_number, result.page_number); 289 | assertEquals(input.result_per_page, result.result_per_page); 290 | assertEquals(input.query, result.query); 291 | assertEquals(input.corpus, result.corpus); 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /src/test/java/com/fasterxml/jackson/dataformat/protobuf/SchemaParsingTest.java: -------------------------------------------------------------------------------- 1 | package com.fasterxml.jackson.dataformat.protobuf; 2 | 3 | import java.util.List; 4 | 5 | import com.fasterxml.jackson.dataformat.protobuf.schema.NativeProtobufSchema; 6 | import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufField; 7 | import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufMessage; 8 | import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufSchema; 9 | import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufSchemaLoader; 10 | 11 | public class SchemaParsingTest extends ProtobufTestBase 12 | { 13 | final protected static String PROTOC_ENUMS = 14 | "message Enums {\n" 15 | +" enum StdEnum {\n" 16 | +" A = 0;\n" 17 | +" B = 1;\n" 18 | +" }\n" 19 | +" enum NonStdEnum {\n" 20 | +" C = 10;\n" 21 | +" D = 20;\n" 22 | +" }\n" 23 | +" optional StdEnum enum1 = 1;\n" 24 | +" optional NonStdEnum enum2 = 2;\n" 25 | +"}\n" 26 | ; 27 | 28 | final protected static String PROTOC_EMPTY = "message Empty { }"; 29 | 30 | final protected static String PROTOC_STRINGS_PACKED = 31 | "message Strings {\n" 32 | +" repeated string values = 2 [packed=true];\n" 33 | +"}\n" 34 | ; 35 | 36 | public void testSimpleSearchRequest() throws Exception 37 | { 38 | // First: with implicit first type: 39 | ProtobufSchema schema = ProtobufSchemaLoader.std.parse(PROTOC_SEARCH_REQUEST); 40 | assertNotNull(schema); 41 | 42 | // then with named, step by step 43 | NativeProtobufSchema nat = ProtobufSchemaLoader.std.parseNative(PROTOC_SEARCH_REQUEST); 44 | assertNotNull(nat); 45 | assertNotNull(nat.forFirstType()); 46 | assertNotNull(nat.forType("SearchRequest")); 47 | 48 | List all = nat.getMessageNames(); 49 | assertEquals(1, all.size()); 50 | assertEquals("SearchRequest", all.get(0)); 51 | ProtobufMessage msg = schema.getRootType(); 52 | assertEquals(4, msg.getFieldCount()); 53 | 54 | _verifyMessageFieldLinking(msg); 55 | } 56 | 57 | public void testBoxAndPoint() throws Exception 58 | { 59 | ProtobufSchema schema = ProtobufSchemaLoader.std.parse(PROTOC_BOX); 60 | assertNotNull(schema); 61 | List all = schema.getMessageTypes(); 62 | assertEquals(2, all.size()); 63 | assertTrue(all.contains("Box")); 64 | assertTrue(all.contains("Point")); 65 | _verifyMessageFieldLinking(schema.getRootType()); 66 | } 67 | 68 | public void testRecursive() throws Exception 69 | { 70 | ProtobufSchema schema = ProtobufSchemaLoader.std.parse(PROTOC_NODE); 71 | assertNotNull(schema); 72 | List all = schema.getMessageTypes(); 73 | assertEquals(1, all.size()); 74 | assertEquals("Node", all.get(0)); 75 | ProtobufMessage msg = schema.getRootType(); 76 | assertEquals(3, msg.getFieldCount()); 77 | ProtobufField f = msg.field("id"); 78 | assertNotNull(f); 79 | assertEquals("id", f.name); 80 | 81 | _verifyMessageFieldLinking(schema.getRootType()); 82 | } 83 | 84 | public void testEnum() throws Exception 85 | { 86 | ProtobufSchema schema = ProtobufSchemaLoader.std.parse(PROTOC_ENUMS); 87 | assertNotNull(schema); 88 | List all = schema.getMessageTypes(); 89 | assertEquals(1, all.size()); 90 | 91 | ProtobufMessage msg = schema.getRootType(); 92 | assertEquals(2, msg.getFieldCount()); 93 | 94 | _verifyMessageFieldLinking(msg); 95 | 96 | ProtobufField f; 97 | 98 | f = msg.field("enum1"); 99 | assertNotNull(f); 100 | assertTrue(f.isStdEnum); 101 | assertFalse(f.packed); 102 | 103 | f = msg.field("enum2"); 104 | assertNotNull(f); 105 | assertFalse(f.isStdEnum); 106 | } 107 | 108 | public void testEmpty() throws Exception 109 | { 110 | ProtobufSchema schema = ProtobufSchemaLoader.std.parse(PROTOC_EMPTY); 111 | assertNotNull(schema); 112 | List all = schema.getMessageTypes(); 113 | assertEquals(1, all.size()); 114 | } 115 | 116 | public void testPacked() throws Exception 117 | { 118 | ProtobufSchema schema = ProtobufSchemaLoader.std.parse(PROTOC_STRINGS_PACKED); 119 | assertNotNull(schema); 120 | List all = schema.getMessageTypes(); 121 | assertEquals(1, all.size()); 122 | ProtobufMessage msg = schema.getRootType(); 123 | assertEquals(1, msg.getFieldCount()); 124 | ProtobufField field = msg.fields().iterator().next(); 125 | assertEquals("values", field.name); 126 | assertEquals(2, field.id); 127 | assertTrue(field.packed); 128 | } 129 | } 130 | 131 | -------------------------------------------------------------------------------- /src/test/java/com/fasterxml/jackson/dataformat/protobuf/SerDeserLongTest.java: -------------------------------------------------------------------------------- 1 | package com.fasterxml.jackson.dataformat.protobuf; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufSchema; 5 | import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufSchemaLoader; 6 | import org.junit.Test; 7 | 8 | import java.io.IOException; 9 | 10 | /** 11 | * Created by miz on 8/10/16. 12 | */ 13 | public class SerDeserLongTest { 14 | @Test 15 | public void testWeirdLongSerDeser() throws IOException { 16 | ObjectMapper mapper = new ObjectMapper(new ProtobufFactory()); 17 | ProtobufSchema schema = ProtobufSchemaLoader.std.parse(BigNumPair.protobuf_str); 18 | 19 | BigNumPair bnp = new BigNumPair(); 20 | bnp.long1 = 72057594037927935L; 21 | bnp.long2 = 0; 22 | 23 | byte[] encoded = mapper.writer(schema).writeValueAsBytes(bnp); 24 | 25 | BigNumPair parsed = mapper.readerFor(BigNumPair.class).with(schema).readValue(encoded); 26 | 27 | assert parsed.long1 == bnp.long1; 28 | assert parsed.long2 == bnp.long2; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/test/java/com/fasterxml/jackson/dataformat/protobuf/WriteArrayTest.java: -------------------------------------------------------------------------------- 1 | package com.fasterxml.jackson.dataformat.protobuf; 2 | 3 | import org.junit.Assert; 4 | 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import com.fasterxml.jackson.databind.ObjectWriter; 7 | import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufSchema; 8 | import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufSchemaLoader; 9 | 10 | public class WriteArrayTest extends ProtobufTestBase 11 | { 12 | final protected static String PROTOC_INT_ARRAY_SPARSE = "message Ints {\n" 13 | +" repeated sint32 values = 1;\n" 14 | +"}\n" 15 | ; 16 | 17 | final protected static String PROTOC_INT_ARRAY_PACKED = "message Ints {\n" 18 | +" repeated sint32 values = 1 [packed=true];\n" 19 | +"}\n" 20 | ; 21 | 22 | final protected static String PROTOC_STRING_ARRAY_SPARSE = "message Ints {\n" 23 | +" repeated string values = 1;\n" 24 | +"}\n" 25 | ; 26 | 27 | final protected static String PROTOC_STRING_ARRAY_PACKED = "message Ints {\n" 28 | +" repeated string values = 1 [packed=true];\n" 29 | +"}\n" 30 | ; 31 | 32 | final protected static String PROTOC_POINT_ARRAY_SPARSE = "message Points {\n" 33 | +" repeated Point points = 1;\n" 34 | +"}\n" 35 | +PROTOC_POINT; 36 | ; 37 | 38 | final protected static String PROTOC_POINT_ARRAY_PACKED = "message Points {\n" 39 | +" repeated Point points = 1 [packed=true];\n" 40 | +"}\n" 41 | +PROTOC_POINT; 42 | ; 43 | 44 | static class IntArray { 45 | public int[] values; 46 | 47 | public IntArray(int... v) { 48 | values = v; 49 | } 50 | } 51 | 52 | static class StringArray { 53 | public String[] values; 54 | 55 | public StringArray(String... v) { 56 | values = v; 57 | } 58 | } 59 | 60 | static class PointArray { 61 | public Point[] points; 62 | 63 | public PointArray(Point... p) { 64 | points = p; 65 | } 66 | } 67 | 68 | final ObjectMapper MAPPER = new ObjectMapper(new ProtobufFactory()); 69 | 70 | final ProtobufSchema SPARSE_STRING_SCHEMA; 71 | final ProtobufSchema PACKED_STRING_SCHEMA; 72 | 73 | public WriteArrayTest() throws Exception { 74 | SPARSE_STRING_SCHEMA = ProtobufSchemaLoader.std.parse(PROTOC_STRING_ARRAY_SPARSE); 75 | PACKED_STRING_SCHEMA = ProtobufSchemaLoader.std.parse(PROTOC_STRING_ARRAY_PACKED); 76 | } 77 | 78 | /* 79 | /********************************************************** 80 | /* Test methods, int arrays 81 | /********************************************************** 82 | */ 83 | 84 | public void testIntArraySparse() throws Exception 85 | { 86 | /* 87 | final protected static String PROTOC_INT_ARRAY = "message Ints {\n" 88 | +" repeated int32 values = 1; }\n"; 89 | */ 90 | final ObjectWriter w = MAPPER.writer(ProtobufSchemaLoader.std.parse(PROTOC_INT_ARRAY_SPARSE)); 91 | byte[] bytes = w.writeValueAsBytes(new IntArray(3, -1, 2)); 92 | // 3 x 2 bytes per value (typed tag, value) -> 6 93 | assertEquals(6, bytes.length); 94 | assertEquals(0x8, bytes[0]); // zig-zagged vint (0) value, field 1 95 | assertEquals(0x6, bytes[1]); // zig-zagged value for 3 96 | assertEquals(0x8, bytes[2]); 97 | assertEquals(0x1, bytes[3]); // zig-zagged value for -1 98 | assertEquals(0x8, bytes[4]); 99 | assertEquals(0x4, bytes[5]); // zig-zagged value for 2 100 | } 101 | 102 | public void testIntArrayPacked() throws Exception 103 | { 104 | final ObjectWriter w = MAPPER.writer(ProtobufSchemaLoader.std.parse(PROTOC_INT_ARRAY_PACKED)); 105 | byte[] bytes = w.writeValueAsBytes(new IntArray(3, -1, 2)); 106 | // 1 byte for typed tag, 1 byte for length, 3 x 1 byte per value -> 5 107 | assertEquals(5, bytes.length); 108 | assertEquals(0x8, bytes[0]); // zig-zagged vint (0) value, field 1 109 | assertEquals(0x3, bytes[1]); // length for array, 3 bytes 110 | assertEquals(0x6, bytes[2]); // zig-zagged value for 3 111 | assertEquals(0x1, bytes[3]); // zig-zagged value for -1 112 | assertEquals(0x4, bytes[4]); // zig-zagged value for 2 113 | } 114 | 115 | /* 116 | /********************************************************** 117 | /* Test methods, String arrays 118 | /********************************************************** 119 | */ 120 | 121 | public void testStringArraySparse() throws Exception 122 | { 123 | final ObjectWriter w = MAPPER.writer(SPARSE_STRING_SCHEMA); 124 | byte[] bytes = w.writeValueAsBytes(new StringArray("Foo", "Bar")); 125 | assertEquals(10, bytes.length); 126 | Assert.assertArrayEquals(new byte[] { 127 | 0xA, 3, 'F', 'o', 'o', 128 | 0xA, 3, 'B', 'a', 'r', 129 | }, bytes); 130 | } 131 | 132 | public void testStringArrayPacked() throws Exception 133 | { 134 | final ObjectWriter w = MAPPER.writer(PACKED_STRING_SCHEMA); 135 | byte[] bytes = w.writeValueAsBytes(new StringArray("A", "B", "C")); 136 | assertEquals(8, bytes.length); 137 | Assert.assertArrayEquals(new byte[] { 138 | 0xA, 6, 139 | 1, 'A', 140 | 1, 'B', 141 | 1, 'C', 142 | }, bytes); 143 | } 144 | 145 | /* 146 | /********************************************************** 147 | /* Test methods, POJO arrays 148 | /********************************************************** 149 | */ 150 | 151 | public void testPointArraySparse() throws Exception 152 | { 153 | final ObjectWriter w = MAPPER.writer(ProtobufSchemaLoader.std.parse(PROTOC_POINT_ARRAY_SPARSE)); 154 | byte[] bytes = w.writeValueAsBytes(new PointArray(new Point(1, 2), new Point(3, 4))); 155 | // sequence of 2 embedded messages, each with 1 byte typed tag, 1 byte length 156 | // and 2 fields of typed-tag and single-byte value 157 | assertEquals(12, bytes.length); 158 | 159 | assertEquals(0xA, bytes[0]); // wire type 2 (length prefix), id of 1 (-> 0x8) 160 | assertEquals(4, bytes[1]); // length 161 | assertEquals(8, bytes[2]); // wire type 0 (3 LSB), id of 1 (-> 0x8) 162 | assertEquals(1, bytes[3]); // VInt 1, no zig-zag 163 | assertEquals(0x10, bytes[4]); // wire type 0 (3 LSB), id of 2 (-> 0x10) 164 | assertEquals(4, bytes[5]); // VInt 2, but with zig-zag 165 | 166 | assertEquals(0xA, bytes[6]); // similar to above 167 | assertEquals(4, bytes[7]); 168 | assertEquals(8, bytes[8]); 169 | assertEquals(3, bytes[9]); // Point(3, ) 170 | assertEquals(0x10, bytes[10]); 171 | assertEquals(8, bytes[11]); // Point (, 4) 172 | } 173 | 174 | public void testPointArrayPacked() throws Exception 175 | { 176 | final ObjectWriter w = MAPPER.writer(ProtobufSchemaLoader.std.parse(PROTOC_POINT_ARRAY_PACKED)); 177 | byte[] bytes = w.writeValueAsBytes(new PointArray(new Point(1, 2), new Point(3, 4))); 178 | // should have 1 byte typed-tag, 1 byte length (for array contents); 179 | // followed by 2 embedded messages of 5 bytes length 180 | 181 | assertEquals(12, bytes.length); 182 | assertEquals(0xA, bytes[0]); // length-prefixed (2) value, field 1 183 | assertEquals(10, bytes[1]); // length of entries in array 184 | 185 | assertEquals(4, bytes[2]); // length of first entry 186 | assertEquals(8, bytes[3]); // wire type 0 (3 LSB), id of 1 (-> 0x8) 187 | assertEquals(1, bytes[4]); // VInt 1, no zig-zag 188 | assertEquals(0x10, bytes[5]); // wire type 0 (3 LSB), id of 2 (-> 0x10) 189 | assertEquals(4, bytes[6]); // VInt 2, but with zig-zag 190 | 191 | assertEquals(4, bytes[7]); // length of second entry 192 | assertEquals(8, bytes[8]); 193 | assertEquals(3, bytes[9]); // Point(3, ) 194 | assertEquals(0x10, bytes[10]); 195 | assertEquals(8, bytes[11]); // Point (, 4) 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /src/test/java/com/fasterxml/jackson/dataformat/protobuf/WriteAsMapTest.java: -------------------------------------------------------------------------------- 1 | package com.fasterxml.jackson.dataformat.protobuf; 2 | 3 | import java.util.*; 4 | 5 | import com.fasterxml.jackson.databind.*; 6 | import com.fasterxml.jackson.dataformat.protobuf.schema.*; 7 | 8 | public class WriteAsMapTest extends ProtobufTestBase 9 | { 10 | final static String PROTOC = 11 | "package tutorial;\n" 12 | +"message Person {\n" 13 | +" required string name = 1;\n" 14 | +" required int32 id = 2;\n" 15 | +" optional string email = 3;\n" 16 | +" enum PhoneType {\n" 17 | +" MOBILE = 0;\n" 18 | +" HOME = 1;\n" 19 | +" WORK = 2;\n" 20 | +" }\n" 21 | +" message PhoneNumber {\n" 22 | +" required string number = 1;\n" 23 | +" optional PhoneType type = 2 [default = HOME];\n" 24 | +" }\n" 25 | +" repeated PhoneNumber phone = 4;\n" 26 | +"}\n" 27 | +"message AddressBook {\n" 28 | +" repeated Person person = 1;\n" 29 | +"}" 30 | ; 31 | 32 | 33 | public void testWriteAsMap() throws Exception 34 | { 35 | ObjectMapper mapper = new ProtobufMapper(); 36 | 37 | NativeProtobufSchema fileSchema = ProtobufSchemaLoader.std.parseNative(PROTOC); 38 | ProtobufSchema schema = fileSchema.forType("Person"); 39 | 40 | final ObjectWriter w = mapper.writer(schema); 41 | 42 | List> phones = new ArrayList<>(); 43 | 44 | Map phone1 = new HashMap<>(); 45 | phone1.put("type", 1); 46 | phone1.put("number", "123456"); 47 | phones.add(phone1); 48 | 49 | Map phone2 = new HashMap<>(); 50 | phone2.put("type", 0); 51 | phone2.put("number", "654321"); 52 | phones.add(phone2); 53 | 54 | Map person = new LinkedHashMap(); // it is ok if using HashMap. 55 | person.put("id", 1111); 56 | person.put("name", "aaaa"); 57 | person.put("phone", phones); 58 | 59 | byte[] bytes = w.writeValueAsBytes(person); 60 | 61 | // should add up to 33 bytes; with bug, less written 62 | assertEquals(33, bytes.length); 63 | 64 | // read 65 | ObjectReader r = mapper.readerFor(HashMap.class).with(schema); 66 | 67 | @SuppressWarnings("unchecked") 68 | Map person2 = (Map) r.readValue(bytes); 69 | assertEquals(person, person2); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/test/java/com/fasterxml/jackson/dataformat/protobuf/WriteBigArrayTest.java: -------------------------------------------------------------------------------- 1 | package com.fasterxml.jackson.dataformat.protobuf; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import com.fasterxml.jackson.databind.ObjectMapper; 7 | import com.fasterxml.jackson.databind.ObjectWriter; 8 | import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufSchema; 9 | import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufSchemaLoader; 10 | 11 | public class WriteBigArrayTest extends ProtobufTestBase 12 | { 13 | final protected static String PROTOC_STRING_ARRAY_SPARSE = "message Strings {\n" 14 | +" repeated string values = 1;\n" 15 | +"}\n" 16 | ; 17 | 18 | final protected static String PROTOC_STRING_ARRAY_PACKED = "message Strings {\n" 19 | +" repeated string values = 1 [packed=true];\n" 20 | +"}\n" 21 | ; 22 | 23 | final protected static String PROTOC_WRAPPED_STRING = "message WrappedStrings {\n" 24 | +" required Strings values = 2;\n" 25 | +"}\n" 26 | +PROTOC_STRING_ARRAY_SPARSE 27 | ; 28 | 29 | final ProtobufSchema SPARSE_STRING_SCHEMA; 30 | final ProtobufSchema PACKED_STRING_SCHEMA; 31 | 32 | public WriteBigArrayTest() throws Exception { 33 | SPARSE_STRING_SCHEMA = ProtobufSchemaLoader.std.parse(PROTOC_STRING_ARRAY_SPARSE); 34 | PACKED_STRING_SCHEMA = ProtobufSchemaLoader.std.parse(PROTOC_STRING_ARRAY_PACKED); 35 | } 36 | 37 | static class StringArray { 38 | public String[] values; 39 | 40 | public StringArray() { } 41 | public StringArray(List v) { 42 | values = v.toArray(new String[v.size()]); 43 | } 44 | 45 | public int size() { return values.length; } 46 | } 47 | 48 | static class StringArrayWrapper 49 | { 50 | public StringArray values; 51 | 52 | public StringArrayWrapper() { values = new StringArray(); } 53 | public StringArrayWrapper(List v) { 54 | values = new StringArray(v); 55 | } 56 | } 57 | 58 | /* 59 | /********************************************************** 60 | /* Test methods 61 | /********************************************************** 62 | */ 63 | 64 | public void testStringArraySparseWithLongValues() throws Exception 65 | { 66 | final ObjectMapper mapper = new ObjectMapper(new ProtobufFactory()); 67 | final ObjectWriter w = mapper.writer(SPARSE_STRING_SCHEMA); 68 | StringBuilder sb = new StringBuilder(); 69 | do { 70 | sb.append("Jexabel"); 71 | } while (sb.length() < 137); 72 | final String LONG_NAME = sb.toString(); 73 | final int longLen = LONG_NAME.length(); 74 | 75 | List strings = new ArrayList(); 76 | final int COUNT = 260000 / longLen; 77 | for (int i = 0; i < COUNT; ++i) { 78 | strings.add(LONG_NAME); 79 | } 80 | byte[] bytes = w.writeValueAsBytes(new StringArray(strings)); 81 | int ptr = 0; 82 | final byte FIRST_LEN_BYTE = (byte) (0x80 + (longLen & 0x7F)); 83 | final byte SECOND_LEN_BYTE = (byte) (longLen >> 7); 84 | 85 | // in case of sparse, same as N copies of a String field 86 | for (int i = 0; i < COUNT; ++i) { 87 | byte b = bytes[ptr++]; 88 | if (b != 0xA) { 89 | fail("Different for String #"+i+", at "+(ptr-1)+", type not 0xA but "+b); 90 | } 91 | assertEquals(FIRST_LEN_BYTE, bytes[ptr++]); 92 | assertEquals(SECOND_LEN_BYTE, bytes[ptr++]); 93 | for (int x = 0; x < longLen; ++x) { 94 | assertEquals((byte) LONG_NAME.charAt(x), bytes[ptr++]); 95 | } 96 | } 97 | assertEquals(bytes.length, ptr); 98 | } 99 | 100 | // and then do something bit more sizable 101 | public void testStringArraySparseLong() throws Exception 102 | { 103 | final int COUNT = 35000; 104 | final ObjectMapper mapper = new ObjectMapper(new ProtobufFactory()); 105 | final ObjectWriter w = mapper.writer(SPARSE_STRING_SCHEMA); 106 | List strings = new ArrayList(); 107 | for (int i = 0; i < COUNT; ++i) { 108 | strings.add("Value"+i); 109 | } 110 | byte[] bytes = w.writeValueAsBytes(new StringArray(strings)); 111 | int ptr = 0; 112 | 113 | // in case of sparse, same as N copies of a String field 114 | for (int i = 0; i < COUNT; ++i) { 115 | final String str = "Value"+i; 116 | byte b = bytes[ptr++]; 117 | if (b != 0xA) { 118 | fail("Different for String #"+i+", at "+(ptr-1)+", type not 0xA but "+b); 119 | } 120 | assertEquals(str.length(), bytes[ptr++]); 121 | for (int x = 0; x < str.length(); ++x) { 122 | assertEquals((byte) str.charAt(x), bytes[ptr++]); 123 | } 124 | } 125 | assertEquals(bytes.length, ptr); 126 | } 127 | 128 | public void testStringArrayPackedLong() throws Exception 129 | { 130 | final int COUNT = 39600; 131 | final ObjectMapper mapper = new ObjectMapper(new ProtobufFactory()); 132 | 133 | final ObjectWriter w = mapper.writer(PACKED_STRING_SCHEMA); 134 | List strings = new ArrayList(); 135 | for (int i = 0; i < COUNT; ++i) { 136 | strings.add("Value"+i); 137 | } 138 | byte[] bytes = w.writeValueAsBytes(new StringArray(strings)); 139 | int ptr = 0; 140 | 141 | assertEquals(0xA, bytes[ptr++]); 142 | 143 | // big enough to actually require 3 bytes (above 0x3FFF bytes) 144 | int len = (bytes[ptr] & 0x7F) + ((bytes[ptr+1] & 0x7F) << 7) 145 | + (bytes[ptr+2] << 14); 146 | ptr += 3; 147 | 148 | assertEquals(bytes.length - 4, len); 149 | 150 | // in case of sparse, same as N copies of a String field 151 | for (int i = 0; i < COUNT; ++i) { 152 | final String str = "Value"+i; 153 | assertEquals(str.length(), bytes[ptr++]); 154 | for (int x = 0; x < str.length(); ++x) { 155 | assertEquals((byte) str.charAt(x), bytes[ptr++]); 156 | } 157 | } 158 | assertEquals(bytes.length, ptr); 159 | } 160 | 161 | public void testWrappedStringArray() throws Exception 162 | { 163 | final int COUNT = 39600; 164 | final ObjectMapper mapper = new ObjectMapper(new ProtobufFactory()); 165 | final ProtobufSchema schema = ProtobufSchemaLoader.std.parse(PROTOC_WRAPPED_STRING); 166 | 167 | final ObjectWriter w = mapper.writer(schema); 168 | List strings = new ArrayList(); 169 | for (int i = 0; i < COUNT; ++i) { 170 | strings.add("Value"+i); 171 | } 172 | byte[] bytes = w.writeValueAsBytes(new StringArrayWrapper(strings)); 173 | 174 | // read back as well 175 | StringArrayWrapper result = mapper.readerFor(StringArrayWrapper.class) 176 | .with(schema) 177 | .readValue(bytes); 178 | assertEquals(COUNT, result.values.size()); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/test/java/com/fasterxml/jackson/dataformat/protobuf/WriteBinaryTest.java: -------------------------------------------------------------------------------- 1 | package com.fasterxml.jackson.dataformat.protobuf; 2 | 3 | import com.fasterxml.jackson.databind.*; 4 | import com.fasterxml.jackson.dataformat.protobuf.schema.*; 5 | 6 | public class WriteBinaryTest extends ProtobufTestBase 7 | { 8 | final protected static String PROTOC_BINARY = 9 | "message Name {\n" 10 | +" optional int32 id = 1;\n" 11 | +" required bytes data = 3;\n" 12 | +" required int32 trailer = 2;\n" 13 | +"}\n" 14 | ; 15 | 16 | static class Binary { 17 | public int id, trailer; 18 | public byte[] data; 19 | 20 | public Binary() { } 21 | public Binary(int id, byte[] data, int trailer) { 22 | this.id = id; 23 | this.data = data; 24 | this.trailer = trailer; 25 | } 26 | } 27 | 28 | /* 29 | /********************************************************** 30 | /* Test methods 31 | /********************************************************** 32 | */ 33 | 34 | private final ObjectMapper MAPPER = new ProtobufMapper(); 35 | 36 | public void testSimpleBinary() throws Exception 37 | { 38 | ProtobufSchema schema = ProtobufSchemaLoader.std.parse(PROTOC_BINARY); 39 | final ObjectWriter w = MAPPER.writer(schema); 40 | byte[] data = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }; 41 | Binary input = new Binary(123, data, 456); 42 | byte[] bytes = w.writeValueAsBytes(input); 43 | assertEquals(18, bytes.length); 44 | 45 | Binary result = MAPPER.readerFor(Binary.class).with(schema) 46 | .readValue(bytes); 47 | assertEquals(input.id, result.id); 48 | assertEquals(input.trailer, result.trailer); 49 | assertNotNull(result.data); 50 | assertEquals(data.length, result.data.length); 51 | 52 | for (int i = 0, len = data.length; i < len; ++i) { 53 | if (data[i] != result.data[i]) { 54 | fail("Binary data differs at #"+i); 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/test/java/com/fasterxml/jackson/dataformat/protobuf/WriteComplexPojoTest.java: -------------------------------------------------------------------------------- 1 | package com.fasterxml.jackson.dataformat.protobuf; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.fasterxml.jackson.databind.ObjectWriter; 5 | 6 | import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufSchema; 7 | import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufSchemaLoader; 8 | 9 | public class WriteComplexPojoTest extends ProtobufTestBase 10 | { 11 | final ObjectMapper MAPPER = new ObjectMapper(new ProtobufFactory()); 12 | 13 | /* 14 | /********************************************************** 15 | /* Test methods 16 | /********************************************************** 17 | */ 18 | 19 | public void testMediaItemSimple() throws Exception 20 | { 21 | ProtobufSchema schema = ProtobufSchemaLoader.std.parse(PROTOC_MEDIA_ITEM); 22 | final ObjectWriter w = MAPPER.writer(schema); 23 | byte[] bytes = w.writeValueAsBytes(MediaItem.buildItem()); 24 | 25 | assertNotNull(bytes); 26 | 27 | assertEquals(252, bytes.length); 28 | 29 | // let's read back for fun 30 | MediaItem output = MAPPER.readerFor(MediaItem.class) 31 | .with(schema) 32 | .readValue(bytes); 33 | assertNotNull(output); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/com/fasterxml/jackson/dataformat/protobuf/WriteErrorsTest.java: -------------------------------------------------------------------------------- 1 | package com.fasterxml.jackson.dataformat.protobuf; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | 5 | import com.fasterxml.jackson.core.JsonGenerator; 6 | import com.fasterxml.jackson.core.JsonProcessingException; 7 | import com.fasterxml.jackson.databind.ObjectMapper; 8 | import com.fasterxml.jackson.databind.ObjectWriter; 9 | import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufSchema; 10 | import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufSchemaLoader; 11 | 12 | /** 13 | * Unit tests for generation that trigger exceptions (or would 14 | * without suppression). 15 | */ 16 | public class WriteErrorsTest extends ProtobufTestBase 17 | { 18 | static class Point3D extends Point { 19 | public int z; 20 | 21 | protected Point3D() { } 22 | public Point3D(int x, int y, int z) { 23 | super(x, y); 24 | this.z = z; 25 | } 26 | } 27 | 28 | private final ObjectMapper MAPPER = new ObjectMapper(new ProtobufFactory()); 29 | 30 | public void testUnknownProperties() throws Exception 31 | { 32 | ProtobufSchema schema = ProtobufSchemaLoader.std.parse(PROTOC_BOX, "Point"); 33 | final ObjectWriter w = MAPPER.writerFor(Point3D.class) 34 | .with(schema); 35 | 36 | // First: if disabled, should get an error 37 | try { 38 | /*byte[] bytes =*/ w 39 | .without(JsonGenerator.Feature.IGNORE_UNKNOWN) 40 | .writeValueAsBytes(new Point3D(1, 2, 3)); 41 | } catch (JsonProcessingException e) { 42 | verifyException(e, "Unrecognized field 'z'"); 43 | } 44 | 45 | // but then not, if we disable checks 46 | ByteArrayOutputStream bytes = new ByteArrayOutputStream(); 47 | JsonGenerator g = MAPPER.getFactory().createGenerator(bytes); 48 | g.enable(JsonGenerator.Feature.IGNORE_UNKNOWN); 49 | g.setSchema(schema); 50 | 51 | g.writeStartObject(); 52 | g.writeFieldName("x"); 53 | g.writeNumber((short) 290); 54 | // unknown field 55 | g.writeFieldName("foo"); 56 | g.writeNumber(1.0f); 57 | // also, should be fine to let generator close the object... 58 | g.close(); 59 | 60 | byte[] b = bytes.toByteArray(); 61 | assertEquals(3, b.length); 62 | 63 | Point3D result = MAPPER.readerFor(Point3D.class).with(schema) 64 | .readValue(b); 65 | assertEquals(290, result.x); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/test/java/com/fasterxml/jackson/dataformat/protobuf/WriteSimpleTest.java: -------------------------------------------------------------------------------- 1 | package com.fasterxml.jackson.dataformat.protobuf; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | 7 | import com.fasterxml.jackson.annotation.JsonPropertyOrder; 8 | import com.fasterxml.jackson.core.JsonGenerator; 9 | import com.fasterxml.jackson.core.io.SerializedString; 10 | import com.fasterxml.jackson.databind.*; 11 | import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufSchema; 12 | import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufSchemaLoader; 13 | 14 | public class WriteSimpleTest extends ProtobufTestBase 15 | { 16 | static class Point3D extends Point { 17 | public int z; 18 | 19 | public Point3D(int x, int y, int z) { 20 | super(x, y); 21 | this.z = z; 22 | } 23 | } 24 | 25 | final protected static String PROTOC_ID_POINTS = 26 | "message OptionalPoint {\n" 27 | +" required int32 id = 1;\n" 28 | +" repeated Point points = 2;\n" 29 | +"}\n" 30 | +PROTOC_POINT; 31 | ; 32 | 33 | @JsonPropertyOrder({ "id", "point" }) 34 | static class IdPoints { 35 | public int id; 36 | public List points; 37 | 38 | protected IdPoints() { } 39 | 40 | public IdPoints(int id, int x, int y) { 41 | this.id = id; 42 | points = Arrays.asList(new Point(x, y)); 43 | } 44 | 45 | @Override 46 | public String toString() { 47 | return String.format("[id=%d, points=%d]", id, points); 48 | } 49 | 50 | @Override 51 | public boolean equals(Object o) { 52 | if (o == this) return true; 53 | if (o.getClass() != getClass()) return false; 54 | IdPoints other = (IdPoints) o; 55 | return (other.id == id) && points.equals(other.points); 56 | } 57 | } 58 | 59 | private final ObjectMapper MAPPER = new ObjectMapper(new ProtobufFactory()); 60 | 61 | /* 62 | /********************************************************** 63 | /* Test methods 64 | /********************************************************** 65 | */ 66 | 67 | public void testWritePointInt() throws Exception 68 | { 69 | ProtobufSchema schema = ProtobufSchemaLoader.std.parse(PROTOC_BOX, "Point"); 70 | final ObjectWriter w = MAPPER.writerFor(Point.class) 71 | .with(schema); 72 | Point input = new Point(7, 2); 73 | byte[] bytes = w.writeValueAsBytes(input); 74 | assertNotNull(bytes); 75 | 76 | // 4 bytes: 1 byte tags, 1 byte values 77 | assertEquals(4, bytes.length); 78 | assertEquals(8, bytes[0]); // wire type 0 (3 LSB), id of 1 (-> 0x8) 79 | assertEquals(7, bytes[1]); // VInt 7, no zig-zag 80 | assertEquals(0x10, bytes[2]); // wire type 0 (3 LSB), id of 2 (-> 0x10) 81 | assertEquals(4, bytes[3]); // VInt 2, but with zig-zag 82 | 83 | // Plus read back using mapper as well 84 | Point result = MAPPER.readerFor(Point.class) 85 | .with(schema) 86 | .readValue(bytes); 87 | assertEquals(input, result); 88 | } 89 | 90 | public void testWritePointLongFixed() throws Exception 91 | { 92 | ProtobufSchema schema = ProtobufSchemaLoader.std.parse(PROTOC_POINT_FL); 93 | final ObjectWriter w = MAPPER.writerFor(PointL.class) 94 | .with(schema); 95 | PointL input = new PointL(1, -1); 96 | byte[] bytes = w.writeValueAsBytes(input); 97 | 98 | assertNotNull(bytes); 99 | assertEquals(18, bytes.length); 100 | 101 | // read back using Mapper as well 102 | PointL result = MAPPER.readerFor(PointL.class) 103 | .with(schema) 104 | .readValue(bytes); 105 | assertEquals(input, result); 106 | } 107 | 108 | public void testWritePointDouble() throws Exception 109 | { 110 | ProtobufSchema schema = ProtobufSchemaLoader.std.parse(PROTOC_POINT_D); 111 | final ObjectWriter w = MAPPER.writerFor(PointD.class) 112 | .with(schema); 113 | PointD input = new PointD(-100.75, 0.375); 114 | byte[] bytes = w.writeValueAsBytes(input); 115 | assertNotNull(bytes); 116 | 117 | // read back using Mapper as well 118 | PointD result = MAPPER.readerFor(PointD.class) 119 | .with(schema) 120 | .readValue(bytes); 121 | assertEquals(input, result); 122 | } 123 | 124 | public void testWriteNameManual() throws Exception 125 | { 126 | ProtobufSchema schema = ProtobufSchemaLoader.std.parse(PROTOC_NAME); 127 | ByteArrayOutputStream bytes = new ByteArrayOutputStream(); 128 | JsonGenerator g = MAPPER.getFactory().createGenerator(bytes); 129 | g.setSchema(schema); 130 | 131 | g.writeStartObject(); 132 | g.writeFieldName("last"); 133 | char[] ch = "Ford".toCharArray(); 134 | g.writeString(ch, 0, ch.length); 135 | // call flush() for fun, at root level, to verify that works 136 | g.flush(); 137 | g.writeFieldName(new SerializedString("last")); 138 | byte[] b = "Bob".getBytes("UTF-8"); 139 | g.writeRawUTF8String(b, 0, b.length); 140 | g.writeEndObject(); 141 | g.close(); 142 | 143 | b = bytes.toByteArray(); 144 | assertNotNull(bytes); 145 | 146 | // 11 bytes: 2 tags, 2 length markers, Strings of 3 and 4 bytes 147 | assertEquals(11, b.length); 148 | } 149 | 150 | public void testWritePointWithLongsManual() throws Exception 151 | { 152 | ProtobufSchema schema = ProtobufSchemaLoader.std.parse(PROTOC_POINT_L); 153 | ByteArrayOutputStream bytes = new ByteArrayOutputStream(); 154 | JsonGenerator g = MAPPER.getFactory().createGenerator(bytes); 155 | g.setSchema(schema); 156 | 157 | g.writeStartObject(); 158 | g.writeFieldName("x"); 159 | g.writeNumber(Long.MAX_VALUE); 160 | g.writeFieldName("y"); 161 | g.writeNumber(Long.MAX_VALUE); 162 | g.writeEndObject(); 163 | g.close(); 164 | 165 | byte[] b = bytes.toByteArray(); 166 | assertNotNull(bytes); 167 | 168 | // 22 bytes: 1 byte tags, 10 byte values 169 | assertEquals(21, b.length); 170 | assertEquals(8, b[0]); // wire type 0 (3 LSB), id of 1 (-> 0x8) 171 | 172 | // 7 x 0xFF, last 0x7F -> 0x7F....FF, NOT using zig-zag 173 | assertEquals(0xFF, b[1] & 0xFF); 174 | assertEquals(0xFF, b[2] & 0xFF); 175 | assertEquals(0xFF, b[3] & 0xFF); 176 | assertEquals(0xFF, b[4] & 0xFF); 177 | assertEquals(0xFF, b[5] & 0xFF); 178 | assertEquals(0xFF, b[6] & 0xFF); 179 | assertEquals(0xFF, b[7] & 0xFF); 180 | assertEquals(0xFF, b[8] & 0xFF); 181 | assertEquals(0x7F, b[9] & 0x7F); 182 | 183 | assertEquals(0x10, b[10]); // wire type 0 (3 LSB), id of 2 (-> 0x10) 184 | 185 | // but 'y' will be using zig-zag 186 | assertEquals(0xFE, b[11] & 0xFF); 187 | assertEquals(0xFF, b[12] & 0xFF); 188 | assertEquals(0xFF, b[13] & 0xFF); 189 | assertEquals(0xFF, b[14] & 0xFF); 190 | assertEquals(0xFF, b[15] & 0xFF); 191 | assertEquals(0xFF, b[16] & 0xFF); 192 | assertEquals(0xFF, b[17] & 0xFF); 193 | assertEquals(0xFF, b[18] & 0xFF); 194 | assertEquals(0xFF, b[19] & 0xFF); 195 | assertEquals(0x01, b[20] & 0x01); 196 | } 197 | 198 | public void testBooleanAndNull() throws Exception 199 | { 200 | ProtobufSchema schema = ProtobufSchemaLoader.std.parse(PROTOC_OPTIONAL_VALUE); 201 | ObjectWriter w = MAPPER.writerFor(OptionalValue.class) 202 | .with(schema); 203 | OptionalValue input = new OptionalValue(false, null); 204 | byte[] bytes = w.writeValueAsBytes(input); 205 | assertNotNull(bytes); 206 | OptionalValue result = MAPPER.readerFor(OptionalValue.class) 207 | .with(schema) 208 | .readValue(bytes); 209 | assertEquals(input, result); 210 | 211 | // and another one with true 212 | input = new OptionalValue(true, "abc"); 213 | bytes = w.writeValueAsBytes(input); 214 | result = MAPPER.readerFor(OptionalValue.class) 215 | .with(schema) 216 | .readValue(bytes); 217 | assertEquals(input, result); 218 | } 219 | 220 | public void testIdPoint() throws Exception 221 | { 222 | ProtobufSchema schema = ProtobufSchemaLoader.std.parse(PROTOC_ID_POINTS); 223 | ObjectWriter w = MAPPER.writerFor(IdPoints.class) 224 | .with(schema); 225 | IdPoints input = new IdPoints(1, 100, -200); 226 | byte[] bytes = w.writeValueAsBytes(input); 227 | assertNotNull(bytes); 228 | IdPoints result = MAPPER.readerFor(IdPoints.class) 229 | .with(schema) 230 | .readValue(bytes); 231 | assertEquals(input, result); 232 | } 233 | 234 | public void testWriteCoord() throws Exception 235 | { 236 | ProtobufSchema schema = ProtobufSchemaLoader.std.parse(PROTOC_BOX, "Box"); 237 | schema = schema.withRootType("Box"); 238 | final ObjectWriter w = MAPPER.writerFor(Box.class) 239 | .with(schema); 240 | byte[] bytes = w.writeValueAsBytes(new Box(0x3F, 0x11, 0x18, 0xF)); 241 | assertNotNull(bytes); 242 | 243 | // 11 bytes for 2 Points; 4 single-byte ids, 3 x 2-byte values, 1 x 1-byte value 244 | // but then 2 x 2 bytes for tag, length 245 | 246 | // Root-level has no length-prefix; so we have sequence of Box fields (topLeft, bottomRight) 247 | // with ids of 3 and 5, respectively. 248 | // As child messages, they have typed-tag, then VInt-encoded length; lengths are 249 | // 4 byte each (typed tag, 1-byte ints) 250 | // It all adds up to 12 bytes as follows: 251 | 252 | /* 253 | "message Point {\n" 254 | +" required int32 x = 1;\n" 255 | +" required sint32 y = 2;\n" 256 | +"}\n" 257 | +"message Box {\n" 258 | +" required Point topLeft = 3;\n" 259 | +" required Point bottomRight = 5;\n" 260 | +"}\n" 261 | */ 262 | 263 | assertEquals(12, bytes.length); 264 | 265 | assertEquals(0x1A, bytes[0]); // wire type 2 (length-prefix), tag id 3 266 | assertEquals(0x4, bytes[1]); // length, 4 bytes 267 | assertEquals(0x8, bytes[2]); // wire type 0 (vint), tag id 1 268 | assertEquals(0x3F, bytes[3]); // vint value, 0x3F remains as is 269 | assertEquals(0x10, bytes[4]); // wire type 0 (vint), tag id 2 270 | assertEquals(0x22, bytes[5]); // zig-zagged vint value, 0x11 becomes 0x22 271 | 272 | assertEquals(0x2A, bytes[6]); // wire type 2 (length-prefix), tag id 5 273 | assertEquals(0x4, bytes[7]); // length, 4 bytes 274 | assertEquals(0x8, bytes[8]); // wire type 0 (vint), tag id 1 275 | assertEquals(0x18, bytes[9]); // vint value, 0x18 remains as is 276 | assertEquals(0x10, bytes[10]); // wire type 0 (vint), tag id 2 277 | assertEquals(0x1E, bytes[11]); // zig-zagged vint value, 0xF becomes 0x1E 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /src/test/java/com/fasterxml/jackson/dataformat/protobuf/WriteStringsTest.java: -------------------------------------------------------------------------------- 1 | package com.fasterxml.jackson.dataformat.protobuf; 2 | 3 | import java.io.*; 4 | 5 | import com.fasterxml.jackson.core.*; 6 | import com.fasterxml.jackson.databind.ObjectMapper; 7 | import com.fasterxml.jackson.databind.ObjectWriter; 8 | import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufSchema; 9 | import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufSchemaLoader; 10 | 11 | public class WriteStringsTest extends ProtobufTestBase 12 | { 13 | 14 | /* 15 | /********************************************************** 16 | /* Test methods 17 | /********************************************************** 18 | */ 19 | 20 | private final ObjectMapper MAPPER = new ObjectMapper(new ProtobufFactory()); 21 | 22 | private final ProtobufSchema NAME_SCHEMA; 23 | { 24 | try { 25 | NAME_SCHEMA = ProtobufSchemaLoader.std.parse(PROTOC_NAME); 26 | } catch (Exception e) { 27 | throw new RuntimeException(e); 28 | } 29 | } 30 | 31 | public void testSimpleShort() throws Exception 32 | { 33 | final ObjectWriter w = MAPPER.writer(NAME_SCHEMA); 34 | byte[] bytes = w.writeValueAsBytes(new Name("Bob", "Burger")); 35 | assertEquals(13, bytes.length); 36 | 37 | // at main level just seq of fields; first one 1 byte tag, 1 byte len, 3 chars -> 5 38 | // and second similarly 1 + 1 + 6 -> 8 39 | assertEquals(13, bytes.length); 40 | assertEquals(0x12, bytes[0]); // length-prefixed (2), field 2 41 | assertEquals(3, bytes[1]); // length for array 42 | assertEquals((byte) 'B', bytes[2]); 43 | assertEquals((byte) 'o', bytes[3]); 44 | assertEquals((byte) 'b', bytes[4]); 45 | 46 | assertEquals(0x3A, bytes[5]); // length-prefixed (2), field 7 47 | assertEquals(6, bytes[6]); // length for array 48 | assertEquals((byte) 'B', bytes[7]); 49 | assertEquals((byte) 'u', bytes[8]); 50 | assertEquals((byte) 'r', bytes[9]); 51 | assertEquals((byte) 'g', bytes[10]); 52 | assertEquals((byte) 'e', bytes[11]); 53 | assertEquals((byte) 'r', bytes[12]); 54 | } 55 | 56 | public void testSimpleLongAscii() throws Exception 57 | { 58 | _testSimpleLong(129, "Bob"); 59 | _testSimpleLong(2007, "Bill"); 60 | _testSimpleLong(9000, "Emily"); 61 | } 62 | 63 | public void testSimpleLongTwoByteUTF8() throws Exception 64 | { 65 | _testSimpleLong(90, "\u00A8a\u00F3"); 66 | _testSimpleLong(129, "\u00A8a\u00F3"); 67 | _testSimpleLong(2007, "\u00E8\u00EC"); 68 | _testSimpleLong(7000, "\u00A8xy"); 69 | } 70 | 71 | public void testSimpleLongThreeByteUTF8() throws Exception 72 | { 73 | _testSimpleLong(90, "\u2009\u3333"); 74 | _testSimpleLong(129, "\u2009\u3333"); 75 | _testSimpleLong(2007, "abc\u3333"); 76 | _testSimpleLong(5000, "\u2009b\u3333a"); 77 | } 78 | 79 | private void _testSimpleLong(int clen, String part) throws Exception 80 | { 81 | StringBuilder sb = new StringBuilder(); 82 | do { 83 | sb.append(part); 84 | } while (sb.length() < clen); 85 | final String longName = sb.toString(); 86 | _testSimpleLongMapper(longName); 87 | _testSimpleLongManual(longName); 88 | } 89 | 90 | private void _testSimpleLongManual(String longName) throws Exception 91 | { 92 | ByteArrayOutputStream bytes = new ByteArrayOutputStream(); 93 | JsonGenerator g = MAPPER.getFactory().createGenerator(bytes); 94 | g.enable(JsonGenerator.Feature.IGNORE_UNKNOWN); 95 | g.setSchema(NAME_SCHEMA); 96 | 97 | g.writeStartObject(); 98 | g.writeStringField("first", null); 99 | g.writeFieldName("last"); 100 | int nameLen = longName.length(); 101 | char[] ch = new char[nameLen + 10]; 102 | longName.getChars(0, nameLen, ch, 5); 103 | g.writeString(ch, 5, nameLen); 104 | g.writeEndObject(); 105 | g.close(); 106 | 107 | JsonParser p = MAPPER.getFactory().createParser(new ByteArrayInputStream(bytes.toByteArray())); 108 | p.setSchema(NAME_SCHEMA); 109 | 110 | assertToken(JsonToken.START_OBJECT, p.nextToken()); 111 | assertToken(JsonToken.FIELD_NAME, p.nextToken()); 112 | 113 | // Note: nulls are never explicitly written, but simple lead to omission of the field... 114 | assertEquals("last", p.getText()); 115 | assertToken(JsonToken.VALUE_STRING, p.nextToken()); 116 | ch = p.getTextCharacters(); 117 | String str = new String(ch, p.getTextOffset(), p.getTextLength()); 118 | assertEquals(longName, str); 119 | assertToken(JsonToken.END_OBJECT, p.nextToken()); 120 | p.close(); 121 | } 122 | 123 | private void _testSimpleLongMapper(String longName) throws Exception 124 | { 125 | final ObjectWriter w = MAPPER.writer(NAME_SCHEMA); 126 | final byte[] LONG_BYTES = longName.getBytes("UTF-8"); 127 | 128 | final int longLen = LONG_BYTES.length; 129 | 130 | byte[] bytes = w.writeValueAsBytes(new Name("Bill", longName)); 131 | // 4 or 5 bytes for fields (tag, length), 4 for first name, N for second 132 | int expLen = 8 + longLen; 133 | if (longLen > 127) { 134 | expLen += 1; 135 | } 136 | assertEquals(expLen, bytes.length); 137 | 138 | // at main level just seq of fields; first one 1 byte tag, 1 byte len, 3 chars -> 5 139 | // and second similarly 1 + 1 + 6 -> 8 140 | assertEquals(0x12, bytes[0]); 141 | assertEquals(4, bytes[1]); // length for array 142 | assertEquals((byte) 'B', bytes[2]); 143 | assertEquals((byte) 'i', bytes[3]); 144 | assertEquals((byte) 'l', bytes[4]); 145 | assertEquals((byte) 'l', bytes[5]); 146 | assertEquals(0x3A, bytes[6]); // length-prefixed (2), field 7 147 | 148 | int offset = 7; 149 | 150 | if (longLen <= 0x7F) { 151 | assertEquals((longLen & 0x7F), bytes[offset++] & 0xFF); // sign set for non-last length bytes 152 | } else { 153 | assertEquals(128 + (longLen & 0x7F), bytes[offset++] & 0xFF); // sign set for non-last length bytes 154 | assertEquals(longLen >> 7, bytes[offset++]); // no sign bit set 155 | } 156 | for (int i = 0; i < longLen; ++i) { 157 | assertEquals((byte) LONG_BYTES[i], bytes[offset+i]); 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/test/java/com/fasterxml/jackson/dataformat/protobuf/schemagen/SchemaGenTest.java: -------------------------------------------------------------------------------- 1 | package com.fasterxml.jackson.dataformat.protobuf.schemagen; 2 | 3 | import static org.junit.Assert.assertArrayEquals; 4 | 5 | import java.nio.ByteBuffer; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | import com.fasterxml.jackson.annotation.JsonProperty; 10 | import com.fasterxml.jackson.databind.ObjectMapper; 11 | import com.fasterxml.jackson.dataformat.protobuf.ProtobufFactory; 12 | import com.fasterxml.jackson.dataformat.protobuf.ProtobufMapper; 13 | import com.fasterxml.jackson.dataformat.protobuf.ProtobufTestBase; 14 | import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufMessage; 15 | import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufSchema; 16 | import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufSchemaLoader; 17 | 18 | public class SchemaGenTest extends ProtobufTestBase { 19 | 20 | public static class WithNestedClass { 21 | @JsonProperty(required = true) 22 | public String name; 23 | public NestedClass[] nestedClasses; 24 | public NestedEnum nestedEnum; 25 | 26 | public static class NestedClass { 27 | public int id; 28 | } 29 | 30 | public static enum NestedEnum { 31 | A, B, C; 32 | } 33 | } 34 | 35 | public static class WithIndexAnnotation { 36 | @JsonProperty(required = true, index = 1) 37 | public float f; 38 | 39 | @JsonProperty(index = 4) 40 | public boolean b; 41 | 42 | @JsonProperty(index = 3) 43 | public ByteBuffer bb; 44 | 45 | @JsonProperty(index = 2) 46 | public double d; 47 | } 48 | 49 | public static class RootType { 50 | public String name; 51 | 52 | public int value; 53 | 54 | public List other; 55 | } 56 | 57 | public static class Employee { 58 | @JsonProperty(required = true) 59 | public String name; 60 | 61 | @JsonProperty(required = true) 62 | public int age; 63 | 64 | public String[] emails; 65 | 66 | public Employee boss; 67 | } 68 | 69 | protected RootType buildRootType() { 70 | RootType rType = new RootType(); 71 | rType.name = "rTpye"; 72 | rType.value = 100; 73 | rType.other = new ArrayList(); 74 | rType.other.add("12345"); 75 | rType.other.add("abcdefg"); 76 | return rType; 77 | } 78 | 79 | protected Employee buildEmployee() { 80 | Employee empl = new Employee(); 81 | empl.name = "Bobbee"; 82 | empl.age = 39; 83 | empl.emails = new String[] { "bob@aol.com", "bobby@gmail.com" }; 84 | empl.boss = null; 85 | return empl; 86 | } 87 | 88 | public void testWithNestedClass() throws Exception { 89 | ObjectMapper mapper = new ObjectMapper(new ProtobufFactory()); 90 | ProtobufSchemaGenerator gen = new ProtobufSchemaGenerator(); 91 | mapper.acceptJsonFormatVisitor(WithNestedClass.class, gen); 92 | ProtobufSchema schemaWrapper = gen.getGeneratedSchema(); 93 | 94 | assertNotNull(schemaWrapper); 95 | 96 | // System.out.println(schemaWrapper.getSource().toString()); 97 | } 98 | 99 | public void testWithIndexAnnotation() throws Exception { 100 | ObjectMapper mapper = new ProtobufMapper(); 101 | ProtobufSchemaGenerator gen = new ProtobufSchemaGenerator(); 102 | mapper.acceptJsonFormatVisitor(WithIndexAnnotation.class, gen); 103 | ProtobufSchema schemaWrapper = gen.getGeneratedSchema(); 104 | 105 | assertNotNull(schemaWrapper); 106 | 107 | // System.out.println(schemaWrapper.getSource().toString()); 108 | 109 | ProtobufMessage pMessage = schemaWrapper.getRootType(); 110 | assertEquals("f", pMessage.field(1).name); 111 | assertEquals("d", pMessage.field(2).name); 112 | assertEquals("bb", pMessage.field(3).name); 113 | assertEquals("b", pMessage.field(4).name); 114 | } 115 | 116 | public void testSelfRefPojoGenProtobufSchema() throws Exception { 117 | ObjectMapper mapper = new ProtobufMapper(); 118 | ProtobufSchemaGenerator gen = new ProtobufSchemaGenerator(); 119 | mapper.acceptJsonFormatVisitor(Employee.class, gen); 120 | ProtobufSchema schemaWrapper = gen.getGeneratedSchema(); 121 | 122 | assertNotNull(schemaWrapper); 123 | 124 | ProtobufMessage pMessage = schemaWrapper.getRootType(); 125 | assertTrue(pMessage.field("name").required); 126 | assertFalse(pMessage.field("boss").required); 127 | 128 | String protoFile = schemaWrapper.getSource().toString(); 129 | // System.out.println(protoFile); 130 | 131 | Employee empl = buildEmployee(); 132 | 133 | byte[] byteMsg = mapper.writer(schemaWrapper).writeValueAsBytes(empl); 134 | // System.out.println(byteMsg); 135 | ProtobufSchema schema = ProtobufSchemaLoader.std.parse(protoFile); 136 | Employee newEmpl = mapper.readerFor(Employee.class).with(schema).readValue(byteMsg); 137 | 138 | // System.out.println(newEmpl); 139 | assertEquals(empl.name, newEmpl.name); 140 | assertEquals(empl.age, newEmpl.age); 141 | assertArrayEquals(empl.emails, newEmpl.emails); 142 | assertEquals(empl.boss, newEmpl.boss); 143 | } 144 | 145 | public void testComplexPojoGenProtobufSchema() throws Exception { 146 | ObjectMapper mapper = new ProtobufMapper(); 147 | ProtobufSchemaGenerator gen = new ProtobufSchemaGenerator(); 148 | mapper.acceptJsonFormatVisitor(MediaItem.class, gen); 149 | ProtobufSchema schemaWrapper = gen.getGeneratedSchema(); 150 | assertNotNull(schemaWrapper); 151 | 152 | String protoFile = schemaWrapper.getSource().toString(); 153 | // System.out.println(protoFile); 154 | 155 | MediaItem mediaItem = MediaItem.buildItem(); 156 | 157 | byte[] byteMsg = mapper.writerFor(MediaItem.class).with(schemaWrapper).writeValueAsBytes(mediaItem); 158 | // System.out.println(byteMsg); 159 | ProtobufSchema schema = ProtobufSchemaLoader.std.parse(protoFile); 160 | MediaItem deserMediaItem = mapper.readerFor(MediaItem.class).with(schema).readValue(byteMsg); 161 | 162 | // System.out.println(deserMediaItem); 163 | assertEquals(mediaItem, deserMediaItem); 164 | } 165 | 166 | public void testSimplePojoGenProtobufSchema() throws Exception { 167 | ObjectMapper mapper = new ProtobufMapper(); 168 | ProtobufSchemaGenerator gen = new ProtobufSchemaGenerator(); 169 | mapper.acceptJsonFormatVisitor(RootType.class, gen); 170 | ProtobufSchema schemaWrapper = gen.getGeneratedSchema(); 171 | 172 | assertNotNull(schemaWrapper); 173 | 174 | String protoFile = schemaWrapper.getSource().toString(); 175 | // System.out.println(protoFile); 176 | 177 | RootType rType = buildRootType(); 178 | 179 | byte[] msg = mapper.writerFor(RootType.class).with(schemaWrapper).writeValueAsBytes(rType); 180 | // System.out.println(msg); 181 | ProtobufSchema schema = ProtobufSchemaLoader.std.parse(protoFile); 182 | RootType parsedRootType = mapper.readerFor(RootType.class).with(schema).readValue(msg); 183 | 184 | // System.out.println(parsedRootType); 185 | assertEquals(rType.name, parsedRootType.name); 186 | assertEquals(rType.value, parsedRootType.value); 187 | assertEquals(rType.other, parsedRootType.other); 188 | } 189 | } 190 | --------------------------------------------------------------------------------