Skip to content

Commit ac45a46

Browse files
committed
Merge branch '2.x' into 3.1
# Conflicts: # protobuf/src/main/java/tools/jackson/dataformat/protobuf/ProtobufGenerator.java # protobuf/src/main/java/tools/jackson/dataformat/protobuf/ProtobufParser.java # protobuf/src/test/java/tools/jackson/dataformat/protobuf/MapField712Test.java # protobuf/src/test/java/tools/jackson/dataformat/protobuf/schema/DescriptorMapField712Test.java
2 parents 3d437fc + 7882cd6 commit ac45a46

14 files changed

Lines changed: 2220 additions & 151 deletions

File tree

protobuf/src/main/java/tools/jackson/dataformat/protobuf/ProtobufGenerator.java

Lines changed: 186 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,12 @@ public JsonGenerator writeName(String name) throws JacksonException
217217
if (!_inObject) {
218218
_reportError("Cannot write a property name: current context not Object but "+_streamWriteContext.typeDesc());
219219
}
220+
// [dataformats-binary#712] Within a `map`, each "field name" is a map key
221+
// that opens a new entry sub-message
222+
if (_streamWriteContext.inMap()) {
223+
_startMapEntry(name);
224+
return this;
225+
}
220226
ProtobufField f = _currField;
221227
// important: use current field only if NOT repeated field; repeated
222228
// field means an array until START_OBJECT
@@ -249,8 +255,14 @@ public JsonGenerator writeName(SerializableString sstr) throws JacksonException
249255
if (!_inObject) {
250256
_reportError("Cannot write a property name: current context not Object but "+_streamWriteContext.typeDesc());
251257
}
252-
ProtobufField f = _currField;
253258
final String name = sstr.getValue();
259+
// [dataformats-binary#712] Within a `map`, each "field name" is a map key
260+
// that opens a new entry sub-message
261+
if (_streamWriteContext.inMap()) {
262+
_startMapEntry(name);
263+
return this;
264+
}
265+
ProtobufField f = _currField;
254266
// important: use current field only if NOT repeated field; repeated
255267
// field means an array until START_OBJECT
256268
// NOTE: not ideal -- depends on if it really is sibling field of an array,
@@ -360,6 +372,11 @@ public JsonGenerator writeStartArray() throws JacksonException
360372
if (_currField == null) { // just a sanity check
361373
return _reportError("Can not write START_ARRAY without field (message type "+_currMessage.getName()+")");
362374
}
375+
// [dataformats-binary#712] A `map` field is also "repeated" underneath, but
376+
// must be written as an Object, not an Array
377+
if (_currField.isMap) {
378+
_reportError("Can not write START_ARRAY: field '"+_currField.name+"' is a `map`; write START_OBJECT instead");
379+
}
363380
if (!_currField.isArray()) {
364381
_reportError("Can not write START_ARRAY: field '"+_currField.name+"' not declared as 'repeated'");
365382
}
@@ -429,6 +446,17 @@ public JsonGenerator writeStartObject() throws JacksonException
429446
}
430447
_currMessage = _schema.getRootType();
431448
// note: no buffering on root
449+
} else if (_currField.isMap) {
450+
// [dataformats-binary#712] a `map<K,V>` field: the Object being opened is
451+
// a sequence of entry sub-messages, not a single one. Push a dedicated map
452+
// context; per-entry buffering happens in writeFieldName. No buffering of
453+
// the map as a whole.
454+
_streamWriteContext = _streamWriteContext.createChildMapContext(_currField);
455+
streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth());
456+
_currMessage = _currField.getMessageType(); // the synthetic entry message
457+
_inObject = true;
458+
_writeTag = true;
459+
return this;
432460
} else {
433461
// but also, field value must be Message if so
434462
if (!_currField.isObject) {
@@ -464,6 +492,28 @@ public JsonGenerator writeEndObject() throws JacksonException
464492
if (!_inObject) {
465493
_reportError("Current context not Object but "+_streamWriteContext.typeDesc());
466494
}
495+
// [dataformats-binary#712] Closing a `map`: finalize the last open entry (if
496+
// any), then pop -- but do NOT finish-buffer the map as a whole (each entry
497+
// was already length-prefixed on its own).
498+
if (_streamWriteContext.inMap()) {
499+
if (_streamWriteContext.isEntryOpen()) {
500+
_streamWriteContext.setEntryOpen(false);
501+
_finishBuffering();
502+
}
503+
_streamWriteContext = _streamWriteContext.getParent();
504+
if (_streamWriteContext.inRoot()) {
505+
if (!_complete) {
506+
_complete();
507+
}
508+
} else {
509+
_currMessage = _streamWriteContext.getMessageType();
510+
}
511+
_currField = _streamWriteContext.getField();
512+
boolean inObj = _streamWriteContext.inObject();
513+
_inObject = inObj;
514+
_writeTag = inObj || !_streamWriteContext.inArray() || !_currField.packed;
515+
return this;
516+
}
467517
_streamWriteContext = _streamWriteContext.getParent();
468518
if (_streamWriteContext.inRoot()) {
469519
if (!_complete) {
@@ -1719,6 +1769,141 @@ private final int _writeTag(int ptr)
17191769
return ptr;
17201770
}
17211771

1772+
/*
1773+
/**********************************************************
1774+
/* Internal map (`map<K,V>`) writes [dataformats-binary#712]
1775+
/**********************************************************
1776+
*/
1777+
1778+
/**
1779+
* Opens a new {@code map} entry sub-message for the given key, finalizing the
1780+
* previous entry first if one is still open. Writes the entry's key (tag 1) and
1781+
* leaves {@link #_currField} pointing at the value field (tag 2) so the value
1782+
* write that follows targets it.
1783+
*/
1784+
private void _startMapEntry(String name) throws JacksonException
1785+
{
1786+
final ProtobufField mapField = _streamWriteContext.getField();
1787+
// Close the previous entry, if any (its length prefix is finalized here)
1788+
if (_streamWriteContext.isEntryOpen()) {
1789+
_streamWriteContext.setEntryOpen(false);
1790+
_finishBuffering();
1791+
}
1792+
// Each entry is a length-delimited sub-message, tagged with the map field's tag
1793+
_startBuffering(mapField.typedTag);
1794+
_streamWriteContext.setEntryOpen(true);
1795+
_currMessage = mapField.getMessageType();
1796+
// Write key (tag 1) ...
1797+
_writeTag = true;
1798+
_writeMapKey(mapField.getKeyField(), name);
1799+
// ... then prime the value field (tag 2) for the value write that follows
1800+
_currField = mapField.getValueField();
1801+
_writeTag = true;
1802+
}
1803+
1804+
/**
1805+
* Writes a map key (always arriving as a {@code String}) using the key field's
1806+
* declared protobuf type. Key types are restricted (and validated during schema
1807+
* resolution) to integral / {@code bool} / {@code string}.
1808+
*/
1809+
private void _writeMapKey(ProtobufField keyField, String name) throws JacksonException
1810+
{
1811+
_currField = keyField;
1812+
switch (keyField.type) {
1813+
case STRING:
1814+
writeString(name);
1815+
break;
1816+
case BOOLEAN:
1817+
writeBoolean(_parseBooleanMapKey(name, keyField));
1818+
break;
1819+
case VINT32_Z: // `sint32`: signed only
1820+
writeNumber(_parseIntMapKey(name, keyField, false));
1821+
break;
1822+
case VINT32_STD: // `int32` or `uint32`
1823+
case FIXINT32: // `sfixed32` or `fixed32`
1824+
writeNumber(_parseIntMapKey(name, keyField, true));
1825+
break;
1826+
case VINT64_Z: // `sint64`: signed only
1827+
writeNumber(_parseLongMapKey(name, keyField, false));
1828+
break;
1829+
case VINT64_STD: // `int64` or `uint64`
1830+
case FIXINT64: // `sfixed64` or `fixed64`
1831+
writeNumber(_parseLongMapKey(name, keyField, true));
1832+
break;
1833+
default:
1834+
// Should not happen: key types are validated during schema resolution
1835+
_reportError("Unsupported `map` key type "+keyField.type+" for field '"+keyField.name+"'");
1836+
}
1837+
}
1838+
1839+
/**
1840+
* Parses a 32-bit {@code map} key.
1841+
*<p>
1842+
* {@link FieldType} does not distinguish signed from unsigned declarations
1843+
* ({@code int32} and {@code uint32} are both {@code VINT32_STD}), so for those the
1844+
* accepted range is the union of the two: {@code [Integer.MIN_VALUE, 0xFFFFFFFF]}.
1845+
* Narrowing a value above {@code Integer.MAX_VALUE} keeps the same 32 bits, which is
1846+
* exactly the {@code uint32} / {@code fixed32} encoding.
1847+
*
1848+
* @param allowUnsigned Whether the key type has an unsigned variant; {@code false}
1849+
* for {@code sint32}, which is always signed.
1850+
*/
1851+
private int _parseIntMapKey(String name, ProtobufField keyField, boolean allowUnsigned)
1852+
throws JacksonException
1853+
{
1854+
final long l;
1855+
try {
1856+
l = Long.parseLong(name);
1857+
} catch (NumberFormatException e) {
1858+
_reportError("Invalid `map` key \""+name+"\" for integral key field '"+keyField.name
1859+
+"' (type "+keyField.type+")");
1860+
return 0; // never reached
1861+
}
1862+
final long max = allowUnsigned ? 0xFFFFFFFFL : Integer.MAX_VALUE;
1863+
if ((l < Integer.MIN_VALUE) || (l > max)) {
1864+
_reportError("Invalid `map` key \""+name+"\" for integral key field '"+keyField.name
1865+
+"' (type "+keyField.type+"): out of range ("+Integer.MIN_VALUE+" to "+max+")");
1866+
}
1867+
return (int) l;
1868+
}
1869+
1870+
/**
1871+
* Parses a 64-bit {@code map} key. As with 32-bit keys the signed and unsigned
1872+
* declarations share a {@link FieldType}, so {@code uint64} keys above
1873+
* {@link Long#MAX_VALUE} are accepted as unsigned: the resulting bit pattern is the
1874+
* same 64-bit varint either way.
1875+
*
1876+
* @param allowUnsigned Whether the key type has an unsigned variant; {@code false}
1877+
* for {@code sint64}, which is always signed.
1878+
*/
1879+
private long _parseLongMapKey(String name, ProtobufField keyField, boolean allowUnsigned)
1880+
throws JacksonException
1881+
{
1882+
try {
1883+
return Long.parseLong(name);
1884+
} catch (NumberFormatException e) {
1885+
if (allowUnsigned) {
1886+
try {
1887+
return Long.parseUnsignedLong(name);
1888+
} catch (NumberFormatException e2) { }
1889+
}
1890+
_reportError("Invalid `map` key \""+name+"\" for integral key field '"+keyField.name
1891+
+"' (type "+keyField.type+")");
1892+
return 0L; // never reached
1893+
}
1894+
}
1895+
1896+
private boolean _parseBooleanMapKey(String name, ProtobufField keyField) throws JacksonException {
1897+
if ("true".equals(name)) {
1898+
return true;
1899+
}
1900+
if ("false".equals(name)) {
1901+
return false;
1902+
}
1903+
_reportError("Invalid `map` key \""+name+"\" for boolean key field '"+keyField.name+"'");
1904+
return false; // never reached
1905+
}
1906+
17221907
/**
17231908
* Method called when buffering an entry that should be prefixed
17241909
* with a type tag.

0 commit comments

Comments
 (0)