/*
 * Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package java.security;

import jdk.internal.event.SecurityProviderServiceEvent;

import javax.crypto.KDFParameters;
import javax.security.auth.login.Configuration;
import java.io.*;
import java.security.cert.CertStoreParameters;
import java.util.*;
import static java.util.Locale.ENGLISH;
import java.lang.ref.*;
import java.lang.reflect.*;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.concurrent.ConcurrentHashMap;

/**
 * This class represents a "provider" for the
 * Java Security API, where a provider implements some or all parts of
 * Java Security. Services that a provider may implement include:
 *
 * <ul>
 *
 * <li>Algorithms (such as DSA, RSA, or SHA-256).
 *
 * <li>Key generation, conversion, and management facilities (such as for
 * algorithm-specific keys).
 *
 * </ul>
 *
 * <p>Some provider implementations may encounter unrecoverable internal
 * errors during their operation, for example a failure to communicate with a
 * security token. A {@link ProviderException} should be used to indicate
 * such errors.
 *
 * <p>Please note that a provider can be used to implement any security
 * service in Java that uses a pluggable architecture with a choice
 * of implementations that fit underneath.
 *
 * <p>The service type {@code Provider} is reserved for use by the
 * security framework. Services of this type cannot be added, removed,
 * or modified by applications.
 * The following attributes are automatically placed in each Provider object:
 * <table class="striped">
 * <caption><b>Attributes Automatically Placed in a Provider Object</b></caption>
 * <thead>
 * <tr><th scope="col">Name</th><th scope="col">Value</th>
 * </thead>
 * <tbody style="text-align:left">
 * <tr><th scope="row">{@code Provider.id name}</th>
 *     <td>{@code String.valueOf(provider.getName())}</td>
 * <tr><th scope="row">{@code Provider.id version}</th>
 *     <td>{@code String.valueOf(provider.getVersionStr())}</td>
 * <tr><th scope="row">{@code Provider.id info}</th>
 *     <td>{@code String.valueOf(provider.getInfo())}</td>
 * <tr><th scope="row">{@code Provider.id className}</th>
 *     <td>{@code provider.getClass().getName()}</td>
 * </tbody>
 * </table>
 *
 * <p>Each provider has a name and a version string. A provider normally
 * identifies itself with a file named {@code java.security.Provider}
 * in the resource directory {@code META-INF/services}.
 * Security providers are looked up via the {@link ServiceLoader} mechanism
 * using the {@link ClassLoader#getSystemClassLoader application class loader}.
 *
 * <p>Providers may be configured such that they are automatically
 * installed and made available at runtime via the
 * {@link Security#getProviders() Security.getProviders()} method.
 * The mechanism for configuring and installing security providers is
 * implementation-specific.
 *
 * @implNote
 * The JDK implementation supports static registration of the security
 * providers via the {@code conf/security/java.security} file in the Java
 * installation directory. These providers are automatically installed by
 * the JDK runtime, see {@extLink security_guide_jca_provider
 * The Provider Class}
 * in the Java Cryptography Architecture (JCA) Reference Guide
 * for information about how a particular type of provider, the cryptographic
 * service provider, works and is installed.
 *
 * @author Benjamin Renaud
 * @author Andreas Sterbenz
 * @since 1.1
 */
public abstract class Provider extends Properties {

    // Declare serialVersionUID to be compatible with JDK1.1
    @java.io.Serial
    private static final long serialVersionUID = -4298000515446427739L;

    private static final sun.security.util.Debug debug =
        sun.security.util.Debug.getInstance("provider", "Provider");

    /**
     * The provider name.
     *
     * @serial
     */
    private final String name;

    /**
     * A description of the provider and its services.
     *
     * @serial
     */
    private final String info;

    /**
     * The provider version number.
     *
     * @serial
     */
    private double version;

    /**
     * The provider version string.
     *
     * @serial
     */
    private String versionStr;

    private transient Set<Map.Entry<Object,Object>> entrySet = null;
    private transient int entrySetCallCount = 0;

    private transient boolean initialized;

    private static final Object[] EMPTY = new Object[0];

    private static double parseVersionStr(String s) {
        try {
            int firstDotIdx = s.indexOf('.');
            int nextDotIdx = s.indexOf('.', firstDotIdx + 1);
            if (nextDotIdx != -1) {
                s = s.substring(0, nextDotIdx);
            }
            int endIdx = s.indexOf('-');
            if (endIdx > 0) {
                s = s.substring(0, endIdx);
            }
            endIdx = s.indexOf('+');
            if (endIdx > 0) {
                s = s.substring(0, endIdx);
            }
            return Double.parseDouble(s);
        } catch (NullPointerException | NumberFormatException e) {
            return 0d;
        }
    }

    /**
     * Constructs a {@code Provider} with the specified name, version number,
     * and information. Calling this constructor is equivalent to call the
     * {@link #Provider(String, String, String)} with {@code name}
     * name, {@code Double.toString(version)}, and {@code info}.
     *
     * @param name the provider name.
     *
     * @param version the provider version number.
     *
     * @param info a description of the provider and its services.
     *
     * @deprecated use {@link #Provider(String, String, String)} instead.
     */
    @Deprecated(since="9")
    @SuppressWarnings("this-escape")
    protected Provider(String name, double version, String info) {
        this.name = name;
        this.version = version;
        this.versionStr = Double.toString(version);
        this.info = info;
        this.serviceMap = new ConcurrentHashMap<>();
        this.legacyMap = new ConcurrentHashMap<>();
        this.prngAlgos = new LinkedHashSet<>(6);
        putId();
        initialized = true;
    }

    /**
     * Constructs a {@code Provider} with the specified name, version string,
     * and information.
     *
     * <p>The version string contains a version number optionally followed
     * by other information separated by one of the characters of '+', '-'.
     *
     * The format for the version number is:
     *
     * <blockquote><pre>
     *     ^[0-9]+(\.[0-9]+)*
     * </pre></blockquote>
     *
     * <p>In order to return the version number in a double, when there are
     * more than two components (separated by '.' as defined above), only
     * the first two components are retained. The resulting string is then
     * passed to {@link Double#valueOf(String)} to generate version number,
     * i.e. {@link #getVersion}.
     * <p>If the conversion failed, value 0 will be used.
     *
     * @param name the provider name.
     *
     * @param versionStr the provider version string.
     *
     * @param info a description of the provider and its services.
     *
     * @since 9
     */
    @SuppressWarnings("this-escape")
    protected Provider(String name, String versionStr, String info) {
        this.name = name;
        this.versionStr = versionStr;
        this.version = parseVersionStr(versionStr);
        this.info = info;
        this.serviceMap = new ConcurrentHashMap<>();
        this.legacyMap = new ConcurrentHashMap<>();
        this.prngAlgos = new LinkedHashSet<>(6);
        putId();
        initialized = true;
    }

    /**
     * Apply the supplied configuration argument to this {@code Provider}
     * instance and return the configured {@code Provider}. Note that if
     * this {@code Provider} cannot be configured in-place, a new
     * {@code Provider} will be created and returned. Therefore,
     * callers should always use the returned {@code Provider}.
     *
     * @implSpec
     * The default implementation throws {@code UnsupportedOperationException}.
     * Subclasses should override this method only if a configuration argument
     * is supported.
     *
     * @param configArg the configuration information for configuring this
     *         provider.
     *
     * @throws UnsupportedOperationException if a configuration argument is
     *         not supported.
     * @throws NullPointerException if the supplied configuration argument is
     *         {@code null}.
     * @throws InvalidParameterException if the supplied configuration argument
     *         is invalid.
     * @return a {@code Provider} configured with the supplied configuration
     *         argument.
     *
     * @since 9
     */
    public Provider configure(String configArg) {
        throw new UnsupportedOperationException("configure is not supported");
    }

    /**
     * Check if this {@code Provider} instance has been configured.
     *
     * @implSpec
     * The default implementation returns {@code true}.
     * Subclasses should override this method if the {@code Provider} requires
     * an explicit {@code configure} call after being constructed.
     *
     * @return {@code true} if no further configuration is needed,
     * {@code false} otherwise.
     *
     * @since 9
     */
    public boolean isConfigured() {
        return true;
    }


    /**
     * Returns the name of this {@code Provider}.
     *
     * @return the name of this {@code Provider}.
     */
    public String getName() {
        return name;
    }

    /**
     * Returns the version number for this {@code Provider}.
     *
     * @return the version number for this {@code Provider}.
     *
     * @deprecated use {@link #getVersionStr} instead.
     */
    @Deprecated(since="9")
    public double getVersion() {
        return version;
    }

    /**
     * Returns the version string for this {@code Provider}.
     *
     * @return the version string for this {@code Provider}.
     *
     * @since 9
     */
    public String getVersionStr() {
        return versionStr;
    }

    /**
     * Returns a human-readable description of the {@code Provider} and its
     * services.  This may return an HTML page, with relevant links.
     *
     * @return a description of the {@code Provider} and its services.
     */
    public String getInfo() {
        return info;
    }

    /**
     * Returns a string with the name and the version string
     * of this {@code Provider}.
     *
     * @return the string with the name and the version string
     * for this {@code Provider}.
     */
    public String toString() {
        return name + " version " + versionStr;
    }

    /**
     * Clears this {@code Provider} so that it no longer contains the properties
     * used to look up facilities implemented by the {@code Provider}.
     *
     * @since 1.2
     */
    @Override
    public synchronized void clear() {
        checkInitialized();
        if (debug != null) {
            debug.println("Remove " + name + " provider properties");
        }
        implClear();
    }

    /**
     * Reads a property list (key and element pairs) from the input stream.
     *
     * @param inStream the input stream.
     * @throws    IOException if an error occurred when reading from the
     *               input stream.
     * @see java.util.Properties#load
     */
    @Override
    public synchronized void load(InputStream inStream) throws IOException {
        checkInitialized();
        if (debug != null) {
            debug.println("Load " + name + " provider properties");
        }
        Properties tempProperties = new Properties();
        tempProperties.load(inStream);
        implPutAll(tempProperties);
    }

    /**
     * Copies all the mappings from the specified Map to this {@code Provider}.
     * These mappings will replace any properties that this {@code Provider} had
     * for any of the keys currently in the specified Map.
     *
     * @since 1.2
     */
    @Override
    public synchronized void putAll(Map<?,?> t) {
        checkInitialized();
        if (debug != null) {
            debug.println("Put all " + name + " provider properties");
        }
        implPutAll(t);
    }

    /**
     * Returns an unmodifiable Set view of the property entries contained
     * in this {@code Provider}.
     *
     * @see   java.util.Map.Entry
     * @since 1.2
     */
    @Override
    public synchronized Set<Map.Entry<Object,Object>> entrySet() {
        checkInitialized();
        if (entrySet == null) {
            if (entrySetCallCount++ == 0)  // Initial call
                entrySet = Collections.unmodifiableMap(this).entrySet();
            else
                return super.entrySet();   // Recursive call
        }

        // This exception will be thrown if the implementation of
        // Collections.unmodifiableMap.entrySet() is changed such that it
        // no longer calls entrySet() on the backing Map.  (Provider's
        // entrySet implementation depends on this "implementation detail",
        // which is unlikely to change.
        if (entrySetCallCount != 2)
            throw new RuntimeException("Internal error.");

        return entrySet;
    }

    /**
     * Returns an unmodifiable Set view of the property keys contained in
     * this {@code Provider}.
     *
     * @since 1.2
     */
    @Override
    public Set<Object> keySet() {
        checkInitialized();
        return Collections.unmodifiableSet(super.keySet());
    }

    /**
     * Returns an unmodifiable Collection view of the property values
     * contained in this {@code Provider}.
     *
     * @since 1.2
     */
    @Override
    public Collection<Object> values() {
        checkInitialized();
        return Collections.unmodifiableCollection(super.values());
    }

    /**
     * Sets the {@code key} property to have the specified
     * {@code value}.
     *
     * @since 1.2
     */
    @Override
    public synchronized Object put(Object key, Object value) {
        checkInitialized();
        if (debug != null) {
            debug.println("Set " + name + " provider property [" +
                          key + "/" + value +"]");
        }
        return implPut(key, value);
    }

    /**
     * If the specified key is not already associated with a value (or is mapped
     * to {@code null}) associates it with the given value and returns
     * {@code null}, else returns the current value.
     *
     * @since 1.8
     */
    @Override
    public synchronized Object putIfAbsent(Object key, Object value) {
        checkInitialized();
        if (debug != null) {
            debug.println("Set " + name + " provider property [" +
                          key + "/" + value +"]");
        }
        return implPutIfAbsent(key, value);
    }

    /**
     * Removes the {@code key} property (and its corresponding
     * {@code value}).
     *
     * @since 1.2
     */
    @Override
    public synchronized Object remove(Object key) {
        checkInitialized();
        if (debug != null) {
            debug.println("Remove " + name + " provider property " + key);
        }
        return implRemove(key);
    }

    /**
     * Removes the entry for the specified key only if it is currently
     * mapped to the specified value.
     *
     * @since 1.8
     */
    @Override
    public synchronized boolean remove(Object key, Object value) {
        checkInitialized();
        if (debug != null) {
            debug.println("Remove " + name + " provider property " + key);
        }
        return implRemove(key, value);
    }

    /**
     * Replaces the entry for the specified key only if currently
     * mapped to the specified value.
     *
     * @since 1.8
     */
    @Override
    public synchronized boolean replace(Object key, Object oldValue,
            Object newValue) {
        checkInitialized();
        if (debug != null) {
            debug.println("Replace " + name + " provider property " + key);
        }
        return implReplace(key, oldValue, newValue);
    }

    /**
     * Replaces the entry for the specified key only if it is
     * currently mapped to some value.
     *
     * @since 1.8
     */
    @Override
    public synchronized Object replace(Object key, Object value) {
        checkInitialized();
        if (debug != null) {
            debug.println("Replace " + name + " provider property " + key);
        }
        return implReplace(key, value);
    }

    /**
     * Replaces each entry's value with the result of invoking the given
     * function on that entry, in the order entries are returned by an entry
     * set iterator, until all entries have been processed or the function
     * throws an exception.
     *
     * @since 1.8
     */
    @Override
    public synchronized void replaceAll(BiFunction<? super Object,
            ? super Object, ? extends Object> function) {
        checkInitialized();
        if (debug != null) {
            debug.println("ReplaceAll " + name + " provider property ");
        }
        implReplaceAll(function);
    }

    /**
     * Attempts to compute a mapping for the specified key and its
     * current mapped value (or {@code null} if there is no current
     * mapping).
     *
     * @since 1.8
     */
    @Override
    public synchronized Object compute(Object key, BiFunction<? super Object,
            ? super Object, ? extends Object> remappingFunction) {
        checkInitialized();
        if (debug != null) {
            debug.println("Compute " + name + " provider property " + key);
        }
        return implCompute(key, remappingFunction);
    }

    /**
     * If the specified key is not already associated with a value (or
     * is mapped to {@code null}), attempts to compute its value using
     * the given mapping function and enters it into this map unless
     * {@code null}.
     *
     * @since 1.8
     */
    @Override
    public synchronized Object computeIfAbsent(Object key,
            Function<? super Object, ? extends Object> mappingFunction) {
        checkInitialized();
        if (debug != null) {
            debug.println("ComputeIfAbsent " + name + " provider property " +
                    key);
        }
        return implComputeIfAbsent(key, mappingFunction);
    }

    /**
     * If the value for the specified key is present and non-null, attempts to
     * compute a new mapping given the key and its current mapped value.
     *
     * @since 1.8
     */
    @Override
    public synchronized Object computeIfPresent(Object key,
            BiFunction<? super Object, ? super Object, ? extends Object>
            remappingFunction) {
        checkInitialized();
        if (debug != null) {
            debug.println("ComputeIfPresent " + name + " provider property " +
                    key);
        }
        return implComputeIfPresent(key, remappingFunction);
    }

    /**
     * If the specified key is not already associated with a value or is
     * associated with {@code null}, associates it with the given value.
     * Otherwise, replaces the value with the results of the given remapping
     * function, or removes if the result is {@code null}. This method may be
     * of use when combining multiple mapped values for a key.
     *
     * @since 1.8
     */
    @Override
    public synchronized Object merge(Object key, Object value,
            BiFunction<? super Object, ? super Object, ? extends Object>
            remappingFunction) {
        checkInitialized();
        if (debug != null) {
            debug.println("Merge " + name + " provider property " + key);
        }
        return implMerge(key, value, remappingFunction);
    }

    // let javadoc show doc from superclass
    @Override
    public Object get(Object key) {
        checkInitialized();
        return super.get(key);
    }
    /**
     * @since 1.8
     */
    @Override
    public synchronized Object getOrDefault(Object key, Object defaultValue) {
        checkInitialized();
        return super.getOrDefault(key, defaultValue);
    }

    /**
     * @since 1.8
     */
    @Override
    public synchronized void forEach(BiConsumer<? super Object, ? super Object>
            action) {
        checkInitialized();
        super.forEach(action);
    }

    // let javadoc show doc from superclass
    @Override
    public Enumeration<Object> keys() {
        checkInitialized();
        return super.keys();
    }

    // let javadoc show doc from superclass
    @Override
    public Enumeration<Object> elements() {
        checkInitialized();
        return super.elements();
    }

    // let javadoc show doc from superclass
    public String getProperty(String key) {
        checkInitialized();
        return super.getProperty(key);
    }

    private void checkInitialized() {
        if (!initialized) {
            throw new IllegalStateException();
        }
    }

    // legacyMap changed since last call to getServices()
    private transient volatile boolean legacyChanged;
    // serviceMap changed since last call to getServices()
    private transient volatile boolean servicesChanged;

    // Map<ServiceKey,Service>
    // used for services added via putService(), initialized on demand
    private transient Map<ServiceKey,Service> serviceMap;

    // For backward compatibility, the registration ordering of
    // SecureRandom (RNG) algorithms needs to be preserved for
    // "new SecureRandom()" calls when this provider is used
    // NOTE: may need extra mechanism for providers to indicate their
    // preferred ordering of SecureRandom algorithms since registration
    // ordering info is lost once serialized
    private transient Set<String> prngAlgos;

    // Map<ServiceKey,Service>
    // used for services added via legacy methods, init on demand
    private transient Map<ServiceKey,Service> legacyMap;

    // Set<Service>
    // Unmodifiable set of all services. Initialized on demand.
    private transient volatile Set<Service> serviceSet;

    // register the id attributes for this provider
    // this is to ensure that equals() and hashCode() do not incorrectly
    // report to different provider objects as the same
    private void putId() {
        // note: name and info may be null
        super.put("Provider.id name", String.valueOf(name));
        super.put("Provider.id version", String.valueOf(versionStr));
        super.put("Provider.id info", String.valueOf(info));
        super.put("Provider.id className", this.getClass().getName());
    }

   /**
    * Reads the {@code ObjectInputStream} for the default serializable fields.
    * If the serialized field {@code versionStr} is found in the STREAM FIELDS,
    * its {@code String} value will be used to populate both the version string
    * and version number. If {@code versionStr} is not found, but
    * {@code version} is, then its double value will be used to populate
    * both fields.
    *
    * @param in the {@code ObjectInputStream} to read
    * @throws IOException if an I/O error occurs
    * @throws ClassNotFoundException if a serialized class cannot be loaded
    */
    @java.io.Serial
    private void readObject(ObjectInputStream in)
                throws IOException, ClassNotFoundException {
        Map<Object,Object> copy = new HashMap<>();
        for (Map.Entry<Object,Object> entry : super.entrySet()) {
            copy.put(entry.getKey(), entry.getValue());
        }

        defaults = null;
        in.defaultReadObject();
        if (this.versionStr == null) {
            // set versionStr based on version when not found in serialized bytes
            this.versionStr = Double.toString(this.version);
        } else {
            // otherwise, set version based on versionStr
            this.version = parseVersionStr(this.versionStr);
        }
        this.serviceMap = new ConcurrentHashMap<>();
        this.legacyMap = new ConcurrentHashMap<>();
        this.prngAlgos = new LinkedHashSet<>(6);
        implClear();
        initialized = true;
        putAll(copy);
    }

    // returns false if no update necessary, i.e. key isn't String or
    // is String, but it's provider-related (name/version/info/className)
    private static boolean checkLegacy(Object key) {
        if (key instanceof String && ((String)key).startsWith("Provider.")) {
            // ignore provider related updates
            return false;
        } else {
            return true;
        }
    }

    /**
     * Copies all the mappings from the specified Map to this provider.
     */
    private void implPutAll(Map<?,?> t) {
        for (Map.Entry<?,?> e : t.entrySet()) {
            implPut(e.getKey(), e.getValue());
        }
    }

    private Object implRemove(Object key) {
        if (!checkLegacy(key)) return null;

        Object o = super.remove(key);
        if (o instanceof String so && key instanceof String sk) {
            parseLegacy(sk, so, OPType.REMOVE);
        }
        return o;
    }

    private boolean implRemove(Object key, Object value) {
        if (!checkLegacy(key)) return false;

        boolean result = super.remove(key, value);
        if (result && key instanceof String sk && value instanceof String sv) {
            parseLegacy(sk, sv, OPType.REMOVE);
        }
        return result;
    }

    private boolean implReplace(Object key, Object oldValue, Object newValue) {
        if (!checkLegacy(key)) return false;

        boolean result = super.replace(key, oldValue, newValue);
        if (result && key instanceof String sk) {
            if (newValue instanceof String sv) {
                parseLegacy(sk, sv, OPType.ADD);
            } else if (oldValue instanceof String sv) {
                parseLegacy(sk, sv, OPType.REMOVE);
            }
        }
        return result;
    }

    private Object implReplace(Object key, Object value) {
        if (!checkLegacy(key)) return null;

        Object o = super.replace(key, value);
        if (key instanceof String sk) {
            if (o instanceof String so) {
                if (value instanceof String sv) {
                    parseLegacy(sk, sv, OPType.ADD);
                } else {
                    parseLegacy(sk, so, OPType.REMOVE);
                }
            }
        }
        return o;
    }

    // Function must actually operate over strings
    private void implReplaceAll(BiFunction<? super Object, ? super Object,
            ? extends Object> function) {

        super.replaceAll(function);
        // clear out all existing mappings and start fresh
        legacyMap.clear();
        legacyChanged = true;
        for (Map.Entry<Object, Object> entry : super.entrySet()) {
            Object key = entry.getKey();
            Object value = entry.getValue();
            if ((key instanceof String sk) && (value instanceof String sv)) {
                if (!checkLegacy(sk)) {
                    continue;
                }
                parseLegacy(sk, sv, OPType.ADD);
            }
        }
    }

    // Function must actually operate over strings
    private Object implMerge(Object key, Object value,
            BiFunction<? super Object, ? super Object, ? extends Object>
            remappingFunction) {
        if (!checkLegacy(key)) return null;

        Object o = super.merge(key, value, remappingFunction);
        if (key instanceof String sk) {
            if (o == null) {
                parseLegacy(sk, null, OPType.REMOVE);
            } else if (o instanceof String so) {
                parseLegacy(sk, so, OPType.ADD);
            }
        }
        return o;
    }

    // Function must actually operate over strings
    private Object implCompute(Object key, BiFunction<? super Object,
            ? super Object, ? extends Object> remappingFunction) {

        if (!checkLegacy(key)) return null;

        Object o = super.compute(key, remappingFunction);
        if (key instanceof String sk) {
            if (o == null) {
                parseLegacy(sk, null, OPType.REMOVE);
            } else if (o instanceof String so) {
                parseLegacy(sk, so, OPType.ADD);
            }
        }
        return o;
    }

    // Function must actually operate over strings
    private Object implComputeIfAbsent(Object key, Function<? super Object,
            ? extends Object> mappingFunction) {
        if (!checkLegacy(key)) return null;

        Object o = super.computeIfAbsent(key, mappingFunction);
        if (o instanceof String so && key instanceof String sk) {
            parseLegacy(sk, so, OPType.ADD);
        }
        return o;
    }

    // Function must actually operate over strings
    private Object implComputeIfPresent(Object key, BiFunction<? super Object,
            ? super Object, ? extends Object> remappingFunction) {
        if (!checkLegacy(key)) return null;

        Object o = super.computeIfPresent(key, remappingFunction);
        if (o instanceof String so && key instanceof String sk) {
            parseLegacy(sk, so, OPType.ADD);
        }
        return o;
    }

    private Object implPut(Object key, Object value) {
        if (!checkLegacy(key)) return null;

        Object o = super.put(key, value);
        if (key instanceof String sk && value instanceof String sv) {
            parseLegacy(sk, sv, OPType.ADD);
        }
        return o;
    }

    private Object implPutIfAbsent(Object key, Object value) {
        if (!checkLegacy(key)) return null;

        Object o = super.putIfAbsent(key, value);
        if (o == null && key instanceof String sk &&
                value instanceof String sv) {
            parseLegacy(sk, sv, OPType.ADD);
        }
        return o;
    }

    private void implClear() {
        legacyMap.clear();
        serviceMap.clear();
        legacyChanged = false;
        servicesChanged = false;
        serviceSet = null;
        prngAlgos.clear();
        super.clear();
        putId();
    }

    // used as key in the serviceMap and legacyMap HashMaps
    private static class ServiceKey {
        private final String type;
        private final String algorithm;
        private final String originalAlgorithm;
        private ServiceKey(String type, String algorithm, boolean intern) {
            this.type = type;
            this.originalAlgorithm = algorithm;
            algorithm = algorithm.toUpperCase(ENGLISH);
            this.algorithm = intern ? algorithm.intern() : algorithm;
        }
        public int hashCode() {
            return type.hashCode() * 31 + algorithm.hashCode();
        }
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            return obj instanceof ServiceKey other
                && this.type.equals(other.type)
                && this.algorithm.equals(other.algorithm);
        }

        // Don't change '==' to equals.
        // This method tests for equality of pointers.
        boolean matches(String type, String algorithm) {
            return (this.type == type) && (this.originalAlgorithm == algorithm);
        }
        public String toString() {
            return type + "." + algorithm;
        }
    }

    private static String[] getTypeAndAlgorithm(String key) {
        int i = key.indexOf('.');
        if (i < 1) {
            if (debug != null) {
                debug.println("Ignoring invalid entry in provider: "
                        + key);
            }
            return null;
        }
        String type = key.substring(0, i);
        String alg = key.substring(i + 1);
        return new String[] {type, alg};
    }

    private static final String ALIAS_PREFIX = "Alg.Alias.";
    private static final String ALIAS_PREFIX_LOWER = "alg.alias.";
    private static final int ALIAS_LENGTH = ALIAS_PREFIX.length();

    private enum OPType {
        ADD, REMOVE
    }

    private void parseLegacy(String name, String value, OPType opType) {
        // alias
        if (name.toLowerCase(ENGLISH).startsWith(ALIAS_PREFIX_LOWER)) {
            // e.g. put("Alg.Alias.MessageDigest.SHA", "SHA-1");
            // aliasKey ~ MessageDigest.SHA
            String aliasKeyStr = name.substring(ALIAS_LENGTH);
            String[] typeAndAlg = getTypeAndAlgorithm(aliasKeyStr);
            if (typeAndAlg == null) {
                return;
            }
            legacyChanged = true;
            Objects.requireNonNull(value, "alias value should map to an alg");
            String type = getEngineName(typeAndAlg[0]);
            String aliasAlg = typeAndAlg[1].intern();
            ServiceKey stdKey = new ServiceKey(type, value, true);
            Service stdService = legacyMap.get(stdKey);
            ServiceKey aliasKey = new ServiceKey(type, aliasAlg, true);
            switch (opType) {
                case ADD:
                    // clean up old alias if present
                    Service prevAliasService = legacyMap.get(aliasKey);
                    if (prevAliasService != null) {
                        prevAliasService.removeAlias(aliasAlg);
                    }
                    if (stdService == null) {
                        // add standard mapping in order to add alias
                        stdService = new Service(this, type, value);
                        legacyMap.put(stdKey, stdService);
                    }
                    stdService.addAlias(aliasAlg);
                    legacyMap.put(aliasKey, stdService);
                    break;
                case REMOVE:
                    if (stdService != null) {
                        stdService.removeAlias(aliasAlg);
                    }
                    legacyMap.remove(aliasKey);
                    break;
                default:
                    throw new AssertionError();
            }
        } else {
            String[] typeAndAlg = getTypeAndAlgorithm(name);
            if (typeAndAlg == null) {
                return;
            }
            legacyChanged = true;
            int i = typeAndAlg[1].indexOf(' ');
            // regular registration
            if (i == -1) {
                // e.g. put("MessageDigest.SHA-1", "sun.security.provider.SHA");
                String type = getEngineName(typeAndAlg[0]);
                String stdAlg = typeAndAlg[1].intern();
                ServiceKey stdKey = new ServiceKey(type, stdAlg, true);
                Service stdService = legacyMap.get(stdKey);
                switch (opType) {
                    case ADD:
                        Objects.requireNonNull(value,
                                "className can't be null");
                        if (stdService == null) {
                            stdService = new Service(this, type, stdAlg);
                            legacyMap.put(stdKey, stdService);
                        }
                        stdService.className = value;
                        break;
                    case REMOVE:
                        // only remove if value also matches when non-null
                        if (stdService != null) {
                            if (value == null) {
                                legacyMap.remove(stdKey);
                            } else if (stdService.className.equals(value)) {
                                legacyMap.remove(stdKey, stdService);
                            }
                            // remove all corresponding alias mappings
                            for (String alias : stdService.getAliases()) {
                                legacyMap.remove(new ServiceKey(type, alias,
                                        true), stdService);
                            }
                        }
                        break;
                    default:
                        throw new AssertionError();
                }
                checkAndUpdateSecureRandom(type, stdAlg,
                        (opType != OPType.REMOVE));
            } else { // attribute
                // e.g. put("MessageDigest.SHA-1 ImplementedIn", "Software");
                String type = getEngineName(typeAndAlg[0]);
                String attrString = typeAndAlg[1];
                String stdAlg = attrString.substring(0, i).intern();
                String attrName = attrString.substring(i + 1);
                // kill additional spaces
                while (attrName.startsWith(" ")) {
                    attrName = attrName.substring(1);
                }
                attrName = attrName.intern();
                ServiceKey stdKey = new ServiceKey(type, stdAlg, true);
                Service stdService = legacyMap.get(stdKey);
                switch (opType) {
                    case ADD:
                        Objects.requireNonNull(value,
                                "attribute value should not be null");

                        if (stdService == null) {
                            stdService = new Service(this, type, stdAlg);
                            legacyMap.put(stdKey, stdService);
                        }
                        stdService.addAttribute(attrName, value);
                        break;
                    case REMOVE:
                        if (stdService != null) {
                            stdService.removeAttribute(attrName, value);
                        }
                        break;
                default:
                    throw new AssertionError();
                }
            }
        }
    }

    /**
     * Get the service describing this Provider's implementation of the
     * specified type of this algorithm or alias. If no such
     * implementation exists, this method returns {@code null}. If there are two
     * matching services, one added to this provider using
     * {@link #putService putService()} and one added via {@link #put put()},
     * the service added via {@link #putService putService()} is returned.
     *
     * @param type the type of {@link Service service} requested
     * (for example, {@code MessageDigest})
     * @param algorithm the case-insensitive algorithm name (or alternate
     * alias) of the service requested (for example, {@code SHA-1})
     *
     * @return the service describing this Provider's matching service
     * or {@code null} if no such service exists
     *
     * @throws NullPointerException if type or algorithm is {@code null}
     *
     * @since 1.5
     */
    public Service getService(String type, String algorithm) {
        checkInitialized();
        // avoid allocating a new ServiceKey object if possible
        ServiceKey key = previousKey.get();
        if (!key.matches(type, algorithm)) {
            key = new ServiceKey(type, algorithm, false);
            previousKey.set(key);
        }

        Service s = serviceMap.get(key);
        if (s == null) {
            s = legacyMap.get(key);
            if (s != null && !s.isValid()) {
                legacyMap.remove(key, s);
                return null;
            }
        }

        if (s != null && SecurityProviderServiceEvent.isTurnedOn()) {
            var e  = new SecurityProviderServiceEvent();
            e.provider = getName();
            e.type = type;
            e.algorithm = algorithm;
            e.commit();
        }

        return s;
    }

    // ServiceKey from previous getService() call
    // by re-using it if possible we avoid allocating a new object
    // and the toUpperCase() call.
    // re-use will occur e.g. as the framework traverses the provider
    // list and queries each provider with the same values until it finds
    // a matching service
    private static final ThreadLocal<ServiceKey> previousKey =
        ThreadLocal.withInitial(() -> new ServiceKey("","", false));

    /**
     * Get an unmodifiable Set of all services supported by
     * this {@code Provider}.
     *
     * @return an unmodifiable Set of all services supported by
     * this {@code Provider}
     *
     * @since 1.5
     */
    public Set<Service> getServices() {
        checkInitialized();
        if (serviceSet == null || legacyChanged || servicesChanged) {
            Set<Service> set = new LinkedHashSet<>();
            if (!serviceMap.isEmpty()) {
                set.addAll(serviceMap.values());
            }
            if (!legacyMap.isEmpty()) {
                legacyMap.entrySet().forEach(entry -> {
                    if (!entry.getValue().isValid()) {
                        legacyMap.remove(entry.getKey(), entry.getValue());
                    } else {
                        set.add(entry.getValue());
                    }
                });
            }
            serviceSet = Collections.unmodifiableSet(set);
            servicesChanged = false;
            legacyChanged = false;
        }
        return serviceSet;
    }

    /**
     * Add a service. If a service of the same type with the same algorithm
     * name exists, and it was added using {@link #putService putService()},
     * it is replaced by the new service.
     * This method also places information about this service
     * in the provider's Hashtable values in the format described in the
     * {@extLink security_guide_jca
     * Java Cryptography Architecture (JCA) Reference Guide}.
     *
     * @param s the Service to add
     *
     * @throws NullPointerException if s is {@code null}
     *
     * @since 1.5
     */
    protected void putService(Service s) {
        checkInitialized();
        if (debug != null) {
            debug.println(name + ".putService(): " + s);
        }
        if (s == null) {
            throw new NullPointerException();
        }
        if (s.getProvider() != this) {
            throw new IllegalArgumentException
                    ("service.getProvider() must match this Provider object");
        }
        String type = s.getType();
        String algorithm = s.getAlgorithm();
        ServiceKey key = new ServiceKey(type, algorithm, true);
        implRemoveService(serviceMap.get(key));
        serviceMap.put(key, s);
        for (String alias : s.getAliases()) {
            serviceMap.put(new ServiceKey(type, alias, true), s);
        }
        servicesChanged = true;
        synchronized (this) {
            putPropertyStrings(s);
            checkAndUpdateSecureRandom(type, algorithm, true);
        }
    }

    private void checkAndUpdateSecureRandom(String type, String algo,
            boolean doAdd) {
        if (type.equalsIgnoreCase("SecureRandom")) {
            if (doAdd) {
                prngAlgos.add(algo);
            } else {
                prngAlgos.remove(algo);
            }
            if (debug != null) {
                debug.println((doAdd? "Add":"Remove") +
                        " SecureRandom algo " + algo);
            }
        }
    }

    // used by new SecureRandom() to find out the default SecureRandom
    // service for this provider
    Service getDefaultSecureRandomService() {
        checkInitialized();

        if (!prngAlgos.isEmpty()) {
            String algo = prngAlgos.iterator().next();
            // IMPORTANT: use the Service obj returned by getService(...) call
            // as providers may override putService(...)/getService(...) and
            // return their own Service objects
            return getService("SecureRandom", algo);
        }

        return null;
    }

    /**
     * Put the string properties for this Service in this Provider's
     * Hashtable.
     */
    private void putPropertyStrings(Service s) {
        String type = s.getType();
        String algorithm = s.getAlgorithm();
        // use super() to avoid other processing
        super.put(type + "." + algorithm, s.getClassName());
        for (String alias : s.getAliases()) {
            super.put(ALIAS_PREFIX + type + "." + alias, algorithm);
        }
        for (Map.Entry<UString,String> entry : s.attributes.entrySet()) {
            String key = type + "." + algorithm + " " + entry.getKey();
            super.put(key, entry.getValue());
        }
    }

    /**
     * Remove the string properties for this Service from this Provider's
     * Hashtable.
     */
    private void removePropertyStrings(Service s) {
        String type = s.getType();
        String algorithm = s.getAlgorithm();
        // use super() to avoid other processing
        super.remove(type + "." + algorithm);
        for (String alias : s.getAliases()) {
            super.remove(ALIAS_PREFIX + type + "." + alias);
        }
        for (Map.Entry<UString,String> entry : s.attributes.entrySet()) {
            String key = type + "." + algorithm + " " + entry.getKey();
            super.remove(key);
        }
    }

    /**
     * Remove a service previously added using
     * {@link #putService putService()}. The specified service is removed from
     * this {@code Provider}. It will no longer be returned by
     * {@link #getService getService()} and its information will be removed
     * from this provider's Hashtable.
     *
     * @param s the Service to be removed
     *
     * @throws NullPointerException if s is {@code null}
     *
     * @since 1.5
     */
    protected void removeService(Service s) {
        checkInitialized();
        if (debug != null) {
            debug.println(name + ".removeService(): " + s);
        }
        if (s == null) {
            throw new NullPointerException();
        }
        implRemoveService(s);
    }

    private void implRemoveService(Service s) {
        if ((s == null) || serviceMap.isEmpty()) {
            return;
        }
        String type = s.getType();
        String algorithm = s.getAlgorithm();
        ServiceKey key = new ServiceKey(type, algorithm, false);
        Service oldService = serviceMap.get(key);
        if (s != oldService) {
            return;
        }
        servicesChanged = true;
        serviceMap.remove(key);
        for (String alias : s.getAliases()) {
            serviceMap.remove(new ServiceKey(type, alias, false));
        }

        removePropertyStrings(s);
        checkAndUpdateSecureRandom(type, algorithm, false);
    }

    // Wrapped String that behaves in a case-insensitive way for equals/hashCode
    private static class UString {
        final String string;
        final String lowerString;

        UString(String s) {
            this.string = s;
            this.lowerString = s.toLowerCase(ENGLISH);
        }

        public int hashCode() {
            return lowerString.hashCode();
        }

        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            return obj instanceof UString other
                    && lowerString.equals(other.lowerString);
        }

        public String toString() {
            return string;
        }
    }

    // describe relevant properties of a type of engine
    private static class EngineDescription {
        final String name;
        final boolean supportsParameter;
        final Class<?> constructorParameterClass;

        EngineDescription(String name, boolean sp, Class<?> constructorParameterClass) {
            this.name = name;
            this.supportsParameter = sp;
            this.constructorParameterClass = constructorParameterClass;
        }
    }

    // built in knowledge of the engine types shipped as part of the JDK
    private static final Map<String,EngineDescription> knownEngines;

    private static void addEngine(String name, boolean sp, Class<?> constructorParameterClass) {
        EngineDescription ed = new EngineDescription(name, sp, constructorParameterClass);
        // also index by canonical name to avoid toLowerCase() for some lookups
        knownEngines.put(name.toLowerCase(ENGLISH), ed);
        knownEngines.put(name, ed);
    }

    static {
        knownEngines = new HashMap<>();
        // JCA
        addEngine("AlgorithmParameterGenerator",        false, null);
        addEngine("AlgorithmParameters",                false, null);
        addEngine("KeyFactory",                         false, null);
        addEngine("KeyPairGenerator",                   false, null);
        addEngine("KeyStore",                           false, null);
        addEngine("MessageDigest",                      false, null);
        addEngine("SecureRandom",                       false,
                SecureRandomParameters.class);
        addEngine("Signature",                          true,  null);
        addEngine("CertificateFactory",                 false, null);
        addEngine("CertPathBuilder",                    false, null);
        addEngine("CertPathValidator",                  false, null);
        addEngine("CertStore",                          false,
                CertStoreParameters.class);
        // JCE
        addEngine("Cipher",                             true,  null);
        addEngine("ExemptionMechanism",                 false, null);
        addEngine("Mac",                                true,  null);
        addEngine("KeyAgreement",                       true,  null);
        addEngine("KeyGenerator",                       false, null);
        addEngine("SecretKeyFactory",                   false, null);
        addEngine("KEM",                                true,  null);
        addEngine("KDF",                                false, KDFParameters.class);
        // JSSE
        addEngine("KeyManagerFactory",                  false, null);
        addEngine("SSLContext",                         false, null);
        addEngine("TrustManagerFactory",                false, null);
        // JGSS
        addEngine("GssApiMechanism",                    false, null);
        // SASL
        addEngine("SaslClientFactory",                  false, null);
        addEngine("SaslServerFactory",                  false, null);
        // POLICY
        @SuppressWarnings("removal")
        Class<Policy.Parameters> policyParams = Policy.Parameters.class;
        addEngine("Policy",                             false,
                policyParams);
        // CONFIGURATION
        addEngine("Configuration",                      false,
                Configuration.Parameters.class);
        // XML DSig
        addEngine("XMLSignatureFactory",                false, null);
        addEngine("KeyInfoFactory",                     false, null);
        addEngine("TransformService",                   false, null);
        // Smart Card I/O
        addEngine("TerminalFactory",                    false,
                            Object.class);
    }

    // get the "standard" (mixed-case) engine name for arbitrary case engine name
    // if there is no known engine by that name, return s
    private static String getEngineName(String s) {
        // try original case first, usually correct
        EngineDescription e = knownEngines.get(s);
        if (e == null) {
            e = knownEngines.get(s.toLowerCase(ENGLISH));
        }
        return (e == null) ? s : e.name;
    }

    /**
     * The description of a security service. It encapsulates the properties
     * of a service and contains a factory method to obtain new implementation
     * instances of this service.
     *
     * <p>Each service has a provider that offers the service, a type,
     * an algorithm name, and the name of the class that implements the
     * service. Optionally, it also includes a list of alternate algorithm
     * names for this service (aliases) and attributes, which are a map of
     * (name, value) {@code String} pairs.
     *
     * <p>This class defines the methods {@link #supportsParameter
     * supportsParameter()} and {@link #newInstance newInstance()}
     * which are used by the Java security framework when it searches for
     * suitable services and instantiates them. The valid arguments to those
     * methods depend on the type of service. For the service types defined
     * within Java SE, see the
     * {@extLink security_guide_jca
     * Java Cryptography Architecture (JCA) Reference Guide}
     * for the valid values.
     * Note that components outside of Java SE can define additional types of
     * services and their behavior.
     *
     * <p>Instances of this class are immutable.
     *
     * @since 1.5
     */
    public static class Service {
        private final String type;
        private final String algorithm;
        private String className;
        private final Provider provider;
        private List<String> aliases;
        private Map<UString,String> attributes;
        private final EngineDescription engineDescription;

        // Reference to the cached implementation Class object.
        // Will be a Class if this service is loaded from the built-in
        // classloader (unloading not possible), otherwise a WeakReference to a
        // Class
        private Object classCache;

        // Will be a Constructor if this service is loaded from the built-in
        // classloader (unloading not possible), otherwise a WeakReference to
        // a Constructor
        private Object constructorCache;

        // flag indicating whether this service has its attributes for
        // supportedKeyFormats or supportedKeyClasses set
        // if null, the values have not been initialized
        // if TRUE, at least one of supportedFormats/Classes is non-null
        private volatile Boolean hasKeyAttributes;

        // supported encoding formats
        private String[] supportedFormats;

        // names of the supported key (super) classes
        private Class<?>[] supportedClasses;

        // whether this service has been registered with the Provider
        private boolean registered;

        private static final Class<?>[] CLASS0 = new Class<?>[0];

        // this constructor and these methods are used for parsing
        // the legacy string properties.

        private Service(Provider provider, String type, String algorithm) {
            this.provider = provider;
            this.type = type;
            this.algorithm = algorithm;
            engineDescription = knownEngines.get(type);
            aliases = Collections.emptyList();
            attributes = Collections.emptyMap();
        }

        private boolean isValid() {
            return (type != null) && (algorithm != null) && (className != null);
        }

        private void addAlias(String alias) {
            if (aliases.isEmpty()) {
                aliases = new ArrayList<>(2);
            }
            aliases.add(alias);
        }

        private void removeAlias(String alias) {
            if (aliases.isEmpty()) {
                return;
            }
            aliases.remove(alias);
        }

        void addAttribute(String type, String value) {
            if (attributes.isEmpty()) {
                attributes = new HashMap<>(8);
            }
            attributes.put(new UString(type), value);
        }

        void removeAttribute(String type, String value) {
            if (attributes.isEmpty()) {
                return;
            }
            if (value == null) {
                attributes.remove(new UString(type));
            } else {
                attributes.remove(new UString(type), value);
            }
        }

        /**
         * Construct a new service.
         *
         * @param provider the provider that offers this service
         * @param type the type of this service
         * @param algorithm the algorithm name
         * @param className the name of the class implementing this service
         * @param aliases List of aliases or {@code null} if algorithm has no
         *                   aliases
         * @param attributes Map of attributes or {@code null} if this
         *                   implementation has no attributes
         *
         * @throws NullPointerException if provider, type, algorithm, or
         * className is {@code null}
         */
        public Service(Provider provider, String type, String algorithm,
                String className, List<String> aliases,
                Map<String,String> attributes) {
            if ((provider == null) || (type == null) ||
                    (algorithm == null) || (className == null)) {
                throw new NullPointerException();
            }
            this.provider = provider;
            this.type = getEngineName(type);
            engineDescription = knownEngines.get(type);
            this.algorithm = algorithm;
            this.className = className;
            if (aliases == null) {
                this.aliases = Collections.emptyList();
            } else {
                this.aliases = new ArrayList<>(aliases);
            }
            if (attributes == null) {
                this.attributes = Collections.emptyMap();
            } else {
                this.attributes = new HashMap<>();
                for (Map.Entry<String,String> entry : attributes.entrySet()) {
                    this.attributes.put(new UString(entry.getKey()), entry.getValue());
                }
            }
        }

        /**
         * Get the type of this service. For example, {@code MessageDigest}.
         *
         * @return the type of this service
         */
        public final String getType() {
            return type;
        }

        /**
         * Return the name of the algorithm of this service. For example,
         * {@code SHA-1}.
         *
         * @return the algorithm of this service
         */
        public final String getAlgorithm() {
            return algorithm;
        }

        /**
         * Return the Provider of this service.
         *
         * @return the Provider of this service
         */
        public final Provider getProvider() {
            return provider;
        }

        /**
         * Return the name of the class implementing this service.
         *
         * @return the name of the class implementing this service
         */
        public final String getClassName() {
            return className;
        }

        // internal only
        private List<String> getAliases() {
            return aliases;
        }

        /**
         * Return the value of the specified attribute or {@code null} if this
         * attribute is not set for this Service.
         *
         * @param name the name of the requested attribute
         *
         * @return the value of the specified attribute or {@code null} if the
         *         attribute is not present
         *
         * @throws NullPointerException if name is {@code null}
         */
        public final String getAttribute(String name) {
            if (name == null) {
                throw new NullPointerException();
            }
            return attributes.get(new UString(name));
        }

        /**
         * Return a new instance of the implementation described by this
         * service. The security provider framework uses this method to
         * construct implementations. Applications will typically not need
         * to call it.
         *
         * <p>The default implementation uses reflection to invoke the
         * standard constructor for this type of service.
         * Security providers can override this method to implement
         * instantiation in a different way.
         * For details and the values of constructorParameter that are
         * valid for the various types of services see the
         * {@extLink security_guide_jca
         * Java Cryptography Architecture (JCA) Reference Guide}.
         *
         * @param constructorParameter the value to pass to the constructor,
         * or {@code null} if this type of service does not use a
         * constructorParameter.
         *
         * @return a new implementation of this service
         *
         * @throws InvalidParameterException if the value of
         * constructorParameter is invalid for this type of service.
         * @throws NoSuchAlgorithmException if instantiation failed for
         * any other reason.
         */
        public Object newInstance(Object constructorParameter)
                throws NoSuchAlgorithmException {
            if (!registered) {
                if (provider.getService(type, algorithm) != this) {
                    throw new NoSuchAlgorithmException
                        ("Service not registered with Provider "
                        + provider.getName() + ": " + this);
                }
                registered = true;
            }
            Class<?> ctrParamClz;
            try {
                EngineDescription cap = engineDescription;
                if (cap == null) {
                    // unknown engine type, use generic code
                    // this is the code path future for non-core
                    // optional packages
                    ctrParamClz = constructorParameter == null?
                        null : constructorParameter.getClass();
                } else {
                    ctrParamClz = cap.constructorParameterClass;
                    if (constructorParameter != null) {
                        if (ctrParamClz == null) {
                            throw new InvalidParameterException
                                ("constructorParameter not used with " + type
                                + " engines");
                        } else {
                            Class<?> argClass = constructorParameter.getClass();
                            if (!ctrParamClz.isAssignableFrom(argClass)) {
                                throw new InvalidParameterException
                                    ("constructorParameter must be instanceof "
                                    + cap.constructorParameterClass.getName().replace('$', '.')
                                    + " for engine type " + type);
                            }
                        }
                    }
                }
                // constructorParameter can be null if not provided
                return newInstanceUtil(ctrParamClz, constructorParameter);
            } catch (NoSuchAlgorithmException e) {
                throw e;
            } catch (InvocationTargetException e) {
                throw new NoSuchAlgorithmException
                    ("Error constructing implementation (algorithm: "
                    + algorithm + ", provider: " + provider.getName()
                    + ", class: " + className + ")", e.getCause());
            } catch (Exception e) {
                throw new NoSuchAlgorithmException
                    ("Error constructing implementation (algorithm: "
                    + algorithm + ", provider: " + provider.getName()
                    + ", class: " + className + ")", e);
            }
        }

        private Object newInstanceOf() throws Exception {
            Constructor<?> con = getDefaultConstructor();
            return con.newInstance(EMPTY);
        }

        private Object newInstanceUtil(Class<?> ctrParamClz, Object ctorParamObj)
                throws Exception
        {
            if (ctrParamClz == null) {
                return newInstanceOf();
            } else {
                // Looking for the constructor with a params first and fallback
                // to one without if not found. This is to support the enhanced
                // SecureRandom where both styles of constructors are supported.
                // Before jdk9, there was no params support (only getInstance(alg))
                // and an impl only had the params-less constructor. Since jdk9,
                // there is getInstance(alg,params) and an impl can contain
                // an Impl(params) constructor.
                try {
                    Constructor<?> con = getImplClass().getConstructor(ctrParamClz);
                    return con.newInstance(ctorParamObj);
                } catch (NoSuchMethodException nsme) {
                    // For pre-jdk9 SecureRandom implementations, they only
                    // have params-less constructors which still works when
                    // the input ctorParamObj is null.
                    //
                    // For other primitives using params, ctorParamObj should not
                    // be null and nsme is thrown, just like before.
                    if (ctorParamObj == null) {
                        try {
                            return newInstanceOf();
                        } catch (NoSuchMethodException nsme2) {
                            nsme.addSuppressed(nsme2);
                            throw nsme;
                        }
                    } else {
                        throw nsme;
                    }
                }
            }
        }

        // return the implementation Class object for this service
        private Class<?> getImplClass() throws NoSuchAlgorithmException {
            try {
                Object cache = classCache;
                if (cache instanceof Class<?> clazz) {
                    return clazz;
                }
                Class<?> clazz = null;
                if (cache instanceof WeakReference<?> ref){
                    clazz = (Class<?>)ref.get();
                }
                if (clazz == null) {
                    ClassLoader cl = provider.getClass().getClassLoader();
                    if (cl == null) {
                        clazz = Class.forName(className);
                    } else {
                        clazz = cl.loadClass(className);
                    }
                    if (!Modifier.isPublic(clazz.getModifiers())) {
                        throw new NoSuchAlgorithmException
                            ("class configured for " + type + " (provider: " +
                            provider.getName() + ") is not public.");
                    }
                    classCache = (cl == null) ? clazz : new WeakReference<Class<?>>(clazz);
                }
                return clazz;
            } catch (ClassNotFoundException e) {
                throw new NoSuchAlgorithmException
                    ("class configured for " + type + " (provider: " +
                    provider.getName() + ") cannot be found.", e);
            }
        }

        private Constructor<?> getDefaultConstructor()
            throws NoSuchAlgorithmException, NoSuchMethodException
        {
            Object cache = constructorCache;
            if (cache instanceof Constructor<?> con) {
                return con;
            }
            Constructor<?> con = null;
            if (cache instanceof WeakReference<?> ref){
                con = (Constructor<?>)ref.get();
            }
            if (con == null) {
                Class<?> clazz = getImplClass();
                con = clazz.getConstructor();
                constructorCache = (clazz.getClassLoader() == null)
                        ? con : new WeakReference<Constructor<?>>(con);
            }
            return con;
        }

        /**
         * Test whether this Service can use the specified parameter.
         * Returns {@code false} if this service cannot use the parameter.
         * Returns {@code true} if this service can use the parameter,
         * if a fast test is infeasible, or if the status is unknown.
         *
         * <p>The security provider framework uses this method with
         * some types of services to quickly exclude non-matching
         * implementations for consideration.
         * Applications will typically not need to call it.
         *
         * <p>For details and the values of parameter that are valid for the
         * various types of services see the top of this class and the
         * {@extLink security_guide_jca
         * Java Cryptography Architecture (JCA) Reference Guide}.
         * Security providers can override it to implement their own test.
         *
         * @param parameter the parameter to test
         *
         * @return {@code false} if this service cannot use the specified
         * parameter; {@code true} if it can possibly use the parameter
         *
         * @throws InvalidParameterException if the value of parameter is
         * invalid for this type of service or if this method cannot be
         * used with this type of service
         */
        public boolean supportsParameter(Object parameter) {
            EngineDescription cap = engineDescription;
            if (cap == null) {
                // unknown engine type, return true by default
                return true;
            }
            if (!cap.supportsParameter) {
                throw new InvalidParameterException("supportsParameter() not "
                    + "used with " + type + " engines");
            }
            // allow null for keys without attributes for compatibility
            if ((parameter != null) && (!(parameter instanceof Key))) {
                throw new InvalidParameterException
                    ("Parameter must be instanceof Key for engine " + type);
            }
            if (!hasKeyAttributes()) {
                return true;
            }
            if (parameter == null) {
                return false;
            }
            Key key = (Key)parameter;
            if (supportsKeyFormat(key)) {
                return true;
            }
            return supportsKeyClass(key);
        }

        /**
         * Return whether this service has its supported properties for
         * keys defined. Parses the attributes if not yet initialized.
         */
        private boolean hasKeyAttributes() {
            Boolean b = hasKeyAttributes;
            if (b == null) {
                synchronized (this) {
                    b = hasKeyAttributes;
                    if (b == null) {
                        String s;
                        s = getAttribute("SupportedKeyFormats");
                        if (s != null) {
                            supportedFormats = s.split("\\|");
                        }
                        s = getAttribute("SupportedKeyClasses");
                        if (s != null) {
                            String[] classNames = s.split("\\|");
                            List<Class<?>> classList =
                                new ArrayList<>(classNames.length);
                            for (String className : classNames) {
                                Class<?> clazz = getKeyClass(className);
                                if (clazz != null) {
                                    classList.add(clazz);
                                }
                            }
                            supportedClasses = classList.toArray(CLASS0);
                        }
                        b = (supportedFormats != null)
                            || (supportedClasses != null);
                        hasKeyAttributes = b;
                    }
                }
            }
            return b.booleanValue();
        }

        // get the key class object of the specified name
        private Class<?> getKeyClass(String name) {
            try {
                return Class.forName(name);
            } catch (ClassNotFoundException e) {
                // ignore
            }
            try {
                ClassLoader cl = provider.getClass().getClassLoader();
                if (cl != null) {
                    return cl.loadClass(name);
                }
            } catch (ClassNotFoundException e) {
                // ignore
            }
            return null;
        }

        private boolean supportsKeyFormat(Key key) {
            if (supportedFormats == null) {
                return false;
            }
            String format = key.getFormat();
            if (format == null) {
                return false;
            }
            for (String supportedFormat : supportedFormats) {
                if (supportedFormat.equals(format)) {
                    return true;
                }
            }
            return false;
        }

        private boolean supportsKeyClass(Key key) {
            if (supportedClasses == null) {
                return false;
            }
            Class<?> keyClass = key.getClass();
            for (Class<?> clazz : supportedClasses) {
                if (clazz.isAssignableFrom(keyClass)) {
                    return true;
                }
            }
            return false;
        }

        /**
         * Return a {@code String} representation of this service.
         *
         * @return a {@code String} representation of this service.
         */
        public String toString() {
            String aString = aliases.isEmpty()
                ? "" : "\r\n  aliases: " + aliases.toString();
            String attrs = attributes.isEmpty()
                ? "" : "\r\n  attributes: " + attributes.toString();
            return provider.getName() + ": " + type + "." + algorithm
                + " -> " + className + aString + attrs + "\r\n";
        }
    }
}
