@Test public void structuredAndUnstructuredOptions() throws Exception { // From https://developers.google.com/protocol-buffers/docs/proto#options Schema schema = new SchemaBuilder() .add( "foo.proto", "" + "import \"google/protobuf/descriptor.proto\";\n" + "message FooOptions {\n" + " optional int32 opt1 = 1;\n" + " optional string opt2 = 2;\n" + "}\n" + "\n" + "extend google.protobuf.FieldOptions {\n" + " optional FooOptions foo_options = 1234;\n" + "}\n" + "\n" + "message Bar {\n" + " optional int32 a = 1 [(foo_options).opt1 = 123, (foo_options).opt2 = \"baz\"];\n" + " optional int32 b = 2 [(foo_options) = { opt1: 456 opt2: \"quux\" }];\n" + "}\n") .add("google/protobuf/descriptor.proto") .build(); ProtoMember fooOptions = ProtoMember.get(Options.FIELD_OPTIONS, "foo_options"); ProtoMember opt1 = ProtoMember.get(ProtoType.get("FooOptions"), "opt1"); ProtoMember opt2 = ProtoMember.get(ProtoType.get("FooOptions"), "opt2"); MessageType bar = (MessageType) schema.getType("Bar"); assertThat(bar.field("a").options().map()) .isEqualTo(ImmutableMap.of(fooOptions, ImmutableMap.of(opt1, "123", opt2, "baz"))); assertThat(bar.field("b").options().map()) .isEqualTo(ImmutableMap.of(fooOptions, ImmutableMap.of(opt1, "456", opt2, "quux"))); }
private void gatherFields(Multimap<ProtoType, ProtoMember> sink, ProtoType type, Object o) { if (o instanceof Map) { for (Map.Entry<?, ?> entry : ((Map<?, ?>) o).entrySet()) { ProtoMember protoMember = (ProtoMember) entry.getKey(); sink.put(type, protoMember); gatherFields(sink, protoMember.type(), entry.getValue()); } } else if (o instanceof List) { for (Object e : (List) o) { gatherFields(sink, type, e); } } }
Map<ProtoMember, Object> canonicalizeOption( Linker linker, ProtoType extensionType, OptionElement option) { Type type = linker.get(extensionType); if (!(type instanceof MessageType)) { return null; // No known extensions for the given extension type. } MessageType messageType = (MessageType) type; String[] path; Field field = messageType.field(option.name()); if (field != null) { // This is an option declared by descriptor.proto. path = new String[] {option.name()}; } else { // This is an option declared by an extension. Map<String, Field> extensionsForType = messageType.extensionFieldsMap(); path = resolveFieldPath(option.name(), extensionsForType.keySet()); String packageName = linker.packageName(); if (path == null && packageName != null) { // If the path couldn't be resolved, attempt again by prefixing it with the package name. path = resolveFieldPath(packageName + "." + option.name(), extensionsForType.keySet()); } if (path == null) { return null; // Unable to find the root of this field path. } field = extensionsForType.get(path[0]); } Map<ProtoMember, Object> result = new LinkedHashMap<>(); Map<ProtoMember, Object> last = result; ProtoType lastProtoType = messageType.type(); for (int i = 1; i < path.length; i++) { Map<ProtoMember, Object> nested = new LinkedHashMap<>(); last.put(ProtoMember.get(lastProtoType, field), nested); lastProtoType = field.type(); last = nested; field = linker.dereference(field, path[i]); if (field == null) { return null; // Unable to dereference this path segment. } } last.put( ProtoMember.get(lastProtoType, field), canonicalizeValue(linker, field, option.value())); return result; }
private Object canonicalizeValue(Linker linker, Field context, Object value) { if (value instanceof OptionElement) { ImmutableMap.Builder<ProtoMember, Object> result = ImmutableMap.builder(); OptionElement option = (OptionElement) value; Field field = linker.dereference(context, option.name()); if (field == null) { linker.addError("unable to resolve option %s on %s", option.name(), context.type()); } else { ProtoMember protoMember = ProtoMember.get(context.type(), field); result.put(protoMember, canonicalizeValue(linker, field, option.value())); } return coerceValueForField(context, result.build()); } if (value instanceof Map) { ImmutableMap.Builder<ProtoMember, Object> result = ImmutableMap.builder(); for (Map.Entry<?, ?> entry : ((Map<?, ?>) value).entrySet()) { String name = (String) entry.getKey(); Field field = linker.dereference(context, name); if (field == null) { linker.addError("unable to resolve option %s on %s", name, context.type()); } else { ProtoMember protoMember = ProtoMember.get(context.type(), field); result.put(protoMember, canonicalizeValue(linker, field, entry.getValue())); } } return coerceValueForField(context, result.build()); } if (value instanceof List) { ImmutableList.Builder<Object> result = ImmutableList.builder(); for (Object element : (List<?>) value) { result.addAll((List) canonicalizeValue(linker, context, element)); } return coerceValueForField(context, result.build()); } if (value instanceof String) { return coerceValueForField(context, value); } throw new IllegalArgumentException("Unexpected option value: " + value); }
@Test public void fullyQualifiedOptionFields() throws Exception { Schema schema = new SchemaBuilder() .add( "a/b/more_options.proto", "" + "syntax = \"proto2\";\n" + "package a.b;\n" + "\n" + "import \"google/protobuf/descriptor.proto\";\n" + "\n" + "extend google.protobuf.MessageOptions {\n" + " optional MoreOptions more_options = 17000;\n" + "}\n" + "\n" + "message MoreOptions {\n" + " extensions 100 to 200;\n" + "}\n") .add( "a/c/event_more_options.proto", "" + "syntax = \"proto2\";\n" + "package a.c;\n" + "\n" + "import \"a/b/more_options.proto\";\n" + "\n" + "extend a.b.MoreOptions {\n" + " optional EvenMoreOptions even_more_options = 100;\n" + "}\n" + "\n" + "message EvenMoreOptions {\n" + " optional string string_option = 1;\n" + "}\n") .add( "a/d/message.proto", "" + "syntax = \"proto2\";\n" + "package a.d;\n" + "\n" + "import \"a/b/more_options.proto\";\n" + "import \"a/c/event_more_options.proto\";\n" + "\n" + "message Message {\n" + " option (a.b.more_options) = {\n" + " [a.c.even_more_options]: {string_option: \"foo\"}\n" + " };\n" + "}\n") .add("google/protobuf/descriptor.proto") .build(); ProtoType moreOptionsType = ProtoType.get("a.b.MoreOptions"); ProtoType evenMoreOptionsType = ProtoType.get("a.c.EvenMoreOptions"); ProtoMember moreOptions = ProtoMember.get(Options.MESSAGE_OPTIONS, "a.b.more_options"); ProtoMember evenMoreOptions = ProtoMember.get(moreOptionsType, "a.c.even_more_options"); ProtoMember stringOption = ProtoMember.get(evenMoreOptionsType, "string_option"); MessageType message = (MessageType) schema.getType("a.d.Message"); assertThat(message.options().map()) .isEqualTo( ImmutableMap.of( moreOptions, ImmutableMap.of(evenMoreOptions, ImmutableMap.of(stringOption, "foo")))); }