Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ISPN-14724 Create a simple DSL to build Protocol Buffers schema #10761

Merged
merged 2 commits into from
Apr 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package org.infinispan.api.protostream.builder;

public class FieldBuilder {

private final MessageBuilder parent;
private String name;
private final int number;
private final boolean required;
private final String type;

private IndexedFieldBuilder indexing = null;
private boolean indexEmbedded = false;

public FieldBuilder(MessageBuilder parent, String name, int number, boolean required, String type) {
this.parent = parent;
this.name = name;
this.number = number;
this.required = required;
this.type = type;
}

public MessageBuilder message(String name) {
return parent.parent.message(name);
}

public FieldBuilder required(String name, int number, String type) {
return parent.required(name, number, type);
}

public FieldBuilder optional(String name, int number, String type) {
return parent.optional(name, number, type);
}

public IndexedFieldBuilder basic() {
indexing = new IndexedFieldBuilder(this, "@Basic");
return indexing;
}

public IndexedFieldBuilder keyword() {
indexing = new IndexedFieldBuilder(this, "@Keyword");
return indexing;
}

public IndexedFieldBuilder text() {
indexing = new IndexedFieldBuilder(this, "@Text");
return indexing;
}

public FieldBuilder embedded() {
indexEmbedded = true;
return this;
}

public String build() {
return parent.build();
}

void write(StringBuilder builder) {
if (indexing != null) {
indexing.write(builder);
} else if (indexEmbedded) {
ProtoBuf.tab(builder);
builder.append("/**\n");
ProtoBuf.tab(builder);
builder.append(" * @Embedded\n");
ProtoBuf.tab(builder);
builder.append(" */\n");
}

// optional string name = 1;\n" +
if (required) {
ProtoBuf.tab(builder);
builder.append("required ");
} else {
ProtoBuf.tab(builder);
builder.append("optional ");
}

builder.append(type);
builder.append(" ");
builder.append(name);
builder.append(" = ");
builder.append(number);
builder.append(";");
ProtoBuf.blankLine(builder);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package org.infinispan.api.protostream.builder;

public class IndexedFieldBuilder {

private final FieldBuilder parent;

// TODO ISPN-14724 Create subclasses for each indexing type
// So that we can limit the options here accordingly.
private final String indexing;

private boolean custom = false;

private Boolean searchable;
private Boolean sortable;
private Boolean projectable;
private Boolean aggregable;
private String indexNullAs;
private String analyzer;
private String searchAnalyzer;
private String normalizer;

public IndexedFieldBuilder(FieldBuilder parent, String indexing) {
this.parent = parent;
this.indexing = indexing;
}

public MessageBuilder message(String name) {
return parent.message(name);
}

public FieldBuilder required(String name, int number, String type) {
return parent.required(name, number, type);
}

public FieldBuilder optional(String name, int number, String type) {
return parent.optional(name, number, type);
}

public IndexedFieldBuilder searchable(boolean value) {
custom = true;
this.searchable = value;
return this;
}

public IndexedFieldBuilder sortable(boolean value) {
custom = true;
this.sortable = value;
return this;
}

public IndexedFieldBuilder projectable(boolean value) {
custom = true;
this.projectable = value;
return this;
}

public IndexedFieldBuilder aggregable(boolean value) {
custom = true;
this.aggregable = value;
return this;
}

public IndexedFieldBuilder indexNullAs(String value) {
custom = true;
this.indexNullAs = value;
return this;
}

public IndexedFieldBuilder analyzer(String value) {
custom = true;
this.analyzer = value;
return this;
}

public IndexedFieldBuilder searchAnalyzer(String value) {
custom = true;
this.searchAnalyzer = value;
return this;
}

public IndexedFieldBuilder normalizer(String value) {
custom = true;
this.normalizer = value;
return this;
}

void write(StringBuilder builder) {
ProtoBuf.tab(builder);
builder.append("/**\n");
ProtoBuf.tab(builder);
builder.append(" * ");
builder.append(indexing);

writeCustomization(builder);

builder.append("\n");
ProtoBuf.tab(builder);
builder.append(" */\n");
}

private void writeCustomization(StringBuilder builder) {
if (!custom) {
return;
}

builder.append("(");
boolean first = true;

if (searchable != null) {
writeAttribute(builder, "searchable", searchable, first);
first = false;
}
if (sortable != null) {
writeAttribute(builder, "sortable", sortable, first);
first = false;
}
if (projectable != null) {
writeAttribute(builder, "projectable", projectable, first);
first = false;
}
if (aggregable != null) {
writeAttribute(builder, "aggregable", aggregable, first);
first = false;
}
if (indexNullAs != null) {
writeAttribute(builder, "indexNullAs", indexNullAs, first);
first = false;
}
if (analyzer != null) {
writeAttribute(builder, "analyzer", analyzer, first);
first = false;
}
if (searchAnalyzer != null) {
writeAttribute(builder, "searchAnalyzer", searchAnalyzer, first);
first = false;
}
if (normalizer != null) {
writeAttribute(builder, "normalizer", normalizer, first);
}

builder.append(")");
}

private void writeAttribute(StringBuilder builder, String attribute, Object value, boolean first) {
if (!first) {
builder.append(", ");
}
builder.append(attribute);
builder.append("=");

if (value instanceof String) {
value = "\"" + value + "\"";
}
builder.append(value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package org.infinispan.api.protostream.builder;

import java.util.ArrayList;
import java.util.List;

public class MessageBuilder {

final ProtoBuf parent;
private final String name;

private final List<FieldBuilder> fields = new ArrayList<>();
private boolean indexed = false;

public MessageBuilder(ProtoBuf parent, String name) {
this.parent = parent;
this.name = name;
}

public MessageBuilder indexed() {
indexed = true;
return this;
}

public FieldBuilder required(String name, int number, String type) {
FieldBuilder field = new FieldBuilder(this, name, number, true, type);
fields.add(field);
return field;
}

public FieldBuilder optional(String name, int number, String type) {
FieldBuilder field = new FieldBuilder(this, name, number, false, type);
fields.add(field);
return field;
}

public String build() {
return parent.build();
}

void write(StringBuilder builder) {
if (indexed) {
builder.append("/**\n");
builder.append(" * @Indexed\n");
builder.append(" */\n");
}

builder.append("message ");
builder.append(name);
builder.append(" {");
ProtoBuf.blankLine(builder);

for (FieldBuilder field : fields) {
field.write(builder);
}

builder.append("}");
ProtoBuf.blankLine(builder);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package org.infinispan.api.protostream.builder;

import java.util.ArrayList;
import java.util.List;

public final class ProtoBuf {

public static ProtoBuf builder() {
return new ProtoBuf();
}

private final List<MessageBuilder> messages = new ArrayList<>();
private String packageName;

private ProtoBuf() {
}

public ProtoBuf packageName(String packageName) {
this.packageName = packageName;
return this;
}

public MessageBuilder message(String name) {
MessageBuilder message = new MessageBuilder(this, name);
messages.add(message);
return message;
}

public String build() {
StringBuilder builder = new StringBuilder();
builder.append("syntax = \"proto2\";");
ProtoBuf.blankLine(builder);

if (packageName != null && !packageName.isBlank()) {
builder.append("package ");
builder.append(packageName);
builder.append(";");
ProtoBuf.blankLine(builder);
}

for (MessageBuilder message : messages) {
message.write(builder);
}
return builder.toString();
}

static void blankLine(StringBuilder builder) {
builder.append("\n\n");
}

static void tab(StringBuilder builder) {
builder.append(" ");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package org.infinispan.api.protostream.builder;

public class ProtoBufBuilderTest {

public void testProtoBuilder() {
ProtoBuf.builder()
.packageName("org.infinispan")
.message("author") // protobuf message is usually lowercase
.indexed()
.required("name", 1, "string")
.basic()
.sortable(true)
.projectable(true)
.optional("age", 2, "int32")
.keyword()
.sortable(true)
.aggregable(true)
.message("book")
.indexed()
.required("title", 1, "string")
.basic()
.projectable(true)
.optional("yearOfPublication", 2, "int32")
.keyword()
.normalizer("lowercase")
.optional("description", 3, "string")
.text()
.analyzer("english")
.searchAnalyzer("whitespace")
.required("author", 4, "author")
.embedded()
.build();
}
}