992
* Parse a text-format message from {@code input} and merge the contents
993
* into {@code builder}.
995
public static void merge(final Readable input,
996
final Message.Builder builder)
998
merge(input, ExtensionRegistry.getEmptyRegistry(), builder);
1002
* Parse a text-format message from {@code input} and merge the contents
1003
* into {@code builder}.
1005
public static void merge(final CharSequence input,
1006
final Message.Builder builder)
1007
throws ParseException {
1008
merge(input, ExtensionRegistry.getEmptyRegistry(), builder);
1012
* Parse a text-format message from {@code input} and merge the contents
1013
* into {@code builder}. Extensions will be recognized if they are
1014
* registered in {@code extensionRegistry}.
1016
public static void merge(final Readable input,
1017
final ExtensionRegistry extensionRegistry,
1018
final Message.Builder builder)
1019
throws IOException {
1020
// Read the entire input to a String then parse that.
1022
// If StreamTokenizer were not quite so crippled, or if there were a kind
1023
// of Reader that could read in chunks that match some particular regex,
1024
// or if we wanted to write a custom Reader to tokenize our stream, then
1025
// we would not have to read to one big String. Alas, none of these is
1026
// the case. Oh well.
1028
merge(toStringBuilder(input), extensionRegistry, builder);
1031
private static final int BUFFER_SIZE = 4096;
1033
// TODO(chrisn): See if working around java.io.Reader#read(CharBuffer)
1034
// overhead is worthwhile
1035
private static StringBuilder toStringBuilder(final Readable input)
1036
throws IOException {
1037
final StringBuilder text = new StringBuilder();
1038
final CharBuffer buffer = CharBuffer.allocate(BUFFER_SIZE);
1040
final int n = input.read(buffer);
1045
text.append(buffer, 0, n);
1051
* Parse a text-format message from {@code input} and merge the contents
1052
* into {@code builder}. Extensions will be recognized if they are
1053
* registered in {@code extensionRegistry}.
1055
public static void merge(final CharSequence input,
1056
final ExtensionRegistry extensionRegistry,
1057
final Message.Builder builder)
1058
throws ParseException {
1059
final Tokenizer tokenizer = new Tokenizer(input);
1061
while (!tokenizer.atEnd()) {
1062
mergeField(tokenizer, extensionRegistry, builder);
1067
* Parse a single field from {@code tokenizer} and merge it into
1070
private static void mergeField(final Tokenizer tokenizer,
1071
final ExtensionRegistry extensionRegistry,
1072
final Message.Builder builder)
1073
throws ParseException {
1074
FieldDescriptor field;
1075
final Descriptor type = builder.getDescriptorForType();
1076
ExtensionRegistry.ExtensionInfo extension = null;
1078
if (tokenizer.tryConsume("[")) {
1080
final StringBuilder name =
1081
new StringBuilder(tokenizer.consumeIdentifier());
1082
while (tokenizer.tryConsume(".")) {
1084
name.append(tokenizer.consumeIdentifier());
1087
extension = extensionRegistry.findExtensionByName(name.toString());
1089
if (extension == null) {
1090
throw tokenizer.parseExceptionPreviousToken(
1091
"Extension \"" + name + "\" not found in the ExtensionRegistry.");
1092
} else if (extension.descriptor.getContainingType() != type) {
1093
throw tokenizer.parseExceptionPreviousToken(
1094
"Extension \"" + name + "\" does not extend message type \"" +
1095
type.getFullName() + "\".");
1098
tokenizer.consume("]");
1100
field = extension.descriptor;
1102
final String name = tokenizer.consumeIdentifier();
1103
field = type.findFieldByName(name);
1105
// Group names are expected to be capitalized as they appear in the
1106
// .proto file, which actually matches their type names, not their field
1108
if (field == null) {
1109
// Explicitly specify US locale so that this code does not break when
1110
// executing in Turkey.
1111
final String lowerName = name.toLowerCase(Locale.US);
1112
field = type.findFieldByName(lowerName);
1113
// If the case-insensitive match worked but the field is NOT a group,
1114
if (field != null && field.getType() != FieldDescriptor.Type.GROUP) {
1108
private static final Parser PARSER = Parser.newBuilder().build();
1111
* Return a {@link Parser} instance which can parse text-format
1112
* messages. The returned instance is thread-safe.
1114
public static Parser getParser() {
1119
* Parse a text-format message from {@code input} and merge the contents
1120
* into {@code builder}.
1122
public static void merge(final Readable input,
1123
final Message.Builder builder)
1124
throws IOException {
1125
PARSER.merge(input, builder);
1129
* Parse a text-format message from {@code input} and merge the contents
1130
* into {@code builder}.
1132
public static void merge(final CharSequence input,
1133
final Message.Builder builder)
1134
throws ParseException {
1135
PARSER.merge(input, builder);
1139
* Parse a text-format message from {@code input} and merge the contents
1140
* into {@code builder}. Extensions will be recognized if they are
1141
* registered in {@code extensionRegistry}.
1143
public static void merge(final Readable input,
1144
final ExtensionRegistry extensionRegistry,
1145
final Message.Builder builder)
1146
throws IOException {
1147
PARSER.merge(input, extensionRegistry, builder);
1152
* Parse a text-format message from {@code input} and merge the contents
1153
* into {@code builder}. Extensions will be recognized if they are
1154
* registered in {@code extensionRegistry}.
1156
public static void merge(final CharSequence input,
1157
final ExtensionRegistry extensionRegistry,
1158
final Message.Builder builder)
1159
throws ParseException {
1160
PARSER.merge(input, extensionRegistry, builder);
1165
* Parser for text-format proto2 instances. This class is thread-safe.
1166
* The implementation largely follows google/protobuf/text_format.cc.
1168
* <p>Use {@link TextFormat#getParser()} to obtain the default parser, or
1169
* {@link Builder} to control the parser behavior.
1171
public static class Parser {
1173
* Determines if repeated values for non-repeated fields and
1174
* oneofs are permitted. For example, given required/optional field "foo"
1175
* and a oneof containing "baz" and "qux":
1177
* <ul>"foo: 1 foo: 2"
1178
* <ul>"baz: 1 qux: 2"
1179
* <ul>merging "foo: 2" into a proto in which foo is already set, or
1180
* <ul>merging "qux: 2" into a proto in which baz is already set.
1183
public enum SingularOverwritePolicy {
1184
/** The last value is retained. */
1185
ALLOW_SINGULAR_OVERWRITES,
1186
/** An error is issued. */
1187
FORBID_SINGULAR_OVERWRITES
1190
private final boolean allowUnknownFields;
1191
private final SingularOverwritePolicy singularOverwritePolicy;
1193
private Parser(boolean allowUnknownFields,
1194
SingularOverwritePolicy singularOverwritePolicy) {
1195
this.allowUnknownFields = allowUnknownFields;
1196
this.singularOverwritePolicy = singularOverwritePolicy;
1200
* Returns a new instance of {@link Builder}.
1202
public static Builder newBuilder() {
1203
return new Builder();
1207
* Builder that can be used to obtain new instances of {@link Parser}.
1209
public static class Builder {
1210
private boolean allowUnknownFields = false;
1211
private SingularOverwritePolicy singularOverwritePolicy =
1212
SingularOverwritePolicy.ALLOW_SINGULAR_OVERWRITES;
1215
* Sets parser behavior when a non-repeated field appears more than once.
1217
public Builder setSingularOverwritePolicy(SingularOverwritePolicy p) {
1218
this.singularOverwritePolicy = p;
1222
public Parser build() {
1223
return new Parser(allowUnknownFields, singularOverwritePolicy);
1228
* Parse a text-format message from {@code input} and merge the contents
1229
* into {@code builder}.
1231
public void merge(final Readable input,
1232
final Message.Builder builder)
1233
throws IOException {
1234
merge(input, ExtensionRegistry.getEmptyRegistry(), builder);
1238
* Parse a text-format message from {@code input} and merge the contents
1239
* into {@code builder}.
1241
public void merge(final CharSequence input,
1242
final Message.Builder builder)
1243
throws ParseException {
1244
merge(input, ExtensionRegistry.getEmptyRegistry(), builder);
1248
* Parse a text-format message from {@code input} and merge the contents
1249
* into {@code builder}. Extensions will be recognized if they are
1250
* registered in {@code extensionRegistry}.
1252
public void merge(final Readable input,
1253
final ExtensionRegistry extensionRegistry,
1254
final Message.Builder builder)
1255
throws IOException {
1256
// Read the entire input to a String then parse that.
1258
// If StreamTokenizer were not quite so crippled, or if there were a kind
1259
// of Reader that could read in chunks that match some particular regex,
1260
// or if we wanted to write a custom Reader to tokenize our stream, then
1261
// we would not have to read to one big String. Alas, none of these is
1262
// the case. Oh well.
1264
merge(toStringBuilder(input), extensionRegistry, builder);
1268
private static final int BUFFER_SIZE = 4096;
1270
// TODO(chrisn): See if working around java.io.Reader#read(CharBuffer)
1271
// overhead is worthwhile
1272
private static StringBuilder toStringBuilder(final Readable input)
1273
throws IOException {
1274
final StringBuilder text = new StringBuilder();
1275
final CharBuffer buffer = CharBuffer.allocate(BUFFER_SIZE);
1277
final int n = input.read(buffer);
1282
text.append(buffer, 0, n);
1288
* Parse a text-format message from {@code input} and merge the contents
1289
* into {@code builder}. Extensions will be recognized if they are
1290
* registered in {@code extensionRegistry}.
1292
public void merge(final CharSequence input,
1293
final ExtensionRegistry extensionRegistry,
1294
final Message.Builder builder)
1295
throws ParseException {
1296
final Tokenizer tokenizer = new Tokenizer(input);
1297
MessageReflection.BuilderAdapter target =
1298
new MessageReflection.BuilderAdapter(builder);
1300
while (!tokenizer.atEnd()) {
1301
mergeField(tokenizer, extensionRegistry, target);
1307
* Parse a single field from {@code tokenizer} and merge it into
1310
private void mergeField(final Tokenizer tokenizer,
1311
final ExtensionRegistry extensionRegistry,
1312
final MessageReflection.MergeTarget target)
1313
throws ParseException {
1314
FieldDescriptor field = null;
1315
final Descriptor type = target.getDescriptorForType();
1316
ExtensionRegistry.ExtensionInfo extension = null;
1318
if (tokenizer.tryConsume("[")) {
1320
final StringBuilder name =
1321
new StringBuilder(tokenizer.consumeIdentifier());
1322
while (tokenizer.tryConsume(".")) {
1324
name.append(tokenizer.consumeIdentifier());
1327
extension = target.findExtensionByName(
1328
extensionRegistry, name.toString());
1330
if (extension == null) {
1331
if (!allowUnknownFields) {
1332
throw tokenizer.parseExceptionPreviousToken(
1333
"Extension \"" + name + "\" not found in the ExtensionRegistry.");
1336
"Extension \"" + name + "\" not found in the ExtensionRegistry.");
1339
if (extension.descriptor.getContainingType() != type) {
1340
throw tokenizer.parseExceptionPreviousToken(
1341
"Extension \"" + name + "\" does not extend message type \"" +
1342
type.getFullName() + "\".");
1344
field = extension.descriptor;
1347
tokenizer.consume("]");
1349
final String name = tokenizer.consumeIdentifier();
1350
field = type.findFieldByName(name);
1352
// Group names are expected to be capitalized as they appear in the
1353
// .proto file, which actually matches their type names, not their field
1355
if (field == null) {
1356
// Explicitly specify US locale so that this code does not break when
1357
// executing in Turkey.
1358
final String lowerName = name.toLowerCase(Locale.US);
1359
field = type.findFieldByName(lowerName);
1360
// If the case-insensitive match worked but the field is NOT a group,
1361
if (field != null && field.getType() != FieldDescriptor.Type.GROUP) {
1365
// Again, special-case group names as described above.
1366
if (field != null && field.getType() == FieldDescriptor.Type.GROUP &&
1367
!field.getMessageType().getName().equals(name)) {
1118
// Again, special-case group names as described above.
1119
if (field != null && field.getType() == FieldDescriptor.Type.GROUP &&
1120
!field.getMessageType().getName().equals(name)) {
1371
if (field == null) {
1372
if (!allowUnknownFields) {
1373
throw tokenizer.parseExceptionPreviousToken(
1374
"Message type \"" + type.getFullName() +
1375
"\" has no field named \"" + name + "\".");
1378
"Message type \"" + type.getFullName() +
1379
"\" has no field named \"" + name + "\".");
1384
// Skips unknown fields.
1124
1385
if (field == null) {
1125
throw tokenizer.parseExceptionPreviousToken(
1126
"Message type \"" + type.getFullName() +
1127
"\" has no field named \"" + name + "\".");
1131
Object value = null;
1133
if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) {
1134
tokenizer.tryConsume(":"); // optional
1136
final String endToken;
1386
// Try to guess the type of this field.
1387
// If this field is not a message, there should be a ":" between the
1388
// field name and the field value and also the field value should not
1389
// start with "{" or "<" which indicates the begining of a message body.
1390
// If there is no ":" or there is a "{" or "<" after ":", this field has
1391
// to be a message or the input is ill-formed.
1392
if (tokenizer.tryConsume(":") && !tokenizer.lookingAt("{") &&
1393
!tokenizer.lookingAt("<")) {
1394
skipFieldValue(tokenizer);
1396
skipFieldMessage(tokenizer);
1401
// Handle potential ':'.
1402
if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) {
1403
tokenizer.tryConsume(":"); // optional
1405
tokenizer.consume(":"); // required
1407
// Support specifying repeated field values as a comma-separated list.
1408
// Ex."foo: [1, 2, 3]"
1409
if (field.isRepeated() && tokenizer.tryConsume("[")) {
1411
consumeFieldValue(tokenizer, extensionRegistry, target, field, extension);
1412
if (tokenizer.tryConsume("]")) {
1416
tokenizer.consume(",");
1419
consumeFieldValue(tokenizer, extensionRegistry, target, field, extension);
1424
* Parse a single field value from {@code tokenizer} and merge it into
1427
private void consumeFieldValue(
1428
final Tokenizer tokenizer,
1429
final ExtensionRegistry extensionRegistry,
1430
final MessageReflection.MergeTarget target,
1431
final FieldDescriptor field,
1432
final ExtensionRegistry.ExtensionInfo extension)
1433
throws ParseException {
1434
Object value = null;
1436
if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) {
1437
final String endToken;
1438
if (tokenizer.tryConsume("<")) {
1441
tokenizer.consume("{");
1445
final MessageReflection.MergeTarget subField;
1446
subField = target.newMergeTargetForField(field,
1447
(extension == null) ? null : extension.defaultInstance);
1449
while (!tokenizer.tryConsume(endToken)) {
1450
if (tokenizer.atEnd()) {
1451
throw tokenizer.parseException(
1452
"Expected \"" + endToken + "\".");
1454
mergeField(tokenizer, extensionRegistry, subField);
1457
value = subField.finish();
1460
switch (field.getType()) {
1464
value = tokenizer.consumeInt32();
1470
value = tokenizer.consumeInt64();
1475
value = tokenizer.consumeUInt32();
1480
value = tokenizer.consumeUInt64();
1484
value = tokenizer.consumeFloat();
1488
value = tokenizer.consumeDouble();
1492
value = tokenizer.consumeBoolean();
1496
value = tokenizer.consumeString();
1500
value = tokenizer.consumeByteString();
1504
final EnumDescriptor enumType = field.getEnumType();
1506
if (tokenizer.lookingAtInteger()) {
1507
final int number = tokenizer.consumeInt32();
1508
value = enumType.findValueByNumber(number);
1509
if (value == null) {
1510
throw tokenizer.parseExceptionPreviousToken(
1511
"Enum type \"" + enumType.getFullName() +
1512
"\" has no value with number " + number + '.');
1515
final String id = tokenizer.consumeIdentifier();
1516
value = enumType.findValueByName(id);
1517
if (value == null) {
1518
throw tokenizer.parseExceptionPreviousToken(
1519
"Enum type \"" + enumType.getFullName() +
1520
"\" has no value named \"" + id + "\".");
1528
throw new RuntimeException("Can't get here.");
1532
if (field.isRepeated()) {
1533
target.addRepeatedField(field, value);
1534
} else if ((singularOverwritePolicy
1535
== SingularOverwritePolicy.FORBID_SINGULAR_OVERWRITES)
1536
&& target.hasField(field)) {
1537
throw tokenizer.parseExceptionPreviousToken("Non-repeated field \""
1538
+ field.getFullName() + "\" cannot be overwritten.");
1539
} else if ((singularOverwritePolicy
1540
== SingularOverwritePolicy.FORBID_SINGULAR_OVERWRITES)
1541
&& field.getContainingOneof() != null
1542
&& target.hasOneof(field.getContainingOneof())) {
1543
Descriptors.OneofDescriptor oneof = field.getContainingOneof();
1544
throw tokenizer.parseExceptionPreviousToken("Field \""
1545
+ field.getFullName() + "\" is specified along with field \""
1546
+ target.getOneofFieldDescriptor(oneof).getFullName()
1547
+ "\", another member of oneof \"" + oneof.getName() + "\".");
1549
target.setField(field, value);
1554
* Skips the next field including the field's name and value.
1556
private void skipField(Tokenizer tokenizer) throws ParseException {
1557
if (tokenizer.tryConsume("[")) {
1560
tokenizer.consumeIdentifier();
1561
} while (tokenizer.tryConsume("."));
1562
tokenizer.consume("]");
1564
tokenizer.consumeIdentifier();
1567
// Try to guess the type of this field.
1568
// If this field is not a message, there should be a ":" between the
1569
// field name and the field value and also the field value should not
1570
// start with "{" or "<" which indicates the begining of a message body.
1571
// If there is no ":" or there is a "{" or "<" after ":", this field has
1572
// to be a message or the input is ill-formed.
1573
if (tokenizer.tryConsume(":") && !tokenizer.lookingAt("<") &&
1574
!tokenizer.lookingAt("{")) {
1575
skipFieldValue(tokenizer);
1577
skipFieldMessage(tokenizer);
1579
// For historical reasons, fields may optionally be separated by commas or
1581
if (!tokenizer.tryConsume(";")) {
1582
tokenizer.tryConsume(",");
1587
* Skips the whole body of a message including the beginning delimeter and
1588
* the ending delimeter.
1590
private void skipFieldMessage(Tokenizer tokenizer) throws ParseException {
1591
final String delimiter;
1137
1592
if (tokenizer.tryConsume("<")) {
1140
1595
tokenizer.consume("{");
1144
final Message.Builder subBuilder;
1145
if (extension == null) {
1146
subBuilder = builder.newBuilderForField(field);
1148
subBuilder = extension.defaultInstance.newBuilderForType();
1151
while (!tokenizer.tryConsume(endToken)) {
1152
if (tokenizer.atEnd()) {
1153
throw tokenizer.parseException(
1154
"Expected \"" + endToken + "\".");
1156
mergeField(tokenizer, extensionRegistry, subBuilder);
1159
value = subBuilder.buildPartial();
1162
tokenizer.consume(":");
1164
switch (field.getType()) {
1168
value = tokenizer.consumeInt32();
1174
value = tokenizer.consumeInt64();
1179
value = tokenizer.consumeUInt32();
1184
value = tokenizer.consumeUInt64();
1188
value = tokenizer.consumeFloat();
1192
value = tokenizer.consumeDouble();
1196
value = tokenizer.consumeBoolean();
1200
value = tokenizer.consumeString();
1204
value = tokenizer.consumeByteString();
1208
final EnumDescriptor enumType = field.getEnumType();
1210
if (tokenizer.lookingAtInteger()) {
1211
final int number = tokenizer.consumeInt32();
1212
value = enumType.findValueByNumber(number);
1213
if (value == null) {
1214
throw tokenizer.parseExceptionPreviousToken(
1215
"Enum type \"" + enumType.getFullName() +
1216
"\" has no value with number " + number + '.');
1219
final String id = tokenizer.consumeIdentifier();
1220
value = enumType.findValueByName(id);
1221
if (value == null) {
1222
throw tokenizer.parseExceptionPreviousToken(
1223
"Enum type \"" + enumType.getFullName() +
1224
"\" has no value named \"" + id + "\".");
1232
throw new RuntimeException("Can't get here.");
1598
while (!tokenizer.lookingAt(">") && !tokenizer.lookingAt("}")) {
1599
skipField(tokenizer);
1601
tokenizer.consume(delimiter);
1236
if (field.isRepeated()) {
1237
builder.addRepeatedField(field, value);
1239
builder.setField(field, value);
1605
* Skips a field value.
1607
private void skipFieldValue(Tokenizer tokenizer) throws ParseException {
1608
if (tokenizer.tryConsumeString()) {
1609
while (tokenizer.tryConsumeString()) {}
1612
if (!tokenizer.tryConsumeIdentifier() && // includes enum & boolean
1613
!tokenizer.tryConsumeInt64() && // includes int32
1614
!tokenizer.tryConsumeUInt64() && // includes uint32
1615
!tokenizer.tryConsumeDouble() &&
1616
!tokenizer.tryConsumeFloat()) {
1617
throw tokenizer.parseException(
1618
"Invalid field value: " + tokenizer.currentToken);