ProtoTypeOptions.java
package net.morimekta.proto.gson;
import net.morimekta.collect.UnmodifiableMap;
import net.morimekta.collect.UnmodifiableSet;
import net.morimekta.proto.utils.ProtoTypeRegistry;
import net.morimekta.strings.NamingUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import static net.morimekta.collect.UnmodifiableMap.mapOf;
import static net.morimekta.collect.UnmodifiableMap.toMap;
import static net.morimekta.collect.UnmodifiableSet.setOf;
import static net.morimekta.collect.util.SetOperations.subtract;
import static net.morimekta.collect.util.SetOperations.union;
import static net.morimekta.proto.gson.ProtoTypeOptions.Option.FAIL_ON_NULL_VALUE;
import static net.morimekta.proto.gson.ProtoTypeOptions.Option.WRITE_DURATION_AS_STRING;
import static net.morimekta.proto.gson.ProtoTypeOptions.Option.WRITE_TIMESTAMP_AS_ISO;
import static net.morimekta.proto.gson.ProtoTypeOptions.Option.WRITE_UNPACKED_ANY;
import static net.morimekta.strings.NamingUtil.Format.CAMEL;
/**
* Class holding options to be used with proto handling.
*/
public final class ProtoTypeOptions {
/**
* Boolean options. All are false by default, and are enabled when needed.
*/
public enum Option {
/**
* Fail when encountering an enum value (from string) that is unknown.
*/
FAIL_ON_UNKNOWN_ENUM,
/**
* Fail when encountering an unknown field on a message.
*/
FAIL_ON_UNKNOWN_FIELD,
/**
* Fail when setting null values to fields.
*/
FAIL_ON_NULL_VALUE,
/**
* Ignore unknown types on <code>google.protobuf.Any</code> when parsing unwrapped
* messages.
*/
IGNORE_UNKNOWN_ANY_TYPE,
/**
* Use a lenient json reader when parsing messages.
*/
LENIENT_READER,
/**
* If set to true will write field names using the field ID.
*/
WRITE_FIELD_AS_NUMBER,
/**
* If set to true will write enum values using the enum number value.
*/
WRITE_ENUM_AS_NUMBER,
/**
* Allows using an unwrapped JSON in place of the binary content of an
* Any struct.
*/
WRITE_UNPACKED_ANY,
/**
* Allow using the <code>json.compact</code> compact notation for
* messages. If set to true the messages will be encoded as an array
* instead of an object.
*/
WRITE_COMPACT_MESSAGE,
/**
* Write google.proto.Timestamp values as ISO timestamp.
*/
WRITE_TIMESTAMP_AS_ISO,
/**
* Write google.proto.Duration as string value.
*/
WRITE_DURATION_AS_STRING,
}
/**
* String value keys that can be modified through the options.
*/
public enum Value {
/**
* When serializing Any as unpacked objects, use this field name for the
* type. Defaults to same as default JsonFormat.
*/
ANY_TYPE_FIELD_NAME("@type"),
/**
* When serializing Any as unpacked objects, use this prefix before the
* full name of the type. Defaults to same as proto default for Any.
*/
ANY_TYPE_PREFIX("type.googleapis.com/"),
;
private final String defaultValue;
Value(String defaultValue) {
this.defaultValue = defaultValue;
}
}
private final Set<Option> options;
private final UnmodifiableMap<Value, String> values;
private final ProtoTypeRegistry registry;
/**
* Default instance of options.
*/
public ProtoTypeOptions() {
this(setOf(FAIL_ON_NULL_VALUE, WRITE_UNPACKED_ANY, WRITE_TIMESTAMP_AS_ISO, WRITE_DURATION_AS_STRING),
mapOf(),
ProtoTypeRegistry.newBuilder().build());
}
/**
* Options set with initial values.
*
* @param options The boolean options.
* @param values The string values.
* @param registry The type registry for the options.
*/
public ProtoTypeOptions(Set<Option> options, Map<Value, String> values, ProtoTypeRegistry registry) {
this.options = UnmodifiableSet.asSet(options);
this.values = UnmodifiableMap.asMap(values);
this.registry = registry;
}
/**
* @return The associated type registry.
*/
public ProtoTypeRegistry getRegistry() {
return registry;
}
/**
* @param registry A type registry.
* @return Proto options with the associated registry.
*/
public ProtoTypeOptions withRegistry(ProtoTypeRegistry registry) {
return new ProtoTypeOptions(options, values, registry);
}
/**
* Check if an option is enabled.
*
* @param option The option to check.
* @return True if enabled, false otherwise.
*/
public boolean isEnabled(Option option) {
return options.contains(option);
}
/**
* Get a configurable string value.
*
* @param entry The value entry.
* @return The configured value or its default if not configured.
*/
public String getValue(Value entry) {
return values.getOrDefault(entry, entry.defaultValue);
}
/**
* Get options with the option enabled.
*
* @param option The option to enable.
* @return The modified options.
*/
public ProtoTypeOptions withEnabled(Option option) {
if (options.contains(option)) {
return this;
}
return new ProtoTypeOptions(union(options, setOf(option)), values, registry);
}
/**
* Get options with set of options enabled.
*
* @param options The option to enable.
* @return The modified options.
*/
public ProtoTypeOptions withEnabled(Option... options) {
Set<Option> enableAll = UnmodifiableSet.asSet(options);
if (this.options.containsAll(enableAll)) {
return this;
}
return new ProtoTypeOptions(union(this.options, enableAll), values, registry);
}
/**
* Get options with option disabled.
*
* @param option The option to disable.
* @return The modified options.
*/
public ProtoTypeOptions withDisabled(Option option) {
if (options.contains(option)) {
return new ProtoTypeOptions(subtract(options, setOf(option)), values, registry);
}
return this;
}
/**
* Get options with a value set.
*
* @param entry The value entry.
* @param value The value to be set.
* @return The modified options.
*/
public ProtoTypeOptions withValue(Value entry, String value) {
if (values.getOrDefault(entry, entry.defaultValue).equals(value)) {
return this;
}
if (entry.defaultValue.equals(value)) {
return withDefaultValue(entry);
}
return new ProtoTypeOptions(options, values.withEntry(entry, value), registry);
}
/**
* Get options with a value reset to default.
*
* @param entry The entry to reset to default.
* @return The modified options.
*/
public ProtoTypeOptions withDefaultValue(Value entry) {
if (values.containsKey(entry)) {
return new ProtoTypeOptions(options,
values.entrySet()
.stream()
.filter(e -> !e.getKey().equals(entry))
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue)),
registry);
}
return this;
}
// ---- Object ----
@Override
public String toString() {
List<String> features = new ArrayList<>();
this.options.stream().map(o -> NamingUtil.format(o.name(), CAMEL)).forEach(features::add);
this.values.forEach((entry, value) -> features.add(
NamingUtil.format(entry.name(), CAMEL) + "=" + value));
return "ProtoTypeOptions{" + String.join(", ", features) + '}';
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ProtoTypeOptions that = (ProtoTypeOptions) o;
return options.equals(that.options) &&
values.equals(that.values);
}
@Override
public int hashCode() {
return Objects.hash(ProtoTypeOptions.class, options, values);
}
}