/*
 * Copyright 2018, 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 io.spine.type;

import com.google.common.base.Optional;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableMap;
import com.google.protobuf.Any;
import com.google.protobuf.Message;
import io.spine.annotation.Internal;

import java.util.Set;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Sets.filter;
import static io.spine.util.Exceptions.newIllegalStateException;

/**
 * All Protobuf types known to the application.
 *
 * <p>This includes:
 * <ul>
 *     <li>Standard Protobuf types.
 *     <li>Types generated by Protobuf compiler and collected by Spine Model Compiler into
 *     the {@linkplain io.spine.Resources#KNOWN_TYPES resource file}.
 * </ul>
 *
 * @author Mikhail Mikhaylov
 * @author Alexander Litus
 * @author Alexander Yevsyukov
 */
@Internal
public class KnownTypes {

    /**
     * A map from Protobuf type URL to Java class name.
     *
     * <p>For example, for a key {@code type.spine.io/spine.base.EventId},
     * there will be the value {@code EventId}.
     */
    private final ImmutableBiMap<TypeUrl, ClassName> names;

    /**
     * A map from Protobuf type name to type URL.
     *
     * <p>For example, for a key {@code "spine.base.EventId"},
     * there will be {@link TypeUrl} with the value {@code "type.spine.io/spine.base.EventId"}.
     *
     * @see TypeUrl
     */
    private final ImmutableMap<String, TypeUrl> urls;

    /**
     * Builds the instance by loading known types and composing lookup map for type URLs.
     */
    private KnownTypes() {
        final ImmutableBiMap<TypeUrl, ClassName> names = Loader.load();
        this.names = names;

        final ImmutableMap.Builder<String, TypeUrl> builder = ImmutableMap.builder();
        for (TypeUrl typeUrl : names.keySet()) {
            builder.put(typeUrl.getTypeName(), typeUrl);
        }
        this.urls = builder.build();
    }

    /**
     * Retrieves a Java class name generated for the Protobuf type by its type url
     * to be used to parse {@link com.google.protobuf.Message Message} from {@link Any}.
     *
     * @param typeUrl {@link Any} type url
     * @return Java class name
     * @throws UnknownTypeException if there is no such type known to the application
     */
    public static ClassName getClassName(TypeUrl typeUrl) throws UnknownTypeException {
        if (!instance().contains(typeUrl)) {
            throw new UnknownTypeException(typeUrl.getTypeName());
        }
        final ClassName result = instance().get(typeUrl);
        return result;
    }

    /**
     * Returns the Protobuf name for the class with the given name.
     *
     * @param className the name of the Java class for which to get Protobuf type
     * @return a Protobuf type name
     * @throws IllegalStateException if there is no Protobuf type for the specified class
     */
    public static TypeUrl getTypeUrl(ClassName className) {
        final TypeUrl result = instance().get(className);
        if (result == null) {
            throw newIllegalStateException("No Protobuf type URL found for the Java class %s",
                                           className);
        }
        return result;
    }

    /**
     * Retrieves Protobuf type URLs known to the application.
     */
    public static Set<TypeUrl> getAllUrls() {
        return instance().types();
    }

    private Set<TypeUrl> types() {
        return names.keySet();
    }

    private ClassName get(TypeUrl typeUrl) {
        return names.get(typeUrl);
    }

    private TypeUrl get(ClassName className) {
        return names.inverse()
                    .get(className);
    }

    private boolean contains(TypeUrl typeUrl) {
        final boolean result = names.containsKey(typeUrl);
        return result;
    }

    private Optional<TypeUrl> find(String typeName) {
        final TypeUrl typeUrl = urls.get(typeName);
        return Optional.fromNullable(typeUrl);
    }

    /**
     * Obtains URL for a type type by its full name.
     *
     * @return URL of the type or {@code null} if the type with this name is not known.
     */
    static Optional<TypeUrl> tryFind(String typeName) {
        return instance().find(typeName);
    }

    /**
     * Obtains immutable set of URLs of types belonging to the passed package.
     */
    private Set<TypeUrl> fromPackage(final String packageName) {
        final Set<TypeUrl> result = filter(types(), TypeUrl.inPackage(packageName));
        return result;
    }

    /**
     * Retrieves all the types that belong to the given package or its subpackages.
     *
     * @param packageName proto package name
     * @return set of {@link TypeUrl TypeUrl}s of types that belong to the given package
     */
    public static Set<TypeUrl> getAllFromPackage(String packageName) {
        return instance().fromPackage(packageName);
    }

    /**
     * Obtains a Java class for the passed type URL.
     *
     * @throws UnknownTypeException if there is no Java class for the passed type URL
     */
    static <T extends Message> Class<T> getJavaClass(TypeUrl typeUrl) throws UnknownTypeException {
        checkNotNull(typeUrl);
        final ClassName className = getClassName(typeUrl);
        try {
            @SuppressWarnings("unchecked") // the client considers this message is of this class
            final Class<T> result = (Class<T>) Class.forName(className.value());
            return result;
        } catch (ClassNotFoundException e) {
            throw new UnknownTypeException(typeUrl.getTypeName(), e);
        }
    }

    private static KnownTypes instance() {
        return  Singleton.INSTANCE.value;
    }

    @SuppressWarnings("NonSerializableFieldInSerializableClass")
    private enum Singleton {
        INSTANCE;

        private final KnownTypes value = new KnownTypes();
    }
}

@@ -5105,20 +5105,33 @@
  %7B@code 
-null
+Optional.absent()
 %7D if the
@@ -5163,17 +5163,16 @@
 ot known
-.
 %0A     */

Fix Javadoc