<commit_before>/*
 * Copyright 2019, TeamDev. 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 io.spine.server.model;

import com.google.common.annotations.VisibleForTesting;
import io.spine.annotation.Internal;
import io.spine.code.java.ClassName;
import io.spine.core.BoundedContextName;
import io.spine.core.BoundedContextNames;
import io.spine.reflect.PackageInfo;
import io.spine.security.InvocationGuard;
import io.spine.server.annotation.BoundedContext;

import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;

import static com.google.common.collect.Maps.newConcurrentMap;

/**
 * Stores information of message handling classes.
 */
@Internal
public class Model {

    /**
     * Maps a raw class of a model object to corresponding Model instance.
     */
    private static final Map<Class<?>, Model> models = newConcurrentMap();

    /**
     * The cache of {@code BoundedContextName}s searched for particular raw classes.
     *
     * @see #findContext(Class)
     */
    private static final
    Map<ClassName, Optional<BoundedContextName>> knownContexts = newConcurrentMap();

    /** The name of the Bounded Context to which this instance belongs. */
    private final BoundedContextName context;

    /** Maps a raw Java class to a {@code ModelClass}. */
    private final ClassMap classes = new ClassMap();

    /**
     * Creates a new instance for the Bounded Context with the passed name. */
    private Model(BoundedContextName context) {
        this.context = context;
    }

    /**
     * Obtains the instance which belongs to the Bounded Context of the passed class.
     *
     * <p>The method tries to obtain the context from the annotations of the package "hierarchy"
     * to which the class belongs. If no annotation found,
     * {@linkplain BoundedContextNames#assumingTests() test-only} will be created.
     *
     * @see BoundedContext
     */
    public static synchronized <T> Model inContextOf(Class<? extends T> rawClass) {
        Model model = models.get(rawClass);
        if (model != null) {
            return model;
        }
        Optional<BoundedContextName> optional = findContext(rawClass);

        // If no name of a Bounded Context found, assume the default name.
        // This is a safety net for newcomers and our tests.
        // We may want to make this check strict, and require specifying Bounded Context names.
        BoundedContextName context = optional.orElseGet(BoundedContextNames::assumingTests);

        // Try to find a Model if it already exists.
        Optional<Model> alreadyAvailable =
                models.values()
                      .stream()
                      .filter((m) -> m.context.equals(context))
                      .findAny();
        if (alreadyAvailable.isPresent()) {
            return alreadyAvailable.get();
        }

        // Since a model is not found, create for new Bounded Context and associated with the
        // passed raw class.
        Model newModel = new Model(context);
        models.put(rawClass, newModel);
        return newModel;
    }

    /**
     * Finds Bounded Context name in the package annotations of the raw class,
     * or in the packages into which the package of the class is nested.
     */
    @VisibleForTesting
    static <T> Optional<BoundedContextName> findContext(Class<? extends T> rawClass) {
        ClassName clazzName = ClassName.of(rawClass);
        if (knownContexts.containsKey(clazzName)) {
            return knownContexts.get(clazzName);
        }

        Optional<BoundedContextName> result;

        PackageInfo pkg = PackageInfo.of(rawClass);
        Optional<BoundedContext> annotation = pkg.findAnnotation(BoundedContext.class);
        if (!annotation.isPresent()) {
            result = Optional.empty();
        } else {
            String nameAsString = annotation.get()
                                            .value();
            BoundedContextName contextName = BoundedContextNames.newName(nameAsString);
            result = Optional.of(contextName);
        }
        knownContexts.put(clazzName, result);
        return result;
    }
<commit_msg>Use Guava's `Cache`.<commit_after>/*
 * Copyright 2019, TeamDev. 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 io.spine.server.model;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import io.spine.annotation.Internal;
import io.spine.code.java.ClassName;
import io.spine.core.BoundedContextName;
import io.spine.core.BoundedContextNames;
import io.spine.reflect.PackageInfo;
import io.spine.security.InvocationGuard;
import io.spine.server.annotation.BoundedContext;

import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.function.Supplier;

import static com.google.common.collect.Maps.newConcurrentMap;
import static io.spine.util.Exceptions.illegalStateWithCauseOf;

/**
 * Stores information of message handling classes.
 */
@Internal
public class Model {

    /**
     * Maps a raw class of a model object to corresponding Model instance.
     */
    private static final Map<Class<?>, Model> models = newConcurrentMap();

    /**
     * The cache of {@code BoundedContextName}s searched for particular raw classes.
     *
     * @see #findContext(Class)
     */
    private static final Cache<ClassName, Optional<BoundedContextName>> knownContexts =
            CacheBuilder.newBuilder().maximumSize(500).build();

    /** The name of the Bounded Context to which this instance belongs. */
    private final BoundedContextName context;

    /** Maps a raw Java class to a {@code ModelClass}. */
    private final ClassMap classes = new ClassMap();

    /**
     * Creates a new instance for the Bounded Context with the passed name. */
    private Model(BoundedContextName context) {
        this.context = context;
    }

    /**
     * Obtains the instance which belongs to the Bounded Context of the passed class.
     *
     * <p>The method tries to obtain the context from the annotations of the package "hierarchy"
     * to which the class belongs. If no annotation found,
     * {@linkplain BoundedContextNames#assumingTests() test-only} will be created.
     *
     * @see BoundedContext
     */
    public static synchronized <T> Model inContextOf(Class<? extends T> rawClass) {
        Model model = models.get(rawClass);
        if (model != null) {
            return model;
        }
        Optional<BoundedContextName> optional = findContext(rawClass);

        // If no name of a Bounded Context found, assume the default name.
        // This is a safety net for newcomers and our tests.
        // We may want to make this check strict, and require specifying Bounded Context names.
        BoundedContextName context = optional.orElseGet(BoundedContextNames::assumingTests);

        // Try to find a Model if it already exists.
        Optional<Model> alreadyAvailable =
                models.values()
                      .stream()
                      .filter((m) -> m.context.equals(context))
                      .findAny();
        if (alreadyAvailable.isPresent()) {
            return alreadyAvailable.get();
        }

        // Since a model is not found, create for new Bounded Context and associated with the
        // passed raw class.
        Model newModel = new Model(context);
        models.put(rawClass, newModel);
        return newModel;
    }

    /**
     * Finds Bounded Context name in the package annotations of the raw class,
     * or in the packages into which the package of the class is nested.
     */
    @VisibleForTesting
    static <T> Optional<BoundedContextName> findContext(Class<? extends T> rawClass) {
        ClassName clazzName = ClassName.of(rawClass);
        try {
            return knownContexts.get(clazzName, () -> {
                Optional<BoundedContextName> result;

                PackageInfo pkg = PackageInfo.of(rawClass);
                Optional<BoundedContext> annotation = pkg.findAnnotation(BoundedContext.class);
                if (!annotation.isPresent()) {
                    result = Optional.empty();
                } else {
                    String nameAsString = annotation.get()
                                                    .value();
                    BoundedContextName contextName = BoundedContextNames.newName(nameAsString);
                    result = Optional.of(contextName);
                }
                return result;
            });
        } catch (ExecutionException e) {
            throw illegalStateWithCauseOf(e);
        }
    }

{'fraction_non_alphanumeric': 0.08195325068609823, 'fraction_numerical': 0.0010409766253430491, 'mean_word_length': 2.722437477985206, 'pattern_counts': {'":': 0, '<': 31, '<?xml version=': 0, '>': 34, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4622588', 'n_tokens_mistral': 2832, 'n_tokens_neox': 2619, 'n_words': 977}