Skip to content

Commit a562a7c

Browse files
authored
Support nested messages (#1)
1 parent a5245ec commit a562a7c

File tree

5 files changed

+177
-12
lines changed

5 files changed

+177
-12
lines changed

src/main/java/io/github/stefanka/protobufgen/model/Message.java

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,7 @@
1818
import io.github.stefanka.protobufgen.exception.FieldAlreadyExistsException;
1919
import io.github.stefanka.protobufgen.exception.FieldNumberAlreadyExistsException;
2020

21-
import java.util.Objects;
22-
import java.util.Set;
23-
import java.util.TreeSet;
21+
import java.util.*;
2422

2523
/**
2624
* Represents a protocol buffer message.
@@ -32,17 +30,28 @@ public class Message implements FieldType, Identifiable {
3230
private Identifier name;
3331
private String comment;
3432
private Set<MessageField> fields;
33+
private List<Message> nestedMessages;
34+
private Message parent;
3535

3636
private Message() {
3737
// use builder to create message
3838
}
3939

4040
/**
41-
* Returns the name of the message as string.
41+
* Returns the full name of the message (including parents, if it is a nested message) as string.
4242
*
43-
* @return the name of the represented message
43+
* @return the full name of the represented message
4444
*/
4545
public String getName() {
46+
return isNestedMessage() ? parent.getName() + "." + name.toString() : name.toString();
47+
}
48+
49+
/**
50+
* Returns the simple name of the message (without parents, if it is a nested message) as string
51+
*
52+
* @return the simple name of the represented message
53+
*/
54+
public String getSimpleName() {
4655
return name.toString();
4756
}
4857

@@ -74,16 +83,55 @@ public Set<MessageField> getFields() {
7483
return new TreeSet<>(fields);
7584
}
7685

86+
/**
87+
* Returns the nested messages inside the represented message.
88+
*
89+
* @return a list with the nested messages inside the represented message
90+
*/
91+
public List<Message> getNestedMessages() {
92+
return new LinkedList<>(nestedMessages);
93+
}
94+
95+
/**
96+
* Indicates whether the represented message is a nested message or not.
97+
*
98+
* @return true if the message is a nested message, false otherwise
99+
*/
100+
public boolean isNestedMessage() {
101+
return parent != null;
102+
}
103+
104+
/**
105+
* Gives the parent message, in case the represented message is a nested message, null otherwise.
106+
*
107+
* @return the parent message of the nested message, or null if it is not a nested message
108+
*/
109+
public Message getParent() {
110+
return parent;
111+
}
112+
113+
/**
114+
* Changes the parent message of the represented message.
115+
*
116+
* @param parent the new parent message
117+
*/
118+
protected void setParent(Message parent) {
119+
this.parent = parent;
120+
}
121+
77122
public static class Builder {
78123
private final Identifier name;
79124
private final Set<MessageField> messageFields;
125+
private final List<Message> nestedMessages;
80126
private String comment;
81127
private int fieldCounter = 1;
128+
private Message parent;
82129

83130
public Builder(String messageName) {
84131
this.name = new Identifier(messageName);
85132
this.comment = "";
86133
this.messageFields = new TreeSet<>();
134+
this.nestedMessages = new LinkedList<>();
87135
}
88136

89137
public Builder withComment(String comment) {
@@ -111,10 +159,19 @@ public Builder withField(FieldType type, String name) {
111159
return this;
112160
}
113161

162+
public Builder withNestedMessage(Message message) {
163+
this.nestedMessages.add(message);
164+
return this;
165+
}
166+
114167
public Message build() {
115168
Message message = new Message();
116169
message.name = this.name;
117170
message.fields = new TreeSet<>(this.messageFields);
171+
message.nestedMessages = new LinkedList<>(this.nestedMessages);
172+
for (Message nested : nestedMessages) {
173+
nested.setParent(message);
174+
}
118175
message.comment = this.comment;
119176
return message;
120177
}

src/main/resources/template/proto.ftl

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,26 @@ import <#if import.isPublic()>public </#if>"${import.fileName}";
1010
<#if importStatements?has_content>
1111

1212
</#if>
13-
<#list messages as message>
13+
<#macro renderMessage message indent>
1414
<#if message.comment?has_content>
15-
/* ${message.comment} */
15+
${""?left_pad(indent * 2)}/* ${message.comment} */
1616
</#if>
17-
message ${message.name} {
18-
<#list message.fields as field>
19-
<#if field.repeated>repeated </#if>${field.type} ${field.name} = ${field.number};<#if field.comment?has_content> // ${field.comment}</#if>
20-
</#list>
21-
}
17+
${""?left_pad(indent * 2)}message ${message.simpleName} {
18+
<#if message.fields?has_content>
19+
<#list message.fields as field>
20+
${""?left_pad((indent + 1) * 2)}<#if field.repeated>repeated </#if>${field.type} ${field.name} = ${field.number};<#if field.comment?has_content> // ${field.comment}</#if>
21+
</#list>
22+
</#if>
23+
<#if message.getNestedMessages()?has_content>
24+
25+
<#list message.getNestedMessages() as message>
26+
<@renderMessage message indent + 1 />
27+
</#list>
28+
</#if>
29+
${""?left_pad(indent * 2)}}
30+
</#macro>
31+
<#list messages as message>
32+
<@renderMessage message 0 />
2233

2334
</#list>
2435
<#list enums as enum>

src/test/java/io/github/stefanka/protobufgen/model/MessageTest.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,47 @@ public void canGiveFieldNumbersAutomatically() {
122122
assertEquals(2, message.getFields().stream().filter(f -> f.getName().equals("testField2")).map(f -> f.getNumber()).findAny().get());
123123
}
124124

125+
@Test
126+
public void canNestMessages() {
127+
// given
128+
Message.Builder parentMessage = new Message.Builder("ParentMessage");
129+
Message.Builder childMessage = new Message.Builder("ChildMessage");
130+
131+
// when
132+
Message child = childMessage.build();
133+
Message parent = parentMessage.withNestedMessage(child).build();
134+
135+
// then
136+
assertEquals(1, parent.getNestedMessages().size());
137+
assertTrue(child.isNestedMessage());
138+
assertEquals("ParentMessage", child.getParent().getName());
139+
assertEquals("ParentMessage.ChildMessage", child.getName());
140+
}
141+
142+
@Test
143+
public void canNestNestedMessages() {
144+
// given
145+
Message.Builder parentMessage = new Message.Builder("ParentMessage");
146+
Message.Builder childMessage = new Message.Builder("ChildMessage");
147+
Message.Builder childChildMessage = new Message.Builder("ChildChildMessage");
148+
149+
// when
150+
Message childChild = childChildMessage.build();
151+
Message child = childMessage.withNestedMessage(childChild).build();
152+
Message parent = parentMessage.withNestedMessage(child).build();
153+
154+
// then
155+
assertEquals(1, parent.getNestedMessages().size());
156+
assertEquals(1, child.getNestedMessages().size());
157+
assertTrue(child.isNestedMessage());
158+
assertTrue(childChild.isNestedMessage());
159+
assertEquals("ParentMessage", child.getParent().getName());
160+
assertEquals("ParentMessage.ChildMessage", childChild.getParent().getName());
161+
assertEquals("ParentMessage.ChildMessage.ChildChildMessage", childChild.getName());
162+
assertEquals("ChildMessage", childChild.getParent().getSimpleName());
163+
assertEquals("ChildChildMessage", childChild.getSimpleName());
164+
}
165+
125166
@Test
126167
public void canDetermineEquality() {
127168
// given

src/test/java/io/github/stefanka/protobufgen/serializer/ProtoSpecSerializerTest.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,39 @@ public void canSerializeStreamsKeywordsInRPC() throws IOException {
134134
assertEquals(readIntegTestFile("streams-test.proto"), proto);
135135
}
136136

137+
@Test
138+
public void canSerializeNestedMessages() throws IOException {
139+
// given
140+
Message childChild = new Message.Builder("ChildChild")
141+
.withField(STRING, "name")
142+
.build();
143+
Message child = new Message.Builder("Child")
144+
.withField(STRING, "name")
145+
.withNestedMessage(childChild)
146+
.build();
147+
Message parent = new Message.Builder("Parent")
148+
.withField(STRING, "name")
149+
.withNestedMessage(child)
150+
.build();
151+
Message refs = new Message.Builder("Refs")
152+
.withField(STRING, "name")
153+
.withField(parent, "ref1")
154+
.withField(child, "ref2")
155+
.withField(childChild, "ref3")
156+
.build();
157+
ProtoSpec spec = new ProtoSpec.Builder()
158+
.withPackage("integTests.NestedTest")
159+
.withMessage(parent)
160+
.withMessage(refs)
161+
.build();
162+
163+
// when
164+
String proto = new ProtoSpecSerializer().serialize(spec);
165+
166+
// then
167+
assertEquals(readIntegTestFile("nested-test.proto"), proto);
168+
}
169+
137170
@Test
138171
public void canWriteToFile() throws IOException {
139172
// given
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
syntax = "proto3";
2+
3+
package integTests.NestedTest;
4+
5+
message Parent {
6+
string name = 1;
7+
8+
message Child {
9+
string name = 1;
10+
11+
message ChildChild {
12+
string name = 1;
13+
}
14+
}
15+
}
16+
17+
message Refs {
18+
string name = 1;
19+
Parent ref1 = 2;
20+
Parent.Child ref2 = 3;
21+
Parent.Child.ChildChild ref3 = 4;
22+
}
23+

0 commit comments

Comments
 (0)