/*
 * Copyright 2017, TeamDev Ltd. All rights reserved.
 *
 * Redistribution and use in source and/or binary forms, with or without
 * modification, must retain the above copyright notice and the following
 * disclaimer.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.spine3.server.command;

import com.google.common.annotations.VisibleForTesting;
import com.google.protobuf.Any;
import com.google.protobuf.Message;
import org.spine3.base.CommandContext;
import org.spine3.base.EventContext;
import org.spine3.change.MessageMismatch;
import org.spine3.change.StringMismatch;
import org.spine3.change.ValueMismatch;
import org.spine3.server.entity.AbstractVersionableEntity;
import org.spine3.server.reflect.CommandHandlerMethod;

import javax.annotation.CheckReturnValue;
import java.util.List;

import static org.spine3.base.Identifiers.idToAny;

/**
 * An entity that can handle commands.
 *
 * <h2>Command handling methods</h2>
 *
 * <p>A command handling method is a {@code public} method that accepts two parameters.
 * The first parameter is a command message of an <strong>exact</strong> type
 * derived from {@code Message}
 * The second (optional) parameter is {@link CommandContext}.
 *
 * <p>The method returns an event message of the specific type, or {@code List} of messages
 * if it produces more than one event.
 *
 * <p>The method may throw one or more throwables derived from
 * {@link org.spine3.base.FailureThrowable FailureThrowable}.
 * Throwing a {@code FailureThrowable} indicates that the passed command cannot be handled
 * because of a {@linkplain org.spine3.base.FailureThrowable#getFailureMessage() business failure}.
 *
 * {@inheritDoc}
 *
 * @author Alexander Yevsyukov
 */
public abstract class CommandHandlingEntity<I, S extends Message>
        extends AbstractVersionableEntity<I, S> {

    /** Cached value of the ID in the form of {@code Any} instance. */
    private final Any idAsAny;

    /**
     * {@inheritDoc}
     */
    protected CommandHandlingEntity(I id) {
        super(id);
        this.idAsAny = idToAny(id);
    }

    protected Any getProducerId() {
        return idAsAny;
    }

    /**
     * Creates a context for an event message.
     *
     * @param commandContext the context of the command, execution of which produced the event
     * @return new instance of the {@code EventContext}
     */
    @CheckReturnValue
    protected EventContext createEventContext(CommandContext commandContext) {
        return CommandHandlerMethod.createEventContext(idAsAny, getVersion(), commandContext);
    }

    /**
     * Dispatches the passed command to appropriate handler.
     *
     * @param command the command message to be handled.
     *                If this parameter is passed as {@link Any} the enclosing
     *                message will be unwrapped.
     * @param context the context of the command
     * @return event messages generated by the handler
     * @throws IllegalStateException if an exception occurred during command dispatching
     *                               with this exception as the cause
     * @see #dispatchForTest(Message, CommandContext)
     */
    protected List<? extends Message> dispatchCommand(Message command, CommandContext context) {
        return CommandHandlerMethod.invokeHandler(this, command, context);
    }

    /**
     * This method is provided <em>only</em> for the purpose of testing command
     * handling and must not be called from the production code.
     *
     * <p>The production code uses the method {@link #dispatchCommand(Message, CommandContext)},
     * which is called automatically.
     */
    @VisibleForTesting
    public final List<? extends Message> dispatchForTest(Message command, CommandContext context) {
        return dispatchCommand(command, context);
    }

    //
    // Helper methods for producing `ValueMismatch`es in command handling methods
    //-----------------------------------------------------------------------------

    /**
     * Creates {@code ValueMismatch} for the case of discovering a non-default value,
     * when the default value was expected by a command.
     *
     * @param actual   the value discovered instead of the default value
     * @param newValue the new value requested in the command
     * @return new {@code ValueMismatch} instance
     */
    protected ValueMismatch expectedDefault(Message actual, Message newValue) {
        return MessageMismatch.expectedDefault(actual, newValue, versionNumber());
    }

    /**
     * Creates a {@code ValueMismatch} for a command that wanted to <em>clear</em> a value,
     * but discovered that the field already has the default value.
     *
     * @param expected the value of the field that the command wanted to clear
     * @return new {@code ValueMismatch} instance
     */
    protected ValueMismatch expectedNotDefault(Message expected) {
        return MessageMismatch.expectedNotDefault(expected, versionNumber());
    }

    /**
     * Creates a {@code ValueMismatch} for a command that wanted to <em>change</em> a field value,
     * but discovered that the field has the default value.
     *
     * @param expected the value expected by the command
     * @param newValue the value the command wanted to set
     * @return new {@code ValueMismatch} instance
     */
    protected ValueMismatch expectedNotDefault(Message expected, Message newValue) {
        return MessageMismatch.expectedNotDefault(expected, newValue, versionNumber());
    }

    /**
     * Creates {@code ValueMismatch} for the case of discovering a value different than by a command.
     *
     * @param expected the value expected by the command
     * @param actual   the value discovered instead of the expected value
     * @param newValue the new value requested in the command
     * @return new {@code ValueMismatch} instance
     */
    protected ValueMismatch unexpectedValue(Message expected, Message actual, Message newValue) {
        return MessageMismatch.unexpectedValue(expected, actual, newValue, versionNumber());
    }

    /**
     * Creates {@code ValueMismatch} for the case of discovering a non-empty value,
     * when an empty string was expected by a command.
     *
     * @param actual   the value discovered instead of the empty string
     * @param newValue the new value requested in the command
     * @return new {@code ValueMismatch} instance
     */
    protected ValueMismatch expectedEmpty(String actual, String newValue) {
        return StringMismatch.expectedEmpty(actual, newValue, versionNumber());
    }

    /**
     * Creates a {@code ValueMismatch} for a command that wanted to clear a string value,
     * but discovered that the field is already empty.
     *
     * @param expected the value of the field that the command wanted to clear
     * @return new ValueMismatch instance
     */
    protected ValueMismatch expectedNotEmpty(String expected) {
        return StringMismatch.expectedNotEmpty(expected, versionNumber());
    }

    /**
     * Creates {@code ValueMismatch} for the case of discovering a value
     * different than expected by a command.
     *
     * @param expected the value expected by the command
     * @param actual   the value discovered instead of the expected string
     * @param newValue the new value requested in the command
     * @return new {@code ValueMismatch} instance
     */
    protected ValueMismatch unexpectedValue(String expected, String actual, String newValue) {
        return StringMismatch.unexpectedValue(expected, actual, newValue, versionNumber());
    }
}

@@ -3266,23 +3266,31 @@
 Context(
-idAsAny
+getProducerId()
 , getVer

Use `getProducerId()` instead of direct field access