ModuleToPackageSliceLoader.java

// Copyright (c) ZeroC, Inc.

package com.zeroc.Ice;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Implements SliceLoader using a map Slice module to Java package.
 */
public final class ModuleToPackageSliceLoader implements SliceLoader {
    private final Map<String, String> _moduleToPackageMap;
    private final ClassLoader _classLoader;

    // We cache successful resolutions. The size of this cache is bounded by the number of Slice classes and exceptions
    // in the program. We can't cache unsuccessful resolutions because it would create an unbounded cache.
    private final Map<String, Class<?>> _typeIdToClass = new ConcurrentHashMap<>();

    /**
     * Creates a ModuleToPackageSliceLoader.
     *
     * @param moduleToPackageMap A map of Slice module names to Java package names.
     * @param classLoader The class loader to use to load the classes. Can be null.
     */
    public ModuleToPackageSliceLoader(Map<String, String> moduleToPackageMap, ClassLoader classLoader) {
        _moduleToPackageMap = new HashMap<>(moduleToPackageMap);
        _classLoader = classLoader;
    }

    /**
     * Creates a ModuleToPackageSliceLoader using a single module to package mapping.
     *
     * @param module The type ID of the Slice module, for example "::VisitorCenter".
     * @param packageName The Java package name, for example "com.example.visitorcenter".
     */
    public ModuleToPackageSliceLoader(String module, String packageName) {
        this(Map.of(module, packageName), null);
    }

    @Override
    public java.lang.Object newInstance(String typeId) {
        if (!typeId.startsWith("::")) {
            // This could be for example a compact ID. This implementation doesn't handle compact IDs.
            return null;
        }

        // Check cache.
        Class<?> mappedClass = _typeIdToClass.get(typeId);
        if (mappedClass != null) {
            return newInstance(mappedClass, typeId);
        }

        int pos = typeId.lastIndexOf("::");

        while (pos > 0) {
            String className = typeId.substring(pos + 2).replace("::", ".");
            String module = typeId.substring(0, pos);
            String packageName = _moduleToPackageMap.get(module);

            if (packageName != null) {
                mappedClass = Util.findClass(packageName + "." + className, _classLoader);
                if (mappedClass != null) {
                    _typeIdToClass.putIfAbsent(typeId, mappedClass);
                    return newInstance(mappedClass, typeId);
                }
            }

            pos = module.lastIndexOf("::");
        }
        return null;
    }

    private static java.lang.Object newInstance(Class<?> mappedClass, String typeId) {
        try {
            return mappedClass.getDeclaredConstructor().newInstance();
        } catch (Exception ex) {
            throw new MarshalException(String.format(
                    "Failed to create an instance of class '%s' for type ID '%s'.",
                    mappedClass.getName(),
                    typeId),
                ex);
        }
    }
}