<commit_before>/*
 * Copyright 2015, 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.aggregate;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.*;
import com.google.protobuf.*;
import org.spine3.CommandClass;
import org.spine3.base.*;
import org.spine3.internal.MessageHandler;
import org.spine3.protobuf.Messages;
import org.spine3.server.Entity;
import org.spine3.server.Snapshot;
import org.spine3.server.SnapshotOrBuilder;
import org.spine3.server.aggregate.error.MissingEventApplierException;
import org.spine3.server.internal.CommandDispatcher;
import org.spine3.server.internal.CommandHandler;
import org.spine3.util.Events;

import javax.annotation.CheckReturnValue;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * Abstract base for aggregate roots.
 *
 * @param <I> the type for ID of the aggregate root. Supported types are:
 *           <ul>
 *              <li>Classes implementing {@link Message}</li>
 *              <li>String</li>
 *              <li>Long</li>
 *              <li>Integer</li>
 *           </ul>
 *           Consider using {@code Message}-based IDs if you want to have typed IDs in your code, and/or
 *           if you need to have IDs with some structure inside. Examples of such structural IDs are:
 *           <ul>
 *              <li>EAN value used in bar codes</li>
 *              <li>ISBN</li>
 *              <li>Phone number</li>
 *              <li>email address as a couple of local-part and domain</li>
 *           </ul>
 * @param <S> the type of the state held by the root
 * @author <NAME>
 * @author <NAME>
 */
@SuppressWarnings({"ClassWithTooManyMethods", "AbstractClassNeverImplemented"})
public abstract class AggregateRoot<I, S extends Message> extends Entity<I, S> {

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

    private volatile boolean initialized = false;
    private CommandDispatcher dispatcher;
    private EventApplier.Map applier;

    private final List<EventRecord> eventRecords = Lists.newLinkedList();

    /**
     * Creates a new instance.
     * @param id the ID for the new instance
     * @throws IllegalArgumentException if the ID is not of one of the supported types
     */
    protected AggregateRoot(I id) {
        super(id);

        //noinspection IfStatementWithTooManyBranches,ChainOfInstanceofChecks
        if (id instanceof Message) {
            Message message = (Message) id;
            this.idAsAny = Messages.toAny(message);
        } else if (id instanceof String) {
            String s = (String) id;
            this.idAsAny = Messages.toAny(StringValue.newBuilder().setValue(s).build());
        } else if (id instanceof Integer) {
            Integer intValue = (Integer) id;
            this.idAsAny = Messages.toAny(UInt32Value.newBuilder().setValue(intValue).build());
        } else if (id instanceof Long) {
            Long longValue = (Long) id;
            this.idAsAny = Messages.toAny(UInt64Value.newBuilder().setValue(longValue).build());
        } else {
            throw new IllegalArgumentException("ID of unsupported type encountered: " + id);
        }
    }

    /**
     * Returns set of the command types handled by a given aggregate root.
     *
     * @param clazz {@link Class} of the aggregate root
     * @return command types handled by aggregate root
     */
    @CheckReturnValue
    public static Set<CommandClass> getCommandClasses(Class<? extends AggregateRoot> clazz) {
        Set<Class<? extends Message>> types = getHandledMessageClasses(clazz, CommandHandler.isCommandHandlerPredicate);
        Iterable<CommandClass> transformed = Iterables.transform(types, new Function<Class<? extends Message>, CommandClass>() {
            @Nullable
            @Override
            public CommandClass apply(@Nullable Class<? extends Message> input) {
                if (input == null) {
                    return null;
                }
                return CommandClass.of(input);
            }
        });
        return ImmutableSet.copyOf(transformed);
    }

    /**
     * Returns event/command types handled by given AggregateRoot class.
     */
    @CheckReturnValue
    static Set<Class<? extends Message>> getHandledMessageClasses(Class<? extends AggregateRoot> clazz, Predicate<Method> methodPredicate) {

        Set<Class<? extends Message>> result = Sets.newHashSet();

        for (Method method : clazz.getDeclaredMethods()) {

            boolean methodMatches = methodPredicate.apply(method);

            if (methodMatches) {
                Class<? extends Message> firstParamType = MessageHandler.getFirstParamType(method);
                result.add(firstParamType);
            }
        }
        return result;
    }

    private void init() {
        if (!this.initialized) {
            initCommandDispatcher();
            initEventApplier();

            if (super.getState() == null) {
                setDefault();
            }

            this.initialized = true;
        }
    }

    /**
     * Returns the current state of the aggregate root.
     *
     * @return a non-null state object or default state instance
     */
    @Nonnull
    @Override
    public S getState() {
        init();
        final S state = super.getState();
        // An aggregate root when initialized may not have a null state because:
        // 1. Its initialization sets the state to default.
        // 2. Modifications are performed via command handlers or event appliers,
        //     which involves prior initialization.
        assert state != null;
        return state;
    }

    /**
     * Returns a non-null timestamp of the last modification.
     *
     * @return a non-null instance, which is the timestamp of the last modification or
     *         the timestamp of the object creation of the root is in the default state
     */
    @Nonnull
    @Override
    public Timestamp whenLastModified() {
        init();
        final Timestamp lastModified = super.whenLastModified();
        // An aggregate root when initialized may not have a null modification timestamp because:
        // 1. Its initialization sets the timestamp.
        // 2. Modifications are performed via command handlers or event appliers,
        //     which involves prior initialization.
        assert lastModified != null;
        return lastModified;
    }

    private Any getIdAsAny() {
        return idAsAny;
    }

    private void initEventApplier() {
        applier = new EventApplier.Map(this);
    }

    private void initCommandDispatcher() {
        dispatcher = new CommandDispatcher();
        dispatcher.register(this);
    }

    /**
     * Dispatches commands, generates events and applies them to the aggregate root.
     *<commit_msg>Remove null check in "getCommandClasses" method; change java docs; change "getHandledMessageClasses" method.<commit_after>/*
 * Copyright 2015, 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.aggregate;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.*;
import com.google.protobuf.*;
import org.spine3.CommandClass;
import org.spine3.base.*;
import org.spine3.internal.MessageHandler;
import org.spine3.protobuf.Messages;
import org.spine3.server.Entity;
import org.spine3.server.Snapshot;
import org.spine3.server.SnapshotOrBuilder;
import org.spine3.server.aggregate.error.MissingEventApplierException;
import org.spine3.server.internal.CommandDispatcher;
import org.spine3.server.internal.CommandHandler;
import org.spine3.util.Events;

import javax.annotation.CheckReturnValue;
import javax.annotation.Nonnull;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * Abstract base for aggregate roots.
 *
 * @param <I> the type for ID of the aggregate root. Supported types are:
 *           <ul>
 *              <li>Classes implementing {@link Message}</li>
 *              <li>String</li>
 *              <li>Long</li>
 *              <li>Integer</li>
 *           </ul>
 *           Consider using {@code Message}-based IDs if you want to have typed IDs in your code, and/or
 *           if you need to have IDs with some structure inside. Examples of such structural IDs are:
 *           <ul>
 *              <li>EAN value used in bar codes</li>
 *              <li>ISBN</li>
 *              <li>Phone number</li>
 *              <li>email address as a couple of local-part and domain</li>
 *           </ul>
 * @param <S> the type of the state held by the root
 * @author <NAME>
 * @author <NAME>
 */
@SuppressWarnings({"ClassWithTooManyMethods", "AbstractClassNeverImplemented"})
public abstract class AggregateRoot<I, S extends Message> extends Entity<I, S> {

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

    private volatile boolean initialized = false;
    private CommandDispatcher dispatcher;
    private EventApplier.Map applier;

    private final List<EventRecord> eventRecords = Lists.newLinkedList();

    /**
     * Creates a new instance.
     * @param id the ID for the new instance
     * @throws IllegalArgumentException if the ID is not of one of the supported types
     */
    protected AggregateRoot(I id) {
        super(id);

        //noinspection IfStatementWithTooManyBranches,ChainOfInstanceofChecks
        if (id instanceof Message) {
            Message message = (Message) id;
            this.idAsAny = Messages.toAny(message);
        } else if (id instanceof String) {
            String s = (String) id;
            this.idAsAny = Messages.toAny(StringValue.newBuilder().setValue(s).build());
        } else if (id instanceof Integer) {
            Integer intValue = (Integer) id;
            this.idAsAny = Messages.toAny(UInt32Value.newBuilder().setValue(intValue).build());
        } else if (id instanceof Long) {
            Long longValue = (Long) id;
            this.idAsAny = Messages.toAny(UInt64Value.newBuilder().setValue(longValue).build());
        } else {
            throw new IllegalArgumentException("ID of unsupported type encountered: " + id);
        }
    }

    /**
     * Returns set of the command types handled by a given aggregate root.
     *
     * @param clazz {@link Class} of the aggregate root
     * @return command types handled by aggregate root
     */
    @CheckReturnValue
    public static Set<CommandClass> getCommandClasses(Class<? extends AggregateRoot> clazz) {
        Set<Class<? extends Message>> types = getHandledMessageClasses(clazz, CommandHandler.isCommandHandlerPredicate);
        Iterable<CommandClass> transformed = Iterables.transform(types, new Function<Class<? extends Message>, CommandClass>() {
            @Override
            public CommandClass apply(Class<? extends Message> input) {
                return CommandClass.of(input);
            }
        });
        return ImmutableSet.copyOf(transformed);
    }

    /**
     * Returns event/command types handled by given AggregateRoot class.
     * @return immutable set of message classes or an empty set
     */
    @CheckReturnValue
    static Set<Class<? extends Message>> getHandledMessageClasses(Class<? extends AggregateRoot> clazz, Predicate<Method> methodPredicate) {

        Set<Class<? extends Message>> result = Sets.newHashSet();

        for (Method method : clazz.getDeclaredMethods()) {

            boolean methodMatches = methodPredicate.apply(method);

            if (methodMatches) {
                Class<? extends Message> firstParamType = MessageHandler.getFirstParamType(method);
                result.add(firstParamType);
            }
        }
        return ImmutableSet.<Class<? extends Message>>builder().addAll(result).build();
    }

    private void init() {
        if (!this.initialized) {
            initCommandDispatcher();
            initEventApplier();

            if (super.getState() == null) {
                setDefault();
            }

            this.initialized = true;
        }
    }

    /**
     * Returns the current state of the aggregate root.
     *
     * @return a non-null state object or default state instance
     */
    @Nonnull
    @Override
    public S getState() {
        init();
        final S state = super.getState();
        // An aggregate root when initialized may not have a null state because:
        // 1. Its initialization sets the state to default.
        // 2. Modifications are performed via command handlers or event appliers,
        //     which involves prior initialization.
        assert state != null;
        return state;
    }

    /**
     * Returns a non-null timestamp of the last modification.
     *
     * @return a non-null instance, which is the timestamp of the last modification or
     *         the timestamp of setting the default state of the aggregate
     */
    @Nonnull
    @Override
    public Timestamp whenLastModified() {
        init();
        final Timestamp lastModified = super.whenLastModified();
        // An aggregate root when initialized may not have a null modification timestamp because:
        // 1. Its initialization sets the timestamp.
        // 2. Modifications are performed via command handlers or event appliers,
        //     which involves prior initialization.
        assert lastModified != null;
        return lastModified;
    }

    private Any getIdAsAny() {
        return idAsAny;
    }

    private void initEventApplier() {
        applier = new EventApplier.Map(this);
    }

    private void initCommandDispatcher() {
        dispatcher = new CommandDispatcher();
        dispatcher.register(this);
    }

    /**
     * Dispatches commands, generates events and applies them to the aggregate root.
     *
{'fraction_non_alphanumeric': 0.08851895079550341, 'fraction_numerical': 0.0029485840653602803, 'mean_word_length': 2.6782647989154995, 'pattern_counts': {'":': 0, '<': 89, '<?xml version=': 0, '>': 89, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6721638', 'n_tokens_mistral': 4323, 'n_tokens_neox': 4136, 'n_words': 1446}