Skip to content

Commit 75ec330

Browse files
committed
add descriptor reader
1 parent 172ee27 commit 75ec330

File tree

6 files changed

+1080
-0
lines changed

6 files changed

+1080
-0
lines changed
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
package com.fasterxml.jackson.dataformat.protobuf.schema;
2+
3+
4+
import com.fasterxml.jackson.databind.JsonNode;
5+
import com.fasterxml.jackson.dataformat.protobuf.ProtobufMapper;
6+
import com.squareup.protoparser.DataType;
7+
import com.squareup.protoparser.DataType.ScalarType;
8+
import com.squareup.protoparser.EnumConstantElement;
9+
import com.squareup.protoparser.EnumElement;
10+
import com.squareup.protoparser.FieldElement;
11+
import com.squareup.protoparser.MessageElement;
12+
import com.squareup.protoparser.OptionElement;
13+
import com.squareup.protoparser.ProtoFile;
14+
15+
import java.io.IOException;
16+
import java.io.InputStream;
17+
import java.net.URL;
18+
import java.util.Arrays;
19+
import java.util.HashMap;
20+
import java.util.Map;
21+
22+
public class DescriptorReader
23+
{
24+
private final String DESCRIPTOR_PROTO = "/descriptor.proto";
25+
private final ProtobufMapper descriptorMapper;
26+
private final ProtobufSchema descriptorFileSchema;
27+
28+
public DescriptorReader() throws IOException
29+
{
30+
// read Descriptor Proto
31+
descriptorMapper = new ProtobufMapper();
32+
InputStream in = getClass().getResourceAsStream(DESCRIPTOR_PROTO);
33+
descriptorFileSchema = ProtobufSchemaLoader.std.load(in, "FileDescriptorSet");
34+
in.close();
35+
}
36+
37+
public Map<String, ProtoFile> readFileDescriptorSet(String descriptorFilePath) throws IOException
38+
{
39+
InputStream fin;
40+
fin = this.getClass().getClassLoader().getResourceAsStream(descriptorFilePath);
41+
if (fin == null) {
42+
URL url = new URL(descriptorFilePath);
43+
fin = url.openConnection().getInputStream();
44+
}
45+
46+
JsonNode descriptorFile = descriptorMapper.readerFor(JsonNode.class)
47+
.with(descriptorFileSchema)
48+
.readValue(fin);
49+
50+
JsonNode fileDescriptorSet = descriptorFile.get("file");
51+
Map<String, JsonNode> fileDescMap = new HashMap<>();
52+
53+
for (JsonNode proto : fileDescriptorSet) {
54+
String protoName = proto.get("name").asText();
55+
fileDescMap.put(protoName, proto);
56+
}
57+
return buildProtoFileMap(fileDescMap);
58+
}
59+
60+
private Map<String, ProtoFile> buildProtoFileMap(Map<String, JsonNode> fileDescMap)
61+
{
62+
Map<String, ProtoFile> protoFileMap = new HashMap<>();
63+
for (Map.Entry<String, JsonNode> entry : fileDescMap.entrySet()) {
64+
String name = entry.getKey();
65+
JsonNode proto = entry.getValue();
66+
ProtoFile protoFile = buildProtoFile(proto, fileDescMap);
67+
protoFileMap.put(name, protoFile);
68+
}
69+
return protoFileMap;
70+
}
71+
72+
private ProtoFile buildProtoFile(JsonNode fileDescriptor, Map<String, JsonNode> fileDescMap)
73+
{
74+
String filePath = fileDescriptor.get("name").asText();
75+
String packageName = fileDescriptor.get("package").asText();
76+
77+
ProtoFile.Builder builder = ProtoFile.builder(filePath);
78+
builder.syntax(ProtoFile.Syntax.PROTO_3); // FIXME: does the desc file has the syntax version? if not, can proto2 be interpreted as proto3?
79+
builder.packageName(packageName);
80+
81+
// dependency file names.
82+
if (fileDescriptor.has("dependency")) {
83+
for (JsonNode n : fileDescriptor.get("dependency")) {
84+
JsonNode dep = fileDescMap.get(n.asText());
85+
for(JsonNode mt: dep.get("message_type")) {
86+
MessageElement me = buildMessageElement(mt);
87+
builder.addType(me);
88+
}
89+
}
90+
}
91+
92+
// FIXME: public dependency file names.
93+
// if (fileDescriptor.has("public_dependency")) {
94+
// for (JsonNode n : fileDescriptor.get("public_dependency")) {
95+
// String dep = fileDescriptor.get("dependency").get(n.asInt()).asText();
96+
// builder.addPublicDependency(dep);
97+
// }
98+
// }
99+
100+
// types
101+
for (JsonNode n : fileDescriptor.get("message_type")) {
102+
MessageElement me = buildMessageElement(n);
103+
builder.addType(me);
104+
}
105+
106+
// FIXME: implement following features
107+
// services
108+
// extendDeclarations
109+
// options
110+
111+
return builder.build();
112+
}
113+
114+
115+
/**
116+
* Register field types, and declare message types at the same time.
117+
*/
118+
private MessageElement buildMessageElement(JsonNode m)
119+
{
120+
MessageElement.Builder messageElementBuilder = MessageElement.builder();
121+
messageElementBuilder.name(m.get("name").asText());
122+
// fields
123+
if (m.has("field")) {
124+
for (JsonNode f : m.get("field")) {
125+
DataType dataType;
126+
String fieldName = f.get("name").asText();
127+
String type = f.get("type").asText();
128+
FieldElement.Label label = FieldElement.Label.valueOf(f.get("label")
129+
.asText()
130+
.substring(6));
131+
// message and enum fields are named fields
132+
if (type.equals("TYPE_MESSAGE") || type.equals("TYPE_ENUM")) {
133+
String fullyQualifiedtypeName = f.get("type_name").asText(); // fully qualified name including package name.
134+
String typeName = fullyQualifiedtypeName.substring(fullyQualifiedtypeName.indexOf(".", 2) + 1);
135+
dataType = DataType.NamedType.create(typeName);
136+
} else {
137+
dataType = ScalarType.valueOf(type.substring(5));
138+
}
139+
140+
// build field
141+
FieldElement.Builder fieldBuilder = FieldElement
142+
.builder()
143+
.name(fieldName)
144+
.type(dataType)
145+
.label(label)
146+
.tag(f.get("number").asInt());
147+
148+
// add field options to the field
149+
for (String optionFieldName : Arrays.asList("json_name")) {
150+
if (f.has(optionFieldName)) {
151+
JsonNode o = f.get(optionFieldName);
152+
OptionElement.Kind kind = OptionElement.Kind.STRING;
153+
OptionElement option = OptionElement.create(optionFieldName, kind, o.asText());
154+
fieldBuilder.addOption(option);
155+
}
156+
}
157+
158+
if (f.has("options")) {
159+
JsonNode os = f.get("options");
160+
for (String optionFieldName : Arrays.asList("packed", "default")) {
161+
if (os.has(optionFieldName)) {
162+
JsonNode o = os.get(optionFieldName);
163+
OptionElement.Kind kind = OptionElement.Kind.STRING;
164+
OptionElement option = OptionElement.create(optionFieldName, kind, o.asText());
165+
fieldBuilder.addOption(option);
166+
}
167+
}
168+
}
169+
// add the field to the message
170+
messageElementBuilder.addField(fieldBuilder.build());
171+
}
172+
}
173+
174+
// message type declarations
175+
if (m.has("nested_type")) {
176+
for (JsonNode n : m.get("nested_type")) {
177+
messageElementBuilder.addType(buildMessageElement(n));
178+
}
179+
}
180+
181+
// enum declarations
182+
if (m.has("enum_type")) {
183+
for (JsonNode e : m.get("enum_type")) {
184+
EnumElement.Builder nestedEnumElement = EnumElement
185+
.builder()
186+
.name(e.get("name").asText());
187+
for (JsonNode v : e.get("value")) {
188+
EnumConstantElement.Builder c = EnumConstantElement.builder()
189+
.name(v.get("name").asText())
190+
.tag(v.get("number").asInt());
191+
nestedEnumElement.addConstant(c.build());
192+
}
193+
messageElementBuilder.addType(nestedEnumElement.build());
194+
}
195+
}
196+
return messageElementBuilder.build();
197+
}
198+
199+
200+
}

0 commit comments

Comments
 (0)