PMessageBuilder.java

/*
 * Copyright 2015-2016 Providence 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.providence;

import net.morimekta.providence.descriptor.PField;

import javax.annotation.Nonnull;
import java.util.Arrays;
import java.util.Collection;

import static net.morimekta.util.collect.Unmodifiables.toSet;
import static net.morimekta.util.collect.Unmodifiables.toSortedSet;

/**
 * Base class for message builders.
 */
public abstract class PMessageBuilder<Message extends PMessage<Message>>
        implements PMessageOrBuilder<Message> {
    /**
     * Checks if the current set data is enough to make a valid struct. It
     * will check for all required fields, and if any are missing it will
     * return false.
     *
     * @return True for a valid message.
     */
    public abstract boolean valid();

    /**
     * Checks if the current set data is enough to make a valid struct. It
     * will check for all required fields, and if any are missing it will
     * throw an {@link IllegalStateException} with an appropriate error
     * message.
     *
     * @return The builder itself.
     * @throws IllegalStateException When the builder will not generate a
     *         valid message model object.
     */
    public abstract PMessageBuilder<Message> validate() throws IllegalStateException;

    /**
     * Set the provided field value.
     *
     * @param key The key of the field to set.
     * @param value The value to be set.
     * @return The message builder.
     */
    @Nonnull
    public abstract PMessageBuilder<Message> set(int key, Object value);

    /**
     * Set the provided field value.
     *
     * @param field The field to set.
     * @param value The value to be set.
     * @return The builder itself.
     */
    @Nonnull
    public PMessageBuilder<Message> set(@Nonnull PField<Message> field, Object value) {
        return set(field.getId(), value);
    }

    /**
     * Checks if a specific field is set on the builder.
     *
     * @param key The key of the field to check.
     * @return True if the field is set.
     */
    public abstract boolean isSet(int key);

    /**
     * Checks if a specific field is set on the builder.
     *
     * @param field The field to check.
     * @return True if the field is set.
     */
    public boolean isSet(@Nonnull PField<Message> field) {
        return isSet(field.getId());
    }

    /**
     * Get a Collection of F with fields set on the builder. A.k.a is
     * present.
     *
     * Unusual naming because it avoids conflict with generated methods.
     *
     * @return Collection of F
     */
    @Nonnull
    public Collection<PField<Message>> presentFields() {
           return Arrays.stream(descriptor().getFields())
                        .filter(this::isSet)
                        .collect(toSet());
    }

    /**
     * Get a sorted set of fields set on the builder. A.k.a is present.
     *
     * @return Collection of F
     */
    public Collection<String> presentFieldNames() {
        return Arrays.stream(descriptor().getFields())
                     .filter(this::isSet)
                     .map(PField::getName)
                     .collect(toSortedSet());
    }

    /**
     * Checks if a specific field is modified on the builder.
     *
     * @param key The key of the field to check.
     * @return True if the field is modified.
     */
    public abstract boolean isModified(int key);

    /**
     * Checks if a specific field is modified on the builder.
     *
     * @param field The field to check.
     * @return True if the field is modified.
     */
    public boolean isModified(@Nonnull PField<Message> field) {
        return isModified(field.getId());
    }

    /**
     * Get a Collection of F with fields Modified since creation of the builder.
     *
     * @return Collection of F
     */
    @Nonnull
    public Collection<PField<Message>> modifiedFields() {
        return Arrays.stream(descriptor().getFields())
                     .filter(this::isModified)
                     .collect(toSet());
    }

    /**
     * Get a sorted set of field names Modified since creation of the builder.
     *
     * @return Collection of F
     */
    public Collection<String> modifiedFieldNames() {
        return Arrays.stream(descriptor().getFields())
                     .filter(this::isModified)
                     .map(PField::getName)
                     .collect(toSortedSet());
    }

    /**
     * Adds a value to a set or list container.
     *
     * @param key The key of the container field to add too.
     * @param value The value to add.
     * @return The builder itself.
     * @throws IllegalArgumentException if the field is not a list or set.
     */
    @Nonnull
    public abstract PMessageBuilder<Message> addTo(int key, Object value);

    /**
     * Checks if a specific field is set on the builder.
     *
     * @param field The container field to add too.
     * @param value The value to add.
     * @return The builder itself.
     */
    @Nonnull
    public PMessageBuilder<Message> addTo(@Nonnull PField<Message> field, Object value) {
        return addTo(field.getId(), value);
    }

    /**
     * Clear the provided field value.
     *
     * @param key The key of the field to clear.
     * @return The builder itself.
     */
    @Nonnull
    public abstract PMessageBuilder<Message> clear(int key);


    /**
     * Clear the provided field value.
     *
     * @param field The field to clear.
     * @return The builder itself.
     */
    @Nonnull
    public PMessageBuilder<Message> clear(@Nonnull PField<Message> field) {
        return clear(field.getId());
    }


    /**
     * Merges the provided message into the builder. Contained messages should
     * in turn be merged and not replaced wholesale. Sets are unioned (addAll)
     * and maps will overwrite / replace on a per-key basis (putAll).
     *
     * @param from The message to merge values from.
     * @return The builder itself.
     */
    @Nonnull
    public abstract PMessageBuilder<Message> merge(@Nonnull Message from);

    /**
     * Get the builder for the given message contained in this builder. If
     * the sub-builder does not exist, create, either from existing instance
     * or from scratch.
     *
     * @param key The field key.
     * @return The field builder.
     * @throws IllegalArgumentException if field is not a message field.
     */
    @Nonnull
    public abstract PMessageBuilder<?> mutator(int key);

    /**
     * Get the builder for the given message contained in this builder. If
     * the sub-builder does not exist, create, either from existing instance
     * or from scratch.
     *
     * @param field The field to mutate.
     * @return The field builder.
     * @throws IllegalArgumentException if field is not a message field.
     */
    @Nonnull
    public PMessageBuilder<?> mutator(@Nonnull PField<Message> field) {
        return mutator(field.getId());
    }

    @Nonnull
    public abstract Message build();
}