/*
 * 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.protobuf;

import com.google.protobuf.Any;
import com.google.protobuf.Message;
import org.spine3.ClassName;
import org.spine3.TypeName;
import org.spine3.protobuf.UnknownTypeInAnyException;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
 * Utility class for reading real proto class names from properties file.
 *
 * @author Mikhail Mikhaylov
 * @author Alexander Yevsyukov
 */
@SuppressWarnings("UtilityClass")
public class TypeToClassMap {

    /**
     * File, containing Protobuf messages' typeUrls and their appropriate class names.
     * Is generated with Gradle during build process.
     */
    private static final String PROPERTIES_FILE_NAME = "protos.properties";

    private static final Map<TypeName, ClassName> namesMap = new HashMap<>();

    static {
        Properties properties = new Properties();

        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        InputStream resourceStream = classLoader.getResourceAsStream(PROPERTIES_FILE_NAME);

        try {
            properties.load(resourceStream);
        } catch (IOException e) {
            //NOP
        }

        for (String key : properties.stringPropertyNames()) {
            final TypeName typeName = TypeName.of(key);
            final ClassName className = ClassName.of(properties.getProperty(key));
            namesMap.put(typeName, className);
        }
    }

    private TypeToClassMap() {
    }

    /**
     * Retrieves compiled proto's java class name by proto type url
     * to be used to parse {@link Message} from {@link Any}.
     *
     * @param protoType {@link Any} type url
     * @return Java class name
     */
    public static ClassName get(TypeName protoType) {
        if (!namesMap.containsKey(protoType)) {
            throw new UnknownTypeInAnyException(protoType.value());
        }
        final ClassName result = namesMap.get(protoType);
        return result;
    }
}

@@ -1168,62 +1168,8 @@
 ame;
-%0Aimport org.spine3.protobuf.UnknownTypeInAnyException;
 %0A%0Aim
@@ -1510,16 +1510,78 @@
 sMap %7B%0A%0A
+    private static final char CLASS_PACKAGE_DELIMITER = '.';%0A%0A
     /**%0A
@@ -2895,153 +2895,2057 @@
     
-throw new UnknownTypeInAnyException(protoType.value());%0A        %7D%0A        final ClassName result = namesMap.get(protoType);%0A        return result
+final ClassName className = searchAsSubclass(protoType);%0A            namesMap.put(protoType, className);%0A        %7D%0A        final ClassName result = namesMap.get(protoType);%0A        return result;%0A    %7D%0A%0A    private static ClassName searchAsSubclass(TypeName lookupTypeName) %7B%0A        String lookupType = lookupTypeName.value();%0A        ClassName className = null;%0A        final StringBuilder suffix = new StringBuilder(lookupType.length());%0A%0A        int lastDotPosition = lookupType.lastIndexOf(CLASS_PACKAGE_DELIMITER);%0A        while (className == null && lastDotPosition != -1) %7B%0A            suffix.insert(0, lookupType.substring(lastDotPosition));%0A%0A            lookupType = lookupType.substring(0, lastDotPosition);%0A            final TypeName typeName = TypeName.of(lookupType);%0A%0A            className = namesMap.get(typeName);%0A%0A            lastDotPosition = lookupType.lastIndexOf(CLASS_PACKAGE_DELIMITER);%0A        %7D%0A%0A        if (className == null) %7B%0A            throw new UnknownTypeInAnyException(lookupTypeName.value());%0A        %7D%0A%0A        className = ClassName.of(className.value() + suffix);%0A        try %7B%0A            Class.forName(className.value());%0A        %7D catch (ClassNotFoundException e) %7B%0A            //noinspection ThrowInsideCatchBlockWhichIgnoresCaughtException%0A            throw new UnknownTypeInAnyException(lookupTypeName.value());%0A        %7D%0A        return className;%0A    %7D%0A%0A    private static ClassName searchClassNameRecursively(String lookupType, StringBuilder currentSuffix) %7B%0A        final int lastDotPosition = lookupType.lastIndexOf(CLASS_PACKAGE_DELIMITER);%0A        if (lastDotPosition == -1) %7B%0A            return null;%0A        %7D%0A        String rootType = lookupType.substring(0, lastDotPosition);%0A        currentSuffix.insert(0, lookupType.substring(lastDotPosition));%0A        final TypeName rootTypeName = TypeName.of(rootType);%0A        if (namesMap.get(rootTypeName) == null) %7B%0A            return searchClassNameRecursively(lookupType, currentSuffix);%0A        %7D%0A        return ClassName.of(rootType + currentSuffix)
 ;%0A  

Support sub messages for lookup.