ProtoMessageBuilder.java

package net.morimekta.proto;

import com.google.protobuf.Descriptors;
import com.google.protobuf.Message;
import net.morimekta.proto.utils.ValueUtil;

import java.util.Objects;
import java.util.Optional;

import static java.util.Objects.requireNonNull;
import static net.morimekta.proto.utils.FieldUtil.getDefaultFieldValue;
import static net.morimekta.proto.utils.FieldUtil.getDefaultTypeValue;

/**
 * A wrapper around a proto message builder to behave more like it's actual builder
 * with methods reflecting both getters and setters.
 */
public class ProtoMessageBuilder
        extends ProtoMessageOrBuilder {
    /**
     * @param builder Message builder to wrap.
     */
    public ProtoMessageBuilder(Message.Builder builder) {
        super(builder);
    }

    /**
     * @param descriptor Message descriptor to get builder for.
     */
    public ProtoMessageBuilder(Descriptors.Descriptor descriptor) {
        this(ProtoMessage.newBuilder(descriptor));
    }

    /**
     * @param type Message type to get builder for.
     */
    public ProtoMessageBuilder(Class<?> type) {
        this(ProtoMessage.newBuilder(type));
    }

    @Override
    public Message.Builder getMessage() {
        return (Message.Builder) super.getMessage();
    }

    @Override
    public <T> T get(Descriptors.FieldDescriptor field) {
        requireNonNull(field, "field == null");
        return getInternal(field);
    }

    @Override
    public <T> Optional<T> optional(Descriptors.FieldDescriptor field) {
        requireNonNull(field, "field == null");
        return optionalInternal(field);
    }

    /**
     * Set the field value.
     *
     * @param field The field to set.
     * @param value The java vale for the field. Or null to clear value.
     */
    public void set(Descriptors.FieldDescriptor field, Object value) {
        requireNonNull(field, "field == null");
        if (value == null) {
            getMessage().clearField(field);
        } else {
            getMessage().setField(field, ValueUtil.toProtoValue(field, value));
        }
    }

    /**
     * Get the mutable instance for the field.
     *
     * @param field The field to get mutating type for.
     * @param <T>   The field type.
     * @return The mutating value, or the value if no mutating value exists.
     */
    @SuppressWarnings("unchecked")
    public <T> T mutable(Descriptors.FieldDescriptor field) {
        requireNonNull(field, "field == null");
        if (field.isRepeated()) {
            if (field.isMapField()) {
                return (T) new ProtoMapBuilder<>(getMessage(), field);
            } else {
                return (T) new ProtoListBuilder<>(getMessage(), field);
            }
        } else if (field.getType() == Descriptors.FieldDescriptor.Type.MESSAGE) {
            return (T) getMessage().getFieldBuilder(field);
        } else if (hasInternal(field)) {
            // not changed.
            return getInternal(field);
        } else {
            var defaultValue = getDefaultFieldValue(field);
            var defaultForType = getDefaultTypeValue(field);
            if (!Objects.equals(defaultForType, defaultValue)) {
                // only matters for proto v2.
                set(field, defaultValue);
            }
            return (T) defaultValue;
        }
    }

    /**
     * @param field Clear field on message.
     */
    public void clear(Descriptors.FieldDescriptor field) {
        requireNonNull(field, "field == null");
        getMessage().clearField(field);
    }
}