<commit_before>/*
 * Copyright 2016, 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.entity;

import org.spine3.server.BoundedContext;
import org.spine3.server.reflect.Classes;
import org.spine3.server.storage.Storage;
import org.spine3.server.storage.StorageFactory;

import javax.annotation.CheckReturnValue;
import javax.annotation.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Throwables.propagate;
import static java.lang.reflect.Modifier.isPrivate;
import static java.lang.reflect.Modifier.isPublic;

/**
 * Abstract base class for repositories.
 *
 * @param <I> the type of IDs of entities managed by the repository
 * @param <E> the entity type
 *
 * @author <NAME>
 */
public abstract class Repository<I, E extends Entity<I, ?>> implements AutoCloseable {

    /** The index of the declaration of the generic type {@code I} in this class. */
    private static final int ID_CLASS_GENERIC_INDEX = 0;

    /** The index of the declaration of the generic type {@code E} in this class. */
    private static final int ENTITY_CLASS_GENERIC_INDEX = 1;

    protected static final String ERR_MSG_STORAGE_NOT_ASSIGNED = "Storage is not assigned.";

    /** The {@code BoundedContext} in which this repository works. */
    private final BoundedContext boundedContext;

    /** The constructor for creating entity instances. */
    private final Constructor<E> entityConstructor;

    /** The data storage for this repository. */
    private Storage storage;

    /**
     * Creates the repository in the passed {@link BoundedContext}.
     *
     * @param boundedContext the {@link BoundedContext} in which this repository works
     */
    protected Repository(BoundedContext boundedContext) {
        this.boundedContext = boundedContext;
        this.entityConstructor = getEntityConstructor();
        this.entityConstructor.setAccessible(true);
    }

    private Constructor<E> getEntityConstructor() {
        final Class<E> entityClass = getEntityClass();
        final Class<I> idClass = getIdClass();
        try {
            final Constructor<E> result = entityClass.getDeclaredConstructor(idClass);
            checkConstructorAccessModifier(result.getModifiers(), entityClass.getName());
            return result;
        } catch (NoSuchMethodException ignored) {
            throw noSuchConstructorException(entityClass.getName(), idClass.getName());
        }
    }

    private static void checkConstructorAccessModifier(int modifiers, String entityClass) {
        if (!isPublic(modifiers)) {
            final String constructorModifier = isPrivate(modifiers) ? "private." : "protected.";
            final String message = "Constructor must be public in the " + entityClass + " class, found: " + constructorModifier;
            throw propagate(new IllegalAccessException(message));
        }
    }

    private static RuntimeException noSuchConstructorException(String entityClass, String idClass) {
        final String message = entityClass + " class must declare a constructor with a single " + idClass + " ID parameter.";
        return propagate(new NoSuchMethodException(message));
    }

    /** Returns the {@link BoundedContext} in which this repository works. */
    protected BoundedContext getBoundedContext() {
        return boundedContext;
    }

    /** Returns the class of IDs used by this repository. */
    @CheckReturnValue
    protected Class<I> getIdClass() {
        return Classes.getGenericParameterType(getClass(), ID_CLASS_GENERIC_INDEX);
    }

    /** Returns the class of entities managed by this repository. */
    @CheckReturnValue
    protected Class<E> getEntityClass() {
        return Classes.getGenericParameterType(getClass(), ENTITY_CLASS_GENERIC_INDEX);
    }

    /**
     * Create a new entity instance with its default state.
     *
     * @param id the id of the entity<commit_msg>Expose the entity type from the Repository as TypeUrl instance.<commit_after>/*
 * Copyright 2016, 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.entity;

import org.spine3.protobuf.KnownTypes;
import org.spine3.protobuf.TypeUrl;
import org.spine3.server.BoundedContext;
import org.spine3.server.reflect.Classes;
import org.spine3.server.storage.Storage;
import org.spine3.server.storage.StorageFactory;

import javax.annotation.CheckReturnValue;
import javax.annotation.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Throwables.propagate;
import static java.lang.reflect.Modifier.isPrivate;
import static java.lang.reflect.Modifier.isPublic;

/**
 * Abstract base class for repositories.
 *
 * @param <I> the type of IDs of entities managed by the repository
 * @param <E> the entity type
 *
 * @author <NAME>
 */
public abstract class Repository<I, E extends Entity<I, ?>> implements AutoCloseable {

    /** The index of the declaration of the generic type {@code I} in this class. */
    private static final int ID_CLASS_GENERIC_INDEX = 0;

    /** The index of the declaration of the generic type {@code E} in this class. */
    private static final int ENTITY_CLASS_GENERIC_INDEX = 1;

    protected static final String ERR_MSG_STORAGE_NOT_ASSIGNED = "Storage is not assigned.";

    /** The {@code BoundedContext} in which this repository works. */
    private final BoundedContext boundedContext;

    /** The constructor for creating entity instances. */
    private final Constructor<E> entityConstructor;

    /** The data storage for this repository. */
    private Storage storage;

    /**
     * Creates the repository in the passed {@link BoundedContext}.
     *
     * @param boundedContext the {@link BoundedContext} in which this repository works
     */
    protected Repository(BoundedContext boundedContext) {
        this.boundedContext = boundedContext;
        this.entityConstructor = getEntityConstructor();
        this.entityConstructor.setAccessible(true);
    }

    private Constructor<E> getEntityConstructor() {
        final Class<E> entityClass = getEntityClass();
        final Class<I> idClass = getIdClass();
        try {
            final Constructor<E> result = entityClass.getDeclaredConstructor(idClass);
            checkConstructorAccessModifier(result.getModifiers(), entityClass.getName());
            return result;
        } catch (NoSuchMethodException ignored) {
            throw noSuchConstructorException(entityClass.getName(), idClass.getName());
        }
    }

    private static void checkConstructorAccessModifier(int modifiers, String entityClass) {
        if (!isPublic(modifiers)) {
            final String constructorModifier = isPrivate(modifiers) ? "private." : "protected.";
            final String message = "Constructor must be public in the " + entityClass + " class, found: " + constructorModifier;
            throw propagate(new IllegalAccessException(message));
        }
    }

    private static RuntimeException noSuchConstructorException(String entityClass, String idClass) {
        final String message = entityClass + " class must declare a constructor with a single " + idClass + " ID parameter.";
        return propagate(new NoSuchMethodException(message));
    }

    /** Returns the {@link BoundedContext} in which this repository works. */
    protected BoundedContext getBoundedContext() {
        return boundedContext;
    }

    /** Returns the class of IDs used by this repository. */
    @CheckReturnValue
    protected Class<I> getIdClass() {
        return Classes.getGenericParameterType(getClass(), ID_CLASS_GENERIC_INDEX);
    }

    /** Returns the class of entities managed by this repository. */
    @CheckReturnValue
    protected Class<E> getEntityClass() {
        return Classes.getGenericParameterType(getClass(), ENTITY_CLASS_GENERIC_INDEX);
    }

    /** Returns the {@link TypeUrl} for the entities managed by this repository */
    @CheckReturnValue
    public TypeUrl getEntityType() {
        final Class<E> entityClass = getEntityClass();
        final TypeUrl typeUrl = KnownTypes.getTypeUrl(entityClass.getSimpleName());
        return typeUrl;
    }

    /**
     * Create a new entity instance with its default state.
     *
     * @param id the id of the entity
{'fraction_non_alphanumeric': 0.08151284765662592, 'fraction_numerical': 0.002309691078818208, 'mean_word_length': 3.6538289296909987, 'pattern_counts': {'":': 0, '<': 28, '<?xml version=': 0, '>': 28, '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': '4891426', 'n_tokens_mistral': 2726, 'n_tokens_neox': 2571, 'n_words': 955}