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