Skip to content

Commit

Permalink
Support nested messages (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
stefan-ka authored Aug 13, 2020
1 parent a5245ec commit a562a7c
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 12 deletions.
67 changes: 62 additions & 5 deletions src/main/java/io/github/stefanka/protobufgen/model/Message.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@
import io.github.stefanka.protobufgen.exception.FieldAlreadyExistsException;
import io.github.stefanka.protobufgen.exception.FieldNumberAlreadyExistsException;

import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
import java.util.*;

/**
* Represents a protocol buffer message.
Expand All @@ -32,17 +30,28 @@ public class Message implements FieldType, Identifiable {
private Identifier name;
private String comment;
private Set<MessageField> fields;
private List<Message> nestedMessages;
private Message parent;

private Message() {
// use builder to create message
}

/**
* Returns the name of the message as string.
* Returns the full name of the message (including parents, if it is a nested message) as string.
*
* @return the name of the represented message
* @return the full name of the represented message
*/
public String getName() {
return isNestedMessage() ? parent.getName() + "." + name.toString() : name.toString();
}

/**
* Returns the simple name of the message (without parents, if it is a nested message) as string
*
* @return the simple name of the represented message
*/
public String getSimpleName() {
return name.toString();
}

Expand Down Expand Up @@ -74,16 +83,55 @@ public Set<MessageField> getFields() {
return new TreeSet<>(fields);
}

/**
* Returns the nested messages inside the represented message.
*
* @return a list with the nested messages inside the represented message
*/
public List<Message> getNestedMessages() {
return new LinkedList<>(nestedMessages);
}

/**
* Indicates whether the represented message is a nested message or not.
*
* @return true if the message is a nested message, false otherwise
*/
public boolean isNestedMessage() {
return parent != null;
}

/**
* Gives the parent message, in case the represented message is a nested message, null otherwise.
*
* @return the parent message of the nested message, or null if it is not a nested message
*/
public Message getParent() {
return parent;
}

/**
* Changes the parent message of the represented message.
*
* @param parent the new parent message
*/
protected void setParent(Message parent) {
this.parent = parent;
}

public static class Builder {
private final Identifier name;
private final Set<MessageField> messageFields;
private final List<Message> nestedMessages;
private String comment;
private int fieldCounter = 1;
private Message parent;

public Builder(String messageName) {
this.name = new Identifier(messageName);
this.comment = "";
this.messageFields = new TreeSet<>();
this.nestedMessages = new LinkedList<>();
}

public Builder withComment(String comment) {
Expand Down Expand Up @@ -111,10 +159,19 @@ public Builder withField(FieldType type, String name) {
return this;
}

public Builder withNestedMessage(Message message) {
this.nestedMessages.add(message);
return this;
}

public Message build() {
Message message = new Message();
message.name = this.name;
message.fields = new TreeSet<>(this.messageFields);
message.nestedMessages = new LinkedList<>(this.nestedMessages);
for (Message nested : nestedMessages) {
nested.setParent(message);
}
message.comment = this.comment;
return message;
}
Expand Down
25 changes: 18 additions & 7 deletions src/main/resources/template/proto.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,26 @@ import <#if import.isPublic()>public </#if>"${import.fileName}";
<#if importStatements?has_content>

</#if>
<#list messages as message>
<#macro renderMessage message indent>
<#if message.comment?has_content>
/* ${message.comment} */
${""?left_pad(indent * 2)}/* ${message.comment} */
</#if>
message ${message.name} {
<#list message.fields as field>
<#if field.repeated>repeated </#if>${field.type} ${field.name} = ${field.number};<#if field.comment?has_content> // ${field.comment}</#if>
</#list>
}
${""?left_pad(indent * 2)}message ${message.simpleName} {
<#if message.fields?has_content>
<#list message.fields as field>
${""?left_pad((indent + 1) * 2)}<#if field.repeated>repeated </#if>${field.type} ${field.name} = ${field.number};<#if field.comment?has_content> // ${field.comment}</#if>
</#list>
</#if>
<#if message.getNestedMessages()?has_content>

<#list message.getNestedMessages() as message>
<@renderMessage message indent + 1 />
</#list>
</#if>
${""?left_pad(indent * 2)}}
</#macro>
<#list messages as message>
<@renderMessage message 0 />

</#list>
<#list enums as enum>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,47 @@ public void canGiveFieldNumbersAutomatically() {
assertEquals(2, message.getFields().stream().filter(f -> f.getName().equals("testField2")).map(f -> f.getNumber()).findAny().get());
}

@Test
public void canNestMessages() {
// given
Message.Builder parentMessage = new Message.Builder("ParentMessage");
Message.Builder childMessage = new Message.Builder("ChildMessage");

// when
Message child = childMessage.build();
Message parent = parentMessage.withNestedMessage(child).build();

// then
assertEquals(1, parent.getNestedMessages().size());
assertTrue(child.isNestedMessage());
assertEquals("ParentMessage", child.getParent().getName());
assertEquals("ParentMessage.ChildMessage", child.getName());
}

@Test
public void canNestNestedMessages() {
// given
Message.Builder parentMessage = new Message.Builder("ParentMessage");
Message.Builder childMessage = new Message.Builder("ChildMessage");
Message.Builder childChildMessage = new Message.Builder("ChildChildMessage");

// when
Message childChild = childChildMessage.build();
Message child = childMessage.withNestedMessage(childChild).build();
Message parent = parentMessage.withNestedMessage(child).build();

// then
assertEquals(1, parent.getNestedMessages().size());
assertEquals(1, child.getNestedMessages().size());
assertTrue(child.isNestedMessage());
assertTrue(childChild.isNestedMessage());
assertEquals("ParentMessage", child.getParent().getName());
assertEquals("ParentMessage.ChildMessage", childChild.getParent().getName());
assertEquals("ParentMessage.ChildMessage.ChildChildMessage", childChild.getName());
assertEquals("ChildMessage", childChild.getParent().getSimpleName());
assertEquals("ChildChildMessage", childChild.getSimpleName());
}

@Test
public void canDetermineEquality() {
// given
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,39 @@ public void canSerializeStreamsKeywordsInRPC() throws IOException {
assertEquals(readIntegTestFile("streams-test.proto"), proto);
}

@Test
public void canSerializeNestedMessages() throws IOException {
// given
Message childChild = new Message.Builder("ChildChild")
.withField(STRING, "name")
.build();
Message child = new Message.Builder("Child")
.withField(STRING, "name")
.withNestedMessage(childChild)
.build();
Message parent = new Message.Builder("Parent")
.withField(STRING, "name")
.withNestedMessage(child)
.build();
Message refs = new Message.Builder("Refs")
.withField(STRING, "name")
.withField(parent, "ref1")
.withField(child, "ref2")
.withField(childChild, "ref3")
.build();
ProtoSpec spec = new ProtoSpec.Builder()
.withPackage("integTests.NestedTest")
.withMessage(parent)
.withMessage(refs)
.build();

// when
String proto = new ProtoSpecSerializer().serialize(spec);

// then
assertEquals(readIntegTestFile("nested-test.proto"), proto);
}

@Test
public void canWriteToFile() throws IOException {
// given
Expand Down
23 changes: 23 additions & 0 deletions src/test/resources/integTests/nested-test.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
syntax = "proto3";

package integTests.NestedTest;

message Parent {
string name = 1;

message Child {
string name = 1;

message ChildChild {
string name = 1;
}
}
}

message Refs {
string name = 1;
Parent ref1 = 2;
Parent.Child ref2 = 3;
Parent.Child.ChildChild ref3 = 4;
}

0 comments on commit a562a7c

Please sign in to comment.