ProtoMessageBuilder.java

/*
 * Copyright 2022 Proto Utils Authors
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
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);
    }
}