/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package java.security;

import java.io.IOException;
import java.io.InputStream;
import java.io.NotActiveException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.apache.harmony.security.fortress.Services;

/**
 * {@code Provider} is the abstract superclass for all security providers in the
 * Java security infrastructure.
 */
public abstract class Provider extends Properties {
    private static final long serialVersionUID = -4298000515446427739L;

    private String name;

    private double version;

    // String representation of the provider version number.
    private transient String versionString;

    private String info;

    //The provider preference order number.
    // Equals -1 for non registered provider.
    private transient int providerNumber = -1;

    // Contains "Service.Algorithm" and Provider.Service classes added using
    // putService()
    private transient LinkedHashMap<String, Service> serviceTable;

    // Contains "Service.Alias" and Provider.Service classes added using
    // putService()
    private transient LinkedHashMap<String, Service> aliasTable;

    // Contains "Service.Algorithm" and Provider.Service classes added using
    // put()
    private transient LinkedHashMap<String, Service> propertyServiceTable;

    // Contains "Service.Alias" and Provider.Service classes added using put()
    private transient LinkedHashMap<String, Service> propertyAliasTable;

    // The properties changed via put()
    private transient LinkedHashMap<Object, Object> changedProperties;

    // For getService(String type, String algorithm) optimization:
    // previous result
    private transient Provider.Service returnedService;
    // previous parameters
    private transient String lastAlgorithm;
    // last name
    private transient String lastServiceName;

    // For getServices() optimization:
    private transient Set<Service> lastServicesSet;

    // For getService(String type) optimization:
    private transient String lastType;
    // last Service found by type
    private transient Provider.Service lastServicesByType;

    /**
     * Constructs a new instance of {@code Provider} with its name, version and
     * description.
     *
     * @param name
     *            the name of the provider.
     * @param version
     *            the version of the provider.
     * @param info
     *            a description of the provider.
     */
    protected Provider(String name, double version, String info) {
        this.name = name;
        this.version = version;
        this.info = info;
        versionString = String.valueOf(version);
        putProviderInfo();
    }

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

    /**
     * Returns the version number for the services being provided.
     *
     * @return the version number for the services being provided.
     */
    public double getVersion() {
        return version;
    }

    /**
     * Returns a description of the services being provided.
     *
     * @return a description of the services being provided.
     */
    public String getInfo() {
        return info;
    }

    /**
     * Returns a string containing a concise, human-readable description of
     * this {@code Provider} including its name and its version.
     *
     * @return a printable representation for this {@code Provider}.
     */
    @Override
    public String toString() {
        return name + " version " + version;
    }

    /**
     * Clears all properties used to look up services implemented by this
     * {@code Provider}.
     */
    @Override
    public synchronized void clear() {
        super.clear();
        if (serviceTable != null) {
            serviceTable.clear();
        }
        if (propertyServiceTable != null) {
            propertyServiceTable.clear();
        }
        if (aliasTable != null) {
            aliasTable.clear();
        }
        if (propertyAliasTable != null) {
            propertyAliasTable.clear();
        }
        changedProperties = null;
        putProviderInfo();
        if (providerNumber != -1) {
            // if registered then refresh Services
            Services.setNeedRefresh();
        }
        servicesChanged();
    }

    @Override
    public synchronized void load(InputStream inStream) throws IOException {
        Properties tmp = new Properties();
        tmp.load(inStream);
        myPutAll(tmp);
    }

    /**
     * Copies all from the provided map to this {@code Provider}.
     * @param t
     *            the mappings to copy to this provider.
     */
    @Override
    public synchronized void putAll(Map<?,?> t) {
        myPutAll(t);
    }

    private void myPutAll(Map<?,?> t) {
        if (changedProperties == null) {
            changedProperties = new LinkedHashMap<Object, Object>();
        }
        Iterator<? extends Map.Entry<?, ?>> it = t.entrySet().iterator();
        Object key;
        Object value;
        while (it.hasNext()) {
            Map.Entry<?, ?> entry = it.next();
            key = entry.getKey();
            if (key instanceof String && ((String) key).startsWith("Provider.")) {
                // Provider service type is reserved
                continue;
            }
            value = entry.getValue();
            super.put(key, value);
            if (changedProperties.remove(key) == null) {
                removeFromPropertyServiceTable(key);
            }
            changedProperties.put(key, value);
        }
        if (providerNumber != -1) {
            // if registered then refresh Services
            Services.setNeedRefresh();
        }
    }

    @Override
    public synchronized Set<Map.Entry<Object,Object>> entrySet() {
        return Collections.unmodifiableSet(super.entrySet());
    }

    @Override
    public Set<Object> keySet() {
        return Collections.unmodifiableSet(super.keySet());
    }

    @Override
    public Collection<Object> values() {
        return Collections.unmodifiableCollection(super.values());
    }

    /**
     * Maps the specified {@code key} property name to the specified {@code
     * value}.
     *
     * @param key
     *            the name of the property.
     * @param value
     *            the value of the property.
     * @return the value that was previously mapped to the specified {@code key}
     *         ,or {@code null} if it did not have one.
     */
    @Override
    public synchronized Object put(Object key, Object value) {
        if (key instanceof String && ((String) key).startsWith("Provider.")) {
            // Provider service type is reserved
            return null;
        }
        if (providerNumber != -1) {
            // if registered then refresh Services
            Services.setNeedRefresh();
        }
        if (changedProperties != null && changedProperties.remove(key) == null) {
            removeFromPropertyServiceTable(key);
        }
        if (changedProperties == null) {
            changedProperties = new LinkedHashMap<Object, Object>();
        }
        changedProperties.put(key, value);
        return super.put(key, value);
    }

    /**
     * Removes the specified {@code key} and its associated value from this
     * {@code Provider}.
     *
     * @param key
     *            the name of the property
     * @return the value that was mapped to the specified {@code key} ,or
     *         {@code null} if no mapping was present
     */
    @Override
    public synchronized Object remove(Object key) {
        if (key instanceof String && ((String) key).startsWith("Provider.")) {
            // Provider service type is reserved
            return null;
        }
        if (providerNumber != -1) {
            // if registered then refresh Services
            Services.setNeedRefresh();
        }
        if (changedProperties != null && changedProperties.remove(key) == null) {
            removeFromPropertyServiceTable(key);
            if (changedProperties.size() == 0) {
                changedProperties = null;
            }
        }
        return super.remove(key);
    }

    /**
     * Returns true if this provider implements the given algorithm. Caller
     * must specify the cryptographic service and specify constraints via the
     * attribute name and value.
     *
     * @param serv
     *            Crypto service.
     * @param alg
     *            Algorithm or type.
     * @param attribute
     *            The attribute name or {@code null}.
     * @param val
     *            The attribute value.
     * @return
     */
    boolean implementsAlg(String serv, String alg, String attribute, String val) {
        String servAlg = serv + "." + alg;
        String prop = getPropertyIgnoreCase(servAlg);
        if (prop == null) {
            alg = getPropertyIgnoreCase("Alg.Alias." + servAlg);
            if (alg != null) {
                servAlg = serv + "." + alg;
                prop = getPropertyIgnoreCase(servAlg);
            }
        }
        if (prop != null) {
            if (attribute == null) {
                return true;
            }
            return checkAttribute(servAlg, attribute, val);
        }
        return false;
    }

    /**
     * Returns true if this provider has the same value as is given for the
     * given attribute
     */
    private boolean checkAttribute(String servAlg, String attribute, String val) {

        String attributeValue = getPropertyIgnoreCase(servAlg + ' ' + attribute);
        if (attributeValue != null) {
            if (attribute.equalsIgnoreCase("KeySize")) {
                if (Integer.parseInt(attributeValue) >= Integer.parseInt(val)) {
                    return true;
                }
            } else { // other attributes
                if (attributeValue.equalsIgnoreCase(val)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     *
     * Set the provider preference order number.
     *
     * @param n
     */
    void setProviderNumber(int n) {
        providerNumber = n;
    }

    /**
     *
     * Get the provider preference order number.
     *
     * @return
     */
    int getProviderNumber() {
        return providerNumber;
    }

    /**
     * Get the service of the specified type
     *
     */
    synchronized Provider.Service getService(String type) {
        updatePropertyServiceTable();
        if (lastServicesByType != null && type.equals(lastType)) {
            return lastServicesByType;
        }
        Provider.Service service;
        for (Iterator<Service> it = getServices().iterator(); it.hasNext();) {
            service = it.next();
            if (type.equals(service.type)) {
                lastType = type;
                lastServicesByType = service;
                return service;
            }
        }
        return null;
    }

    /**
     * Returns the service with the specified {@code type} implementing the
     * specified {@code algorithm}, or {@code null} if no such implementation
     * exists.
     * <p>
     * If two services match the requested type and algorithm, the one added
     * with the {@link #putService(Service)} is returned (as opposed to the one
     * added via {@link #put(Object, Object)}.
     *
     * @param type
     *            the type of the service (for example {@code KeyPairGenerator})
     * @param algorithm
     *            the algorithm name (case insensitive)
     * @return the requested service, or {@code null} if no such implementation
     *         exists
     */
    public synchronized Provider.Service getService(String type,
            String algorithm) {
        if (type == null) {
            throw new NullPointerException("type == null");
        } else if (algorithm == null) {
            throw new NullPointerException("algorithm == null");
        }

        if (type.equals(lastServiceName) && algorithm.equalsIgnoreCase(lastAlgorithm)) {
            return returnedService;
        }

        String key = key(type, algorithm);
        Object o = null;
        if (serviceTable != null) {
            o = serviceTable.get(key);
        }
        if (o == null && aliasTable != null) {
            o = aliasTable.get(key);
        }
        if (o == null) {
            updatePropertyServiceTable();
        }
        if (o == null && propertyServiceTable != null) {
            o = propertyServiceTable.get(key);
        }
        if (o == null && propertyAliasTable != null) {
            o = propertyAliasTable.get(key);
        }

        if (o != null) {
            lastServiceName = type;
            lastAlgorithm = algorithm;
            returnedService = (Provider.Service) o;
            return returnedService;
        }
        return null;
    }

    /**
     * Returns an unmodifiable {@code Set} of all services registered by this
     * provider.
     *
     * @return an unmodifiable {@code Set} of all services registered by this
     *         provider
     */
    public synchronized Set<Provider.Service> getServices() {
        updatePropertyServiceTable();
        if (lastServicesSet != null) {
            return lastServicesSet;
        }
        if (serviceTable != null) {
            lastServicesSet = new LinkedHashSet<Service>(serviceTable.values());
        } else {
            lastServicesSet = new LinkedHashSet<Service>();
        }
        if (propertyServiceTable != null) {
            lastServicesSet.addAll(propertyServiceTable.values());
        }
        lastServicesSet = Collections.unmodifiableSet(lastServicesSet);
        return lastServicesSet;
    }

    /**
     * Adds a {@code Service} to this {@code Provider}. If a service with the
     * same name was registered via this method, it is replace.
     *
     * @param s
     *            the {@code Service} to register
     */
    protected synchronized void putService(Provider.Service s) {
        if (s == null) {
            throw new NullPointerException("s == null");
        }
        if ("Provider".equals(s.getType())) { // Provider service type cannot be added
            return;
        }
        servicesChanged();
        if (serviceTable == null) {
            serviceTable = new LinkedHashMap<String, Service>(128);
        }
        serviceTable.put(key(s.type, s.algorithm), s);
        if (s.aliases != null) {
            if (aliasTable == null) {
                aliasTable = new LinkedHashMap<String, Service>(256);
            }
            for (String alias : s.getAliases()) {
                aliasTable.put(key(s.type, alias), s);
            }
        }
        serviceInfoToProperties(s);
    }

    /**
     * Removes a previously registered {@code Service} from this {@code
     * Provider}.
     *
     * @param s
     *            the {@code Service} to remove
     * @throws NullPointerException
     *             if {@code s} is {@code null}
     */
    protected synchronized void removeService(Provider.Service s) {
        if (s == null) {
            throw new NullPointerException("s == null");
        }
        servicesChanged();
        if (serviceTable != null) {
            serviceTable.remove(key(s.type, s.algorithm));
        }
        if (aliasTable != null && s.aliases != null) {
            for (String alias: s.getAliases()) {
                aliasTable.remove(key(s.type, alias));
            }
        }
        serviceInfoFromProperties(s);
    }

    /**
     * Add Service information to the provider's properties.
     */
    private void serviceInfoToProperties(Provider.Service s) {
        super.put(s.type + "." + s.algorithm, s.className);
        if (s.aliases != null) {
            for (Iterator<String> i = s.aliases.iterator(); i.hasNext();) {
                super.put("Alg.Alias." + s.type + "." + i.next(), s.algorithm);
            }
        }
        if (s.attributes != null) {
            for (Map.Entry<String, String> entry : s.attributes.entrySet()) {
                super.put(s.type + "." + s.algorithm + " " + entry.getKey(),
                        entry.getValue());
            }
        }
        if (providerNumber != -1) {
            // if registered then refresh Services
            Services.setNeedRefresh();
        }
    }

    /**
     * Remove Service information from the provider's properties.
     */
    private void serviceInfoFromProperties(Provider.Service s) {
        super.remove(s.type + "." + s.algorithm);
        if (s.aliases != null) {
            for (Iterator<String> i = s.aliases.iterator(); i.hasNext();) {
                super.remove("Alg.Alias." + s.type + "." + i.next());
            }
        }
        if (s.attributes != null) {
            for (Map.Entry<String, String> entry : s.attributes.entrySet()) {
                super.remove(s.type + "." + s.algorithm + " " + entry.getKey());
            }
        }
        if (providerNumber != -1) {
            // if registered then refresh Services
            Services.setNeedRefresh();
        }
    }

    // Remove property information from provider Services
    private void removeFromPropertyServiceTable(Object key) {
        if (key == null || !(key instanceof String)) {
            return;
        }
        String k = (String) key;
        if (k.startsWith("Provider.")) { // Provider service type is reserved
            return;
        }
        Provider.Service s;
        String serviceName;
        String algorithm = null;
        String attribute = null;
        int i;
        if (k.startsWith("Alg.Alias.")) { // Alg.Alias.<crypto_service>.<aliasName>=<standardName>
            String aliasName;
            String service_alias = k.substring(10);
            i = service_alias.indexOf('.');
            serviceName = service_alias.substring(0, i);
            aliasName = service_alias.substring(i + 1);
            if (propertyAliasTable != null) {
                propertyAliasTable.remove(key(serviceName, aliasName));
            }
            if (propertyServiceTable != null) {
                for (Iterator<Service> it = propertyServiceTable.values().iterator(); it
                        .hasNext();) {
                    s = it.next();
                    if (s.aliases.contains(aliasName)) {
                        s.aliases.remove(aliasName);
                        return;
                    }
                }
            }
            return;
        }
        int j = k.indexOf('.');
        if (j == -1) { // unknown format
            return;
        }

        i = k.indexOf(' ');
        if (i == -1) { // <crypto_service>.<algorithm_or_type>=<className>
            serviceName = k.substring(0, j);
            algorithm = k.substring(j + 1);
            if (propertyServiceTable != null) {
                Provider.Service ser = propertyServiceTable.remove(key(serviceName, algorithm));
                if (ser != null && propertyAliasTable != null
                        && ser.aliases != null) {
                    for (String alias : ser.aliases) {
                        propertyAliasTable.remove(key(serviceName, alias));
                    }
                }
            }
        } else {
            // <crypto_service>.<algorithm_or_type>
            // <attribute_name>=<attrValue>
            attribute = k.substring(i + 1);
            serviceName = k.substring(0, j);
            algorithm = k.substring(j + 1, i);
            if (propertyServiceTable != null) {
                Object o = propertyServiceTable.get(key(serviceName, algorithm));
                if (o != null) {
                    s = (Provider.Service) o;
                    s.attributes.remove(attribute);
                }
            }
        }
    }

    // Update provider Services if the properties was changed
    private void updatePropertyServiceTable() {
        Object _key;
        Object _value;
        Provider.Service s;
        String serviceName;
        String algorithm;
        if (changedProperties == null || changedProperties.isEmpty()) {
            return;
        }
        for (Iterator<Map.Entry<Object, Object>> it = changedProperties.entrySet().iterator(); it
                .hasNext();) {
            Map.Entry<Object, Object> entry = it.next();
            _key = entry.getKey();
            _value = entry.getValue();
            if (_key == null || _value == null || !(_key instanceof String)
                    || !(_value instanceof String)) {
                continue;
            }
            String key = (String) _key;
            String value = (String) _value;
            if (key.startsWith("Provider")) {
                // Provider service type is reserved
                continue;
            }
            int i;
            if (key.startsWith("Alg.Alias.")) {
                // Alg.Alias.<crypto_service>.<aliasName>=<standardName>
                String aliasName;
                String service_alias = key.substring(10);
                i = service_alias.indexOf('.');
                serviceName = service_alias.substring(0, i);
                aliasName = service_alias.substring(i + 1);
                algorithm = value;
                String propertyServiceTableKey = key(serviceName, algorithm);
                Object o = null;
                if (propertyServiceTable == null) {
                    propertyServiceTable = new LinkedHashMap<String, Service>(128);
                } else {
                    o = propertyServiceTable.get(propertyServiceTableKey);
                }
                if (o != null) {
                    s = (Provider.Service) o;
                    s.addAlias(aliasName);
                    if (propertyAliasTable == null) {
                        propertyAliasTable = new LinkedHashMap<String, Service>(256);
                    }
                    propertyAliasTable.put(key(serviceName, aliasName), s);
                } else {
                    String className = (String) changedProperties
                            .get(serviceName + "." + algorithm);
                    if (className != null) {
                        List<String> l = new ArrayList<String>();
                        l.add(aliasName);
                        s = new Provider.Service(this, serviceName, algorithm,
                                className, l, new HashMap<String, String>());
                        propertyServiceTable.put(propertyServiceTableKey, s);
                        if (propertyAliasTable == null) {
                            propertyAliasTable = new LinkedHashMap<String, Service>(256);
                        }
                        propertyAliasTable.put(key(serviceName, aliasName), s);
                    }
                }
                continue;
            }
            int j = key.indexOf('.');
            if (j == -1) { // unknown format
                continue;
            }
            i = key.indexOf(' ');
            if (i == -1) { // <crypto_service>.<algorithm_or_type>=<className>
                serviceName = key.substring(0, j);
                algorithm = key.substring(j + 1);
                String propertyServiceTableKey = key(serviceName, algorithm);
                Object o = null;
                if (propertyServiceTable != null) {
                    o = propertyServiceTable.get(propertyServiceTableKey);
                }
                if (o != null) {
                    s = (Provider.Service) o;
                    s.className = value;
                } else {
                    s = new Provider.Service(this, serviceName, algorithm,
                            value, Collections.<String>emptyList(),
                            Collections.<String,String>emptyMap());
                    if (propertyServiceTable == null) {
                        propertyServiceTable = new LinkedHashMap<String, Service>(128);
                    }
                    propertyServiceTable.put(propertyServiceTableKey, s);

                }
            } else {
                // <crypto_service>.<algorithm_or_type> <attribute_name>=<attrValue>
                serviceName = key.substring(0, j);
                algorithm = key.substring(j + 1, i);
                String attribute = key.substring(i + 1);
                String propertyServiceTableKey = key(serviceName, algorithm);
                Object o = null;
                if (propertyServiceTable != null) {
                    o = propertyServiceTable.get(propertyServiceTableKey);
                }
                if (o != null) {
                    s = (Provider.Service) o;
                    s.putAttribute(attribute, value);
                } else {
                    String className = (String) changedProperties
                            .get(serviceName + "." + algorithm);
                    if (className != null) {
                        Map<String, String> m = new HashMap<String, String>();
                        m.put(attribute, value);
                        s = new Provider.Service(this, serviceName, algorithm,
                                className, new ArrayList<String>(), m);
                        if (propertyServiceTable == null) {
                            propertyServiceTable = new LinkedHashMap<String, Service>(128);
                        }
                        propertyServiceTable.put(propertyServiceTableKey, s);
                    }
                }
            }
        }
        servicesChanged();
        changedProperties = null;
    }

    private void servicesChanged() {
        lastServicesByType = null;
        lastServiceName = null;
        lastServicesSet = null;
    }

    /**
     * These attributes should be placed in each Provider object:
     * Provider.id name, Provider.id version, Provider.id info,
     * Provider.id className
     */
    private void putProviderInfo() {
        super.put("Provider.id name", (name != null) ? name : "null");
        super.put("Provider.id version", versionString);
        super.put("Provider.id info", (info != null) ? info : "null");
        super.put("Provider.id className", this.getClass().getName());
    }

    /**
     * Returns the property with the specified key in the provider properties.
     * The name is not case-sensitive.
     */
    private String getPropertyIgnoreCase(String key) {
        String res = getProperty(key);
        if (res != null) {
            return res;
        }
        for (Enumeration<?> e = propertyNames(); e.hasMoreElements(); ) {
            String propertyName = (String) e.nextElement();
            if (key.equalsIgnoreCase(propertyName)) {
                return getProperty(propertyName);
            }
        }
        return null;
    }

    private static String key(String type, String algorithm) {
        return type + '.' + algorithm.toUpperCase(Locale.US);
    }

    /**
     * {@code Service} represents a service in the Java Security infrastructure.
     * Each service describes its type, the algorithm it implements, to which
     * provider it belongs and other properties.
     */
    public static class Service {
        /** Attribute name of supported key classes. */
        private static final String ATTR_SUPPORTED_KEY_CLASSES = "SupportedKeyClasses";

        /** Attribute name of supported key formats. */
        private static final String ATTR_SUPPORTED_KEY_FORMATS = "SupportedKeyFormats";

        /** Whether this type supports calls to {@link #supportsParameter(Object)}. */
        private static final HashMap<String, Boolean> supportsParameterTypes
                = new HashMap<String, Boolean>();
        static {
            // Does not support parameter
            supportsParameterTypes.put("AlgorithmParameterGenerator", false);
            supportsParameterTypes.put("AlgorithmParameters", false);
            supportsParameterTypes.put("CertificateFactory", false);
            supportsParameterTypes.put("CertPathBuilder", false);
            supportsParameterTypes.put("CertPathValidator", false);
            supportsParameterTypes.put("CertStore", false);
            supportsParameterTypes.put("KeyFactory", false);
            supportsParameterTypes.put("KeyGenerator", false);
            supportsParameterTypes.put("KeyManagerFactory", false);
            supportsParameterTypes.put("KeyPairGenerator", false);
            supportsParameterTypes.put("KeyStore", false);
            supportsParameterTypes.put("MessageDigest", false);
            supportsParameterTypes.put("SecretKeyFactory", false);
            supportsParameterTypes.put("SecureRandom", false);
            supportsParameterTypes.put("SSLContext", false);
            supportsParameterTypes.put("TrustManagerFactory", false);

            // Supports parameter
            supportsParameterTypes.put("Cipher", true);
            supportsParameterTypes.put("KeyAgreement", true);
            supportsParameterTypes.put("Mac", true);
            supportsParameterTypes.put("Signature", true);
        }

        /** Constructor argument classes for calls to {@link #newInstance(Object)}. */
        private static final HashMap<String, Class<?>> constructorParameterClasses = new HashMap<String, Class<?>>();
        static {
            // Types that take a parameter to newInstance
            constructorParameterClasses.put("CertStore",
                    loadClassOrThrow("java.security.cert.CertStoreParameters"));

            // Types that do not take any kind of parameter
            constructorParameterClasses.put("AlgorithmParameterGenerator", null);
            constructorParameterClasses.put("AlgorithmParameters", null);
            constructorParameterClasses.put("CertificateFactory", null);
            constructorParameterClasses.put("CertPathBuilder", null);
            constructorParameterClasses.put("CertPathValidator", null);
            constructorParameterClasses.put("KeyFactory", null);
            constructorParameterClasses.put("KeyGenerator", null);
            constructorParameterClasses.put("KeyManagerFactory", null);
            constructorParameterClasses.put("KeyPairGenerator", null);
            constructorParameterClasses.put("KeyStore", null);
            constructorParameterClasses.put("MessageDigest", null);
            constructorParameterClasses.put("SecretKeyFactory", null);
            constructorParameterClasses.put("SecureRandom", null);
            constructorParameterClasses.put("SSLContext", null);
            constructorParameterClasses.put("TrustManagerFactory", null);
            constructorParameterClasses.put("Cipher", null);
            constructorParameterClasses.put("KeyAgreement", null);
            constructorParameterClasses.put("Mac", null);
            constructorParameterClasses.put("Signature", null);
        }

        /** Called to load a class if it's critical that the class exists. */
        private static Class<?> loadClassOrThrow(String className) {
            try {
                return Provider.class.getClassLoader().loadClass(className);
            } catch (Exception e) {
                throw new AssertionError(e);
            }
        }

        // The provider
        private Provider provider;

        // The type of this service
        private String type;

        // The algorithm name
        private String algorithm;

        // The class implementing this service
        private String className;

        // The aliases
        private List<String> aliases;

        // The attributes
        private Map<String,String> attributes;

        // Service implementation
        private Class<?> implementation;

        // For newInstance() optimization
        private String lastClassName;

        /** Indicates whether supportedKeyClasses and supportedKeyFormats. */
        private volatile boolean supportedKeysInitialized;

        /** List of classes that this service supports. */
        private Class<?>[] keyClasses;

        /** List of key formats this service supports. */
        private String[] keyFormats;

        /**
         * Constructs a new instance of {@code Service} with the given
         * attributes.
         *
         * @param provider
         *            the provider to which this service belongs.
         * @param type
         *            the type of this service (for example {@code
         *            KeyPairGenerator}).
         * @param algorithm
         *            the algorithm this service implements.
         * @param className
         *            the name of the class implementing this service.
         * @param aliases
         *            {@code List} of aliases for the algorithm name, or {@code
         *            null} if the implemented algorithm has no aliases.
         * @param attributes
         *            {@code Map} of additional attributes, or {@code null} if
         *            this {@code Service} has no attributed.
         * @throws NullPointerException
         *             if {@code provider, type, algorithm} or {@code className}
         *             is {@code null}.
         */
        public Service(Provider provider, String type, String algorithm,
                String className, List<String> aliases, Map<String, String> attributes) {
            if (provider == null) {
                throw new NullPointerException("provider == null");
            } else if (type == null) {
                throw new NullPointerException("type == null");
            } else if (algorithm == null) {
                throw new NullPointerException("algorithm == null");
            } else if (className == null) {
                throw new NullPointerException("className == null");
            }
            this.provider = provider;
            this.type = type;
            this.algorithm = algorithm;
            this.className = className;
            this.aliases = ((aliases != null) && (aliases.size() == 0))
                    ? Collections.<String>emptyList() : aliases;
            this.attributes =
                    ((attributes != null) && (attributes.size() == 0))
                    ? Collections.<String,String>emptyMap() : attributes;
        }

        /**
         * Adds an alias.
         *
         * @param alias the alias to add
         */
        /*package*/ void addAlias(String alias) {
            if ((aliases == null) || (aliases.size() == 0)) {
                aliases = new ArrayList<String>();
            }
            aliases.add(alias);
        }

        /**
         * Puts a new attribute mapping.
         *
         * @param name the attribute name.
         * @param value the attribute value.
         */
        /*package*/ void putAttribute(String name, String value) {
            if ((attributes == null) || (attributes.size() == 0)) {
                attributes = new HashMap<String,String>();
            }
            attributes.put(name, value);
        }

        /**
         * Returns the type of this {@code Service}. For example {@code
         * KeyPairGenerator}.
         *
         * @return the type of this {@code Service}.
         */
        public final String getType() {
            return type;
        }

        /**
         * Returns the name of the algorithm implemented by this {@code
         * Service}.
         *
         * @return the name of the algorithm implemented by this {@code
         *         Service}.
         */
        public final String getAlgorithm() {
            return algorithm;
        }

        /**
         * Returns the {@code Provider} this {@code Service} belongs to.
         *
         * @return the {@code Provider} this {@code Service} belongs to.
         */
        public final Provider getProvider() {
            return provider;
        }

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

        /**
         * Returns the value of the attribute with the specified {@code name}.
         *
         * @param name
         *            the name of the attribute.
         * @return the value of the attribute, or {@code null} if no attribute
         *         with the given name is set.
         * @throws NullPointerException
         *             if {@code name} is {@code null}.
         */
        public final String getAttribute(String name) {
            if (name == null) {
                throw new NullPointerException("name == null");
            }
            if (attributes == null) {
                return null;
            }
            return attributes.get(name);
        }

        List<String> getAliases() {
            if (aliases == null){
                aliases = new ArrayList<String>(0);
            }
            return aliases;
        }

        /**
         * Creates and returns a new instance of the implementation described by
         * this {@code Service}.
         *
         * @param constructorParameter
         *            the parameter that is used by the constructor, or {@code
         *            null} if the implementation does not declare a constructor
         *            parameter.
         * @return a new instance of the implementation described by this
         *         {@code Service}.
         * @throws NoSuchAlgorithmException
         *             if the instance could not be constructed.
         * @throws InvalidParameterException
         *             if the implementation does not support the specified
         *             {@code constructorParameter}.
         */
        public Object newInstance(Object constructorParameter) throws NoSuchAlgorithmException {
            if (implementation == null || !className.equals(lastClassName)) {
                ClassLoader cl = provider.getClass().getClassLoader();
                if (cl == null) {
                    cl = ClassLoader.getSystemClassLoader();
                }
                try {
                    implementation = Class.forName(className, true, cl);
                    lastClassName = className;
                } catch (Exception e) {
                    throw new NoSuchAlgorithmException(type + " " + algorithm + " implementation not found: " + e);
                }
            }

            // We don't know whether this takes a parameter or not.
            if (!constructorParameterClasses.containsKey(type)) {
                if (constructorParameter == null) {
                    return newInstanceNoParameter();
                } else {
                    return newInstanceWithParameter(constructorParameter,
                            constructorParameter.getClass());
                }
            }

            // A known type, but it's not required to have a parameter even if a
            // class is specified.
            if (constructorParameter == null) {
                return newInstanceNoParameter();
            }

            // Make sure the provided constructor class is valid.
            final Class<?> expectedClass = constructorParameterClasses.get(type);
            if (expectedClass == null) {
                throw new IllegalArgumentException("Constructor parameter not supported for "
                        + type);
            }
            if (!expectedClass.isAssignableFrom(constructorParameter.getClass())) {
                throw new IllegalArgumentException("Expecting constructor parameter of type "
                        + expectedClass.getName() + " but was "
                        + constructorParameter.getClass().getName());
            }
            return newInstanceWithParameter(constructorParameter, expectedClass);
        }

        private Object newInstanceWithParameter(Object constructorParameter,
                Class<?> parameterClass) throws NoSuchAlgorithmException {
            try {
                Class<?>[] parameterTypes = { parameterClass };
                Object[] initargs = { constructorParameter };
                return implementation.getConstructor(parameterTypes).newInstance(initargs);
            } catch (Exception e) {
                throw new NoSuchAlgorithmException(type + " " + algorithm
                        + " implementation not found", e);
            }
        }

        private Object newInstanceNoParameter() throws NoSuchAlgorithmException {
            try {
                return implementation.newInstance();
            } catch (Exception e) {
                throw new NoSuchAlgorithmException(type + " " + algorithm
                        + " implementation not found", e);
            }
        }

        /**
         * Indicates whether this {@code Service} supports the specified
         * constructor parameter.
         *
         * @param parameter
         *            the parameter to test.
         * @return {@code true} if this {@code Service} supports the specified
         *         constructor parameter, {@code false} otherwise.
         */
        public boolean supportsParameter(Object parameter) {
            Boolean supportsParameter = supportsParameterTypes.get(type);
            if (supportsParameter == null) {
                return true;
            }
            if (!supportsParameter) {
                throw new InvalidParameterException("Cannot use a parameter with " + type);
            }

            /*
             * Only Key parameters are allowed, but allow null since there might
             * not be any listed classes or formats for this instance.
             */
            if (parameter != null && !(parameter instanceof Key)) {
                throw new InvalidParameterException("Parameter should be of type Key");
            }

            ensureSupportedKeysInitialized();

            // No restriction specified by Provider registration.
            if (keyClasses == null && keyFormats == null) {
                return true;
            }

            // Restriction specified by registration, so null is not acceptable.
            if (parameter == null) {
                return false;
            }

            Key keyParam = (Key) parameter;
            if (keyClasses != null && isInArray(keyClasses, keyParam.getClass())) {
                return true;
            }
            if (keyFormats != null && isInArray(keyFormats, keyParam.getFormat())) {
                return true;
            }

            return false;
        }

        /**
         * Initialize the list of supported key classes and formats.
         */
        private void ensureSupportedKeysInitialized() {
            if (supportedKeysInitialized) {
                return;
            }

            final String supportedClassesString = getAttribute(ATTR_SUPPORTED_KEY_CLASSES);
            if (supportedClassesString != null) {
                String[] keyClassNames = supportedClassesString.split("\\|");
                ArrayList<Class<?>> supportedClassList = new ArrayList<Class<?>>(
                        keyClassNames.length);
                final ClassLoader classLoader = getProvider().getClass().getClassLoader();
                for (String keyClassName : keyClassNames) {
                    try {
                        Class<?> keyClass = classLoader.loadClass(keyClassName);
                        if (Key.class.isAssignableFrom(keyClass)) {
                            supportedClassList.add(keyClass);
                        }
                    } catch (ClassNotFoundException ignored) {
                    }
                }
                keyClasses = supportedClassList.toArray(new Class<?>[supportedClassList.size()]);
            }

            final String supportedFormatString = getAttribute(ATTR_SUPPORTED_KEY_FORMATS);
            if (supportedFormatString != null) {
                keyFormats = supportedFormatString.split("\\|");
            }

            supportedKeysInitialized = true;
        }

        /**
         * Check if an item is in the array. The array of supported key classes
         * and formats is usually just a length of 1, so a simple array is
         * faster than a Set.
         */
        private static <T> boolean isInArray(T[] itemList, T target) {
            if (target == null) {
                return false;
            }
            for (T item : itemList) {
                if (target.equals(item)) {
                    return true;
                }
            }
            return false;
        }

        /**
         * Check if an item is in the array. The array of supported key classes
         * and formats is usually just a length of 1, so a simple array is
         * faster than a Set.
         */
        private static boolean isInArray(Class<?>[] itemList, Class<?> target) {
            if (target == null) {
                return false;
            }
            for (Class<?> item : itemList) {
                if (item.isAssignableFrom(target)) {
                    return true;
                }
            }
            return false;
        }

        /**
         * Returns a string containing a concise, human-readable description of
         * this {@code Service}.
         *
         * @return a printable representation for this {@code Service}.
         */
        @Override
        public String toString() {
            String result = "Provider " + provider.getName() + " Service "
                    + type + "." + algorithm + " " + className;
            if (aliases != null) {
                result = result + "\nAliases " + aliases.toString();
            }
            if (attributes != null) {
                result = result + "\nAttributes " + attributes.toString();
            }
            return result;
        }
    }

    private void readObject(java.io.ObjectInputStream in)
            throws NotActiveException, IOException, ClassNotFoundException {
        in.defaultReadObject();
        versionString = String.valueOf(version);
        providerNumber = -1;
    }
}
