content: /*
   * Copyright (C) 2014 The Android Open Source Project
   * Copyright (c) 1996, 2014, 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 java.io.*;
  import java.util.*;
  import java.util.concurrent.atomic.AtomicBoolean;
  
  import static java.util.Locale.ENGLISH;
  
  import java.lang.ref.*;
  import java.lang.reflect.*;
  import java.security.Security;
  import java.util.function.BiConsumer;
  import java.util.function.BiFunction;
  import java.util.function.Function;
  
  /**
   * 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, MD5 or SHA-1).
   *
   * <li>Key generation, conversion, and management facilities (such as for
   * algorithm-specific keys).
   *
   *</ul>
   *
   * <p>Each provider has a name and a version number, and is configured
   * in each runtime it is installed in.
   *
   * <p>See <a href =
   * "../../../technotes/guides/security/crypto/CryptoSpec.html#Provider">The Provider Class</a>
   * in the "Java Cryptography Architecture API Specification &amp; Reference"
   * for information about how a particular type of provider, the
   * cryptographic service provider, works and is installed. However,
   * 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>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>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 cellspacing=4>
   * <caption><b>Attributes Automatically Placed in a Provider Object</b></caption>
   * <tr><th>Name</th><th>Value</th>
   * <tr><td>{@code Provider.id name}</td>
    *    <td>{@code String.valueOf(provider.getName())}</td>
   * <tr><td>{@code Provider.id version}</td>
   *     <td>{@code String.valueOf(provider.getVersion())}</td>
   * <tr><td>{@code Provider.id info}</td>
         <td>{@code String.valueOf(provider.getInfo())}</td>
   * <tr><td>{@code Provider.id className}</td>
   *     <td>{@code provider.getClass().getName()}</td>
   * </table>
   *
   * @author Benjamin Renaud
   * @author Andreas Sterbenz
   */
  public abstract class Provider extends Properties {
  
      // Declare serialVersionUID to be compatible with JDK1.1
      static final long serialVersionUID = -4298000515446427739L;
  
      // Android-added: Provider registration
      // Marking a provider as "registered" makes it change the security version when
      // changes to it are made.  As of 2017-05-22 this is only used in ProviderTest.
      // TODO: Change ProviderTest to no longer require this mechanism
      private volatile boolean registered = false;
  
      private static final sun.security.util.Debug debug =
          sun.security.util.Debug.getInstance
          ("provider", "Provider");
  
      /**
       * The provider name.
       *
       * @serial
       */
      private String name;
  
      /**
       * A description of the provider and its services.
       *
       * @serial
       */
      private String info;
  
      /**
       * The provider version number.
       *
       * @serial
       */
      private double version;
  
  
      private transient Set<Entry<Object,Object>> entrySet = null;
      private transient int entrySetCallCount = 0;
  
      private transient boolean initialized;
  
      /**
       * Constructs a provider with the specified name, version number,
       * and information.
       *
       * @param name the provider name.
       *
       * @param version the provider version number.
       *
       * @param info a description of the provider and its services.
       */
      protected Provider(String name, double version, String info) {
          this.name = name;
          this.version = version;
          this.info = info;
          putId();
          initialized = true;
      }
  
      /**
       * Returns the name of this provider.
       *
       * @return the name of this provider.
       */
      public String getName() {
          return name;
      }
  
      /**
       * Returns the version number for this provider.
       *
       * @return the version number for this provider.
       */
      public double getVersion() {
          return version;
      }
  
      /**
       * Returns a human-readable description of the provider and its
       * services.  This may return an HTML page, with relevant links.
       *
       * @return a description of the provider and its services.
       */
      public String getInfo() {
          return info;
      }
  
      /**
       * Returns a string with the name and the version number
       * of this provider.
       *
       * @return the string with the name and the version number
       * for this provider.
       */
      public String toString() {
          return name + " version " + version;
      }
  
      /*
       * override the following methods to ensure that provider
       * information can only be changed if the caller has the appropriate
       * permissions.
       */
  
      /**
       * Clears this provider so that it no longer contains the properties
       * used to look up facilities implemented by the provider.
       *
       * <p>If a security manager is enabled, its {@code checkSecurityAccess}
       * method is called with the string {@code "clearProviderProperties."+name}
       * (where {@code name} is the provider name) to see if it's ok to clear
       * this provider.
       *
       * @throws  SecurityException
       *          if a security manager exists and its {@link
       *          SecurityManager#checkSecurityAccess} method
       *          denies access to clear this provider
       *
       * @since 1.2
       */
      @Override
      public synchronized void clear() {
          check("clearProviderProperties."+name);
          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.
       * @exception  IOException  if an error occurred when reading from the
       *               input stream.
       * @see Properties#load
       */
      @Override
      public synchronized void load(InputStream inStream) throws IOException {
          check("putProviderProperty."+name);
          if (debug != null) {
              debug.println("Load " + name + " provider properties");
          }
          Properties tempProperties = new Properties();
          tempProperties.load(inStream);
          implPutAll(tempProperties);
      }
  
      /**
       * Copies all of the mappings from the specified Map to this provider.
       * These mappings will replace any properties that this provider had
       * for any of the keys currently in the specified Map.
       *
       * @since 1.2
       */
      @Override
      public synchronized void putAll(Map<?,?> t) {
          check("putProviderProperty."+name);
          if (debug != null) {
              debug.println("Put all " + name + " provider properties");
          }
          implPutAll(t);
      }
  
      /**
       * Returns an unmodifiable Set view of the property entries contained
       * in this Provider.
       *
       * @see   Entry
       * @since 1.2
       */
      @Override
      public synchronized Set<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 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 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}.
       *
       * <p>If a security manager is enabled, its {@code checkSecurityAccess}
       * method is called with the string {@code "putProviderProperty."+name},
       * where {@code name} is the provider name, to see if it's ok to set this
       * provider's property values.
       *
       * @throws  SecurityException
       *          if a security manager exists and its {@link
       *          SecurityManager#checkSecurityAccess} method
       *          denies access to set property values.
       *
       * @since 1.2
       */
      @Override
      public synchronized Object put(Object key, Object value) {
          check("putProviderProperty."+name);
          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.
       *
       * <p>If a security manager is enabled, its {@code checkSecurityAccess}
       * method is called with the string {@code "putProviderProperty."+name},
       * where {@code name} is the provider name, to see if it's ok to set this
       * provider's property values.
       *
       * @throws  SecurityException
       *          if a security manager exists and its {@link
       *          SecurityManager#checkSecurityAccess} method
       *          denies access to set property values.
       *
       * @since 1.8
       */
      @Override
      public synchronized Object putIfAbsent(Object key, Object value) {
          check("putProviderProperty."+name);
          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}).
       *
       * <p>If a security manager is enabled, its {@code checkSecurityAccess}
       * method is called with the string {@code "removeProviderProperty."+name},
       * where {@code name} is the provider name, to see if it's ok to remove this
       * provider's properties.
       *
       * @throws  SecurityException
       *          if a security manager exists and its {@link
       *          SecurityManager#checkSecurityAccess} method
       *          denies access to remove this provider's properties.
       *
       * @since 1.2
       */
      @Override
      public synchronized Object remove(Object key) {
          check("removeProviderProperty."+name);
          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.
       *
       * <p>If a security manager is enabled, its {@code checkSecurityAccess}
       * method is called with the string {@code "removeProviderProperty."+name},
       * where {@code name} is the provider name, to see if it's ok to remove this
       * provider's properties.
       *
       * @throws  SecurityException
       *          if a security manager exists and its {@link
       *          SecurityManager#checkSecurityAccess} method
       *          denies access to remove this provider's properties.
       *
       * @since 1.8
       */
      @Override
      public synchronized boolean remove(Object key, Object value) {
          check("removeProviderProperty."+name);
          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.
       *
       * <p>If a security manager is enabled, its {@code checkSecurityAccess}
       * method is called with the string {@code "putProviderProperty."+name},
       * where {@code name} is the provider name, to see if it's ok to set this
       * provider's property values.
       *
       * @throws  SecurityException
       *          if a security manager exists and its {@link
       *          SecurityManager#checkSecurityAccess} method
       *          denies access to set property values.
       *
       * @since 1.8
       */
      @Override
      public synchronized boolean replace(Object key, Object oldValue,
              Object newValue) {
          check("putProviderProperty." + name);
  
          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.
       *
       * <p>If a security manager is enabled, its {@code checkSecurityAccess}
       * method is called with the string {@code "putProviderProperty."+name},
       * where {@code name} is the provider name, to see if it's ok to set this
       * provider's property values.
       *
       * @throws  SecurityException
       *          if a security manager exists and its {@link
       *          SecurityManager#checkSecurityAccess} method
       *          denies access to set property values.
       *
       * @since 1.8
       */
      @Override
      public synchronized Object replace(Object key, Object value) {
          check("putProviderProperty." + name);
  
          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.
       *
       * <p>If a security manager is enabled, its {@code checkSecurityAccess}
       * method is called with the string {@code "putProviderProperty."+name},
       * where {@code name} is the provider name, to see if it's ok to set this
       * provider's property values.
       *
       * @throws  SecurityException
       *          if a security manager exists and its {@link
       *          SecurityManager#checkSecurityAccess} method
       *          denies access to set property values.
       *
       * @since 1.8
       */
      @Override
      public synchronized void replaceAll(BiFunction<? super Object, ? super Object, ? extends Object> function) {
          check("putProviderProperty." + name);
  
          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).
       *
       * <p>If a security manager is enabled, its {@code checkSecurityAccess}
       * method is called with the strings {@code "putProviderProperty."+name}
       * and {@code "removeProviderProperty."+name}, where {@code name} is the
       * provider name, to see if it's ok to set this provider's property values
       * and remove this provider's properties.
       *
       * @throws  SecurityException
       *          if a security manager exists and its {@link
       *          SecurityManager#checkSecurityAccess} method
       *          denies access to set property values or remove properties.
       *
       * @since 1.8
       */
      @Override
      public synchronized Object compute(Object key,
          BiFunction<? super Object, ? super Object, ? extends Object> remappingFunction) {
          check("putProviderProperty." + name);
          check("removeProviderProperty" + name);
  
          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}.
       *
       * <p>If a security manager is enabled, its {@code checkSecurityAccess}
       * method is called with the strings {@code "putProviderProperty."+name}
       * and {@code "removeProviderProperty."+name}, where {@code name} is the
       * provider name, to see if it's ok to set this provider's property values
       * and remove this provider's properties.
       *
       * @throws  SecurityException
       *          if a security manager exists and its {@link
       *          SecurityManager#checkSecurityAccess} method
       *          denies access to set property values and remove properties.
       *
       * @since 1.8
       */
      @Override
      public synchronized Object computeIfAbsent(Object key, Function<? super Object, ? extends Object> mappingFunction) {
          check("putProviderProperty." + name);
          check("removeProviderProperty" + name);
  
          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.
       *
       * <p>If a security manager is enabled, its {@code checkSecurityAccess}
       * method is called with the strings {@code "putProviderProperty."+name}
       * and {@code "removeProviderProperty."+name}, where {@code name} is the
       * provider name, to see if it's ok to set this provider's property values
       * and remove this provider's properties.
       *
       * @throws  SecurityException
       *          if a security manager exists and its {@link
       *          SecurityManager#checkSecurityAccess} method
       *          denies access to set property values or remove properties.
       *
       * @since 1.8
       */
      @Override
      public synchronized Object computeIfPresent(Object key, BiFunction<? super Object, ? super Object, ? extends Object> remappingFunction) {
          check("putProviderProperty." + name);
          check("removeProviderProperty" + name);
  
          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 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 null. This method may be of use when
       * combining multiple mapped values for a key.
       *
       * <p>If a security manager is enabled, its {@code checkSecurityAccess}
       * method is called with the strings {@code "putProviderProperty."+name}
       * and {@code "removeProviderProperty."+name}, where {@code name} is the
       * provider name, to see if it's ok to set this provider's property values
       * and remove this provider's properties.
       *
       * @throws  SecurityException
       *          if a security manager exists and its {@link
       *          SecurityManager#checkSecurityAccess} method
       *          denies access to set property values or remove properties.
       *
       * @since 1.8
       */
      @Override
      public synchronized Object merge(Object key, Object value,  BiFunction<? super Object, ? super Object, ? extends Object>  remappingFunction) {
          check("putProviderProperty." + name);
          check("removeProviderProperty" + name);
  
          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();
          }
      }
  
      private void check(String directive) {
          checkInitialized();
          SecurityManager security = System.getSecurityManager();
          if (security != null) {
              security.checkSecurityAccess(directive);
          }
      }
  
      // legacy properties changed since last call to any services method?
      private transient boolean legacyChanged;
      // serviceMap changed since last call to getServices()
      private transient boolean servicesChanged;
  
      // Map<String,String>
      private transient Map<String,String> legacyStrings;
  
      // Map<ServiceKey,Service>
      // used for services added via putService(), initialized on demand
      private transient Map<ServiceKey,Service> serviceMap;
  
      // 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 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(version));
          super.put("Provider.id info", String.valueOf(info));
          super.put("Provider.id className", this.getClass().getName());
      }
  
      private void readObject(ObjectInputStream in)
                  throws IOException, ClassNotFoundException {
          // Android-added: Provider registration
          registered = false;
          Map<Object,Object> copy = new HashMap<>();
          for (Entry<Object,Object> entry : super.entrySet()) {
              copy.put(entry.getKey(), entry.getValue());
          }
          defaults = null;
          in.defaultReadObject();
          implClear();
          initialized = true;
          putAll(copy);
      }
  
      private boolean checkLegacy(Object key) {
          // Android-added: Provider registration
          if (registered) {
              Security.increaseVersion();
          }
          String keyString = (String)key;
          if (keyString.startsWith("Provider.")) {
              return false;
          }
  
          legacyChanged = true;
          if (legacyStrings == null) {
              legacyStrings = new LinkedHashMap<String,String>();
          }
          return true;
      }
  
      /**
       * Copies all of the mappings from the specified Map to this provider.
       * Internal method to be called AFTER the security check has been
       * performed.
       */
      private void implPutAll(Map<?,?> t) {
          for (Entry<?,?> e : t.entrySet()) {
              implPut(e.getKey(), e.getValue());
          }
          // Android-added: Provider registration
          if (registered) {
              Security.increaseVersion();
          }
      }
  
      private Object implRemove(Object key) {
          if (key instanceof String) {
              if (!checkLegacy(key)) {
                  return null;
              }
              legacyStrings.remove((String)key);
          }
          return super.remove(key);
      }
  
      private boolean implRemove(Object key, Object value) {
          if (key instanceof String && value instanceof String) {
              if (!checkLegacy(key)) {
                  return false;
              }
              legacyStrings.remove((String)key, value);
          }
          return super.remove(key, value);
      }
  
      private boolean implReplace(Object key, Object oldValue, Object newValue) {
          if ((key instanceof String) && (oldValue instanceof String) &&
                  (newValue instanceof String)) {
              if (!checkLegacy(key)) {
                  return false;
              }
              legacyStrings.replace((String)key, (String)oldValue,
                      (String)newValue);
          }
          return super.replace(key, oldValue, newValue);
      }
  
      private Object implReplace(Object key, Object value) {
          if ((key instanceof String) && (value instanceof String)) {
              if (!checkLegacy(key)) {
                  return null;
              }
              legacyStrings.replace((String)key, (String)value);
          }
          return super.replace(key, value);
      }
  
      private void implReplaceAll(BiFunction<? super Object, ? super Object, ? extends Object> function) {
          legacyChanged = true;
          if (legacyStrings == null) {
              legacyStrings = new LinkedHashMap<String,String>();
          } else {
              legacyStrings.replaceAll((BiFunction<? super String, ? super String, ? extends String>) function);
          }
          super.replaceAll(function);
      }
  
  
      private Object implMerge(Object key, Object value, BiFunction<? super Object, ? super Object, ? extends Object> remappingFunction) {
          if ((key instanceof String) && (value instanceof String)) {
              if (!checkLegacy(key)) {
                  return null;
              }
              legacyStrings.merge((String)key, (String)value,
                      (BiFunction<? super String, ? super String, ? extends String>) remappingFunction);
          }
          return super.merge(key, value, remappingFunction);
      }
  
      private Object implCompute(Object key, BiFunction<? super Object, ? super Object, ? extends Object> remappingFunction) {
          if (key instanceof String) {
              if (!checkLegacy(key)) {
                  return null;
              }
              // BEGIN Android-changed: use compute() instead of computeIfAbsent() to avoid cast fails
              // The upstream code cannot ever succeed as the cast from BiFunction to Function
              // always fails.
              // legacyStrings.computeIfAbsent((String) key,
              //         (Function<? super String, ? extends String>) remappingFunction);
              legacyStrings.compute((String) key,
                      (BiFunction<? super String, ? super String, ? extends String>)
                              remappingFunction);
              // END Android-changed: use compute() instead of computeIfAbsent() to avoid cast fails
          }
          return super.compute(key, remappingFunction);
      }
  
      private Object implComputeIfAbsent(Object key, Function<? super Object, ? extends Object> mappingFunction) {
          if (key instanceof String) {
              if (!checkLegacy(key)) {
                  return null;
              }
              legacyStrings.computeIfAbsent((String) key,
                      (Function<? super String, ? extends String>) mappingFunction);
          }
          return super.computeIfAbsent(key, mappingFunction);
      }
  
      private Object implComputeIfPresent(Object key, BiFunction<? super Object, ? super Object, ? extends Object> remappingFunction) {
          if (key instanceof String) {
              if (!checkLegacy(key)) {
                  return null;
              }
              legacyStrings.computeIfPresent((String) key,
                      (BiFunction<? super String, ? super String, ? extends String>) remappingFunction);
          }
          return super.computeIfPresent(key, remappingFunction);
      }
  
      private Object implPut(Object key, Object value) {
          if ((key instanceof String) && (value instanceof String)) {
              if (!checkLegacy(key)) {
                  return null;
              }
              legacyStrings.put((String)key, (String)value);
          }
          return super.put(key, value);
      }
  
      private Object implPutIfAbsent(Object key, Object value) {
          if ((key instanceof String) && (value instanceof String)) {
              if (!checkLegacy(key)) {
                  return null;
              }
              legacyStrings.putIfAbsent((String)key, (String)value);
          }
          return super.putIfAbsent(key, value);
      }
  
      private void implClear() {
          if (legacyStrings != null) {
              legacyStrings.clear();
          }
          if (legacyMap != null) {
              legacyMap.clear();
          }
          if (serviceMap != null) {
              serviceMap.clear();
          }
          legacyChanged = false;
          servicesChanged = false;
          serviceSet = null;
          super.clear();
          putId();
          // Android-added: Provider registration
          if (registered) {
            Security.increaseVersion();
          }
      }
  
      // 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() + algorithm.hashCode();
          }
          public boolean equals(Object obj) {
              if (this == obj) {
                  return true;
              }
              if (obj instanceof ServiceKey == false) {
                  return false;
              }
              ServiceKey other = (ServiceKey)obj;
              return this.type.equals(other.type)
                  && this.algorithm.equals(other.algorithm);
          }
          boolean matches(String type, String algorithm) {
              return (this.type == type) && (this.originalAlgorithm == algorithm);
          }
      }
  
      /**
       * Ensure all the legacy String properties are fully parsed into
       * service objects.
       */
      private void ensureLegacyParsed() {
          if ((legacyChanged == false) || (legacyStrings == null)) {
              return;
          }
          serviceSet = null;
          if (legacyMap == null) {
              legacyMap = new LinkedHashMap<ServiceKey,Service>();
          } else {
              legacyMap.clear();
          }
          for (Entry<String,String> entry : legacyStrings.entrySet()) {
              parseLegacyPut(entry.getKey(), entry.getValue());
          }
          removeInvalidServices(legacyMap);
          legacyChanged = false;
      }
  
      /**
       * Remove all invalid services from the Map. Invalid services can only
       * occur if the legacy properties are inconsistent or incomplete.
       */
      private void removeInvalidServices(Map<ServiceKey,Service> map) {
          for (Iterator<Entry<ServiceKey, Service>> t =
               map.entrySet().iterator(); t.hasNext(); ) {
              Service s = t.next().getValue();
              if (s.isValid() == false) {
                  t.remove();
              }
          }
      }
  
      private String[] getTypeAndAlgorithm(String key) {
          int i = key.indexOf(".");
          if (i < 1) {
              if (debug != null) {
                  debug.println("Ignoring invalid entry in provider "
                          + name + ":" + key);
              }
              return null;
          }
          String type = key.substring(0, i);
          String alg = key.substring(i + 1);
          return new String[] {type, alg};
      }
  
      private final static String ALIAS_PREFIX = "Alg.Alias.";
      private final static String ALIAS_PREFIX_LOWER = "alg.alias.";
      private final static int ALIAS_LENGTH = ALIAS_PREFIX.length();
  
      private void parseLegacyPut(String name, String value) {
          if (name.toLowerCase(ENGLISH).startsWith(ALIAS_PREFIX_LOWER)) {
              // e.g. put("Alg.Alias.MessageDigest.SHA", "SHA-1");
              // aliasKey ~ MessageDigest.SHA
              String stdAlg = value;
              String aliasKey = name.substring(ALIAS_LENGTH);
              String[] typeAndAlg = getTypeAndAlgorithm(aliasKey);
              if (typeAndAlg == null) {
                  return;
              }
              String type = getEngineName(typeAndAlg[0]);
              String aliasAlg = typeAndAlg[1].intern();
              ServiceKey key = new ServiceKey(type, stdAlg, true);
              Service s = legacyMap.get(key);
              if (s == null) {
                  s = new Service(this);
                  s.type = type;
                  s.algorithm = stdAlg;
                  legacyMap.put(key, s);
              }
              legacyMap.put(new ServiceKey(type, aliasAlg, true), s);
              s.addAlias(aliasAlg);
          } else {
              String[] typeAndAlg = getTypeAndAlgorithm(name);
              if (typeAndAlg == null) {
                  return;
              }
              int i = typeAndAlg[1].indexOf(' ');
              if (i == -1) {
                  // e.g. put("MessageDigest.SHA-1", "sun.security.provider.SHA");
                  String type = getEngineName(typeAndAlg[0]);
                  String stdAlg = typeAndAlg[1].intern();
                  String className = value;
                  ServiceKey key = new ServiceKey(type, stdAlg, true);
                  Service s = legacyMap.get(key);
                  if (s == null) {
                      s = new Service(this);
                      s.type = type;
                      s.algorithm = stdAlg;
                      legacyMap.put(key, s);
                  }
                  s.className = className;
              } else { // attribute
                  // e.g. put("MessageDigest.SHA-1 ImplementedIn", "Software");
                  String attributeValue = value;
                  String type = getEngineName(typeAndAlg[0]);
                  String attributeString = typeAndAlg[1];
                  String stdAlg = attributeString.substring(0, i).intern();
                  String attributeName = attributeString.substring(i + 1);
                  // kill additional spaces
                  while (attributeName.startsWith(" ")) {
                      attributeName = attributeName.substring(1);
                  }
                  attributeName = attributeName.intern();
                  ServiceKey key = new ServiceKey(type, stdAlg, true);
                  Service s = legacyMap.get(key);
                  if (s == null) {
                      s = new Service(this);
                      s.type = type;
                      s.algorithm = stdAlg;
                      legacyMap.put(key, s);
                  }
                  s.addAttribute(attributeName, attributeValue);
              }
          }
      }
  
      /**
       * 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 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 null if no such service exists
       *
       * @throws NullPointerException if type or algorithm is null
       *
       * @since 1.5
       */
      public synchronized Service getService(String type, String algorithm) {
          checkInitialized();
          // avoid allocating a new key object if possible
          ServiceKey key = previousKey;
          if (key.matches(type, algorithm) == false) {
              key = new ServiceKey(type, algorithm, false);
              previousKey = key;
          }
          if (serviceMap != null) {
              Service service = serviceMap.get(key);
              if (service != null) {
                  return service;
              }
          }
          ensureLegacyParsed();
          return (legacyMap != null) ? legacyMap.get(key) : null;
      }
  
      // 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 volatile ServiceKey previousKey =
                                              new ServiceKey("", "", false);
  
      /**
       * Get an unmodifiable Set of all services supported by
       * this Provider.
       *
       * @return an unmodifiable Set of all services supported by
       * this Provider
       *
       * @since 1.5
       */
      public synchronized Set<Service> getServices() {
          checkInitialized();
          if (legacyChanged || servicesChanged) {
              serviceSet = null;
          }
          if (serviceSet == null) {
              ensureLegacyParsed();
              Set<Service> set = new LinkedHashSet<>();
              if (serviceMap != null) {
                  set.addAll(serviceMap.values());
              }
              if (legacyMap != null) {
                  set.addAll(legacyMap.values());
              }
              serviceSet = Collections.unmodifiableSet(set);
              servicesChanged = 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
       * <a href="../../../technotes/guides/security/crypto/CryptoSpec.html">
       * Java Cryptography Architecture API Specification &amp; Reference </a>.
       *
       * <p>Also, if there is a security manager, its
       * {@code checkSecurityAccess} method is called with the string
       * {@code "putProviderProperty."+name}, where {@code name} is
       * the provider name, to see if it's ok to set this provider's property
       * values. If the default implementation of {@code checkSecurityAccess}
       * is used (that is, that method is not overriden), then this results in
       * a call to the security manager's {@code checkPermission} method with
       * a {@code SecurityPermission("putProviderProperty."+name)}
       * permission.
       *
       * @param s the Service to add
       *
       * @throws SecurityException
       *      if a security manager exists and its {@link
       *      SecurityManager#checkSecurityAccess} method denies
       *      access to set property values.
       * @throws NullPointerException if s is null
       *
       * @since 1.5
       */
      protected synchronized void putService(Service s) {
          check("putProviderProperty." + name);
          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");
          }
          if (serviceMap == null) {
              serviceMap = new LinkedHashMap<ServiceKey,Service>();
          }
          servicesChanged = true;
          String type = s.getType();
          String algorithm = s.getAlgorithm();
          ServiceKey key = new ServiceKey(type, algorithm, true);
          // remove existing service
          implRemoveService(serviceMap.get(key));
          serviceMap.put(key, s);
          for (String alias : s.getAliases()) {
              serviceMap.put(new ServiceKey(type, alias, true), s);
          }
          putPropertyStrings(s);
      }
  
      /**
       * 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 permission check and other processing
          super.put(type + "." + algorithm, s.getClassName());
          for (String alias : s.getAliases()) {
              super.put(ALIAS_PREFIX + type + "." + alias, algorithm);
          }
          for (Entry<UString,String> entry : s.attributes.entrySet()) {
              String key = type + "." + algorithm + " " + entry.getKey();
              super.put(key, entry.getValue());
          }
          // Android-added: Provider registration
          if (registered) {
              Security.increaseVersion();
          }
      }
  
      /**
       * 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 permission check and other processing
          super.remove(type + "." + algorithm);
          for (String alias : s.getAliases()) {
              super.remove(ALIAS_PREFIX + type + "." + alias);
          }
          for (Entry<UString,String> entry : s.attributes.entrySet()) {
              String key = type + "." + algorithm + " " + entry.getKey();
              super.remove(key);
          }
          // Android-added: Provider registration
          if (registered) {
            Security.increaseVersion();
          }
      }
  
      /**
       * Remove a service previously added using
       * {@link #putService putService()}. The specified service is removed from
       * this provider. It will no longer be returned by
       * {@link #getService getService()} and its information will be removed
       * from this provider's Hashtable.
       *
       * <p>Also, if there is a security manager, its
       * {@code checkSecurityAccess} method is called with the string
       * {@code "removeProviderProperty."+name}, where {@code name} is
       * the provider name, to see if it's ok to remove this provider's
       * properties. If the default implementation of
       * {@code checkSecurityAccess} is used (that is, that method is not
       * overriden), then this results in a call to the security manager's
       * {@code checkPermission} method with a
       * {@code SecurityPermission("removeProviderProperty."+name)}
       * permission.
       *
       * @param s the Service to be removed
       *
       * @throws  SecurityException
       *          if a security manager exists and its {@link
       *          SecurityManager#checkSecurityAccess} method denies
       *          access to remove this provider's properties.
       * @throws NullPointerException if s is null
       *
       * @since 1.5
       */
      protected synchronized void removeService(Service s) {
          check("removeProviderProperty." + name);
          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 == null)) {
              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);
      }
  
      // 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;
              }
              if (obj instanceof UString == false) {
                  return false;
              }
              UString other = (UString)obj;
              return 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 String constructorParameterClassName;
          private volatile Class<?> constructorParameterClass;
  
          EngineDescription(String name, boolean sp, String paramName) {
              this.name = name;
              this.supportsParameter = sp;
              this.constructorParameterClassName = paramName;
          }
          Class<?> getConstructorParameterClass() throws ClassNotFoundException {
              Class<?> clazz = constructorParameterClass;
              if (clazz == null) {
                  clazz = Class.forName(constructorParameterClassName);
                  constructorParameterClass = clazz;
              }
              return clazz;
          }
      }
  
      // 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, String paramName) {
          EngineDescription ed = new EngineDescription(name, sp, paramName);
          // 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<String,EngineDescription>();
          // 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, null);
          addEngine("Signature",                          true,  null);
          addEngine("CertificateFactory",                 false, null);
          addEngine("CertPathBuilder",                    false, null);
          addEngine("CertPathValidator",                  false, null);
          addEngine("CertStore",                          false,
                              "java.security.cert.CertStoreParameters");
          // 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);
          // 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
          addEngine("Policy",                             false,
                              "java.security.Policy$Parameters");
          // CONFIGURATION
          addEngine("Configuration",                      false,
                              "javax.security.auth.login.Configuration$Parameters");
          // XML DSig
          addEngine("XMLSignatureFactory",                false, null);
          addEngine("KeyInfoFactory",                     false, null);
          addEngine("TransformService",                   false, null);
          // Smart Card I/O
          addEngine("TerminalFactory",                    false,
                              "java.lang.Object");
      }
  
      // get the "standard" (mixed-case) engine name for arbitary 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) 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
       * <a href="../../../technotes/guides/security/crypto/CryptoSpec.html">
       * Java Cryptography Architecture API Specification &amp; Reference </a>
       * 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 String type, algorithm, className;
          private final Provider provider;
          private List<String> aliases;
          private Map<UString,String> attributes;
  
          // Reference to the cached implementation Class object
          private volatile Reference<Class<?>> classRef;
  
          // 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) {
              this.provider = provider;
              aliases = Collections.<String>emptyList();
              attributes = Collections.<UString,String>emptyMap();
          }
  
          private boolean isValid() {
              return (type != null) && (algorithm != null) && (className != null);
          }
  
          private void addAlias(String alias) {
              if (aliases.isEmpty()) {
                  aliases = new ArrayList<String>(2);
              }
              aliases.add(alias);
          }
  
          void addAttribute(String type, String value) {
              if (attributes.isEmpty()) {
                  attributes = new HashMap<UString,String>(8);
              }
              attributes.put(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 null if algorithm has no aliases
           * @param attributes Map of attributes or null if this implementation
           *                   has no attributes
           *
           * @throws NullPointerException if provider, type, algorithm, or
           * className is 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);
              this.algorithm = algorithm;
              this.className = className;
              if (aliases == null) {
                  this.aliases = Collections.<String>emptyList();
              } else {
                  this.aliases = new ArrayList<String>(aliases);
              }
              if (attributes == null) {
                  this.attributes = Collections.<UString,String>emptyMap();
              } else {
                  this.attributes = new HashMap<UString,String>();
                  for (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 final List<String> getAliases() {
              return aliases;
          }
  
          /**
           * Return the value of the specified attribute or 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 null if the
           *         attribute is not present
           *
           * @throws NullPointerException if name is 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
           * <a href="../../../technotes/guides/security/crypto/CryptoSpec.html">
           * Java Cryptography Architecture API Specification &amp;
           * Reference</a>.
           *
           * @param constructorParameter the value to pass to the constructor,
           * or 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 == false) {
                  if (provider.getService(type, algorithm) != this) {
                      throw new NoSuchAlgorithmException
                          ("Service not registered with Provider "
                          + provider.getName() + ": " + this);
                  }
                  registered = true;
              }
              try {
                  EngineDescription cap = knownEngines.get(type);
                  if (cap == null) {
                      // unknown engine type, use generic code
                      // this is the code path future for non-core
                      // optional packages
                      return newInstanceGeneric(constructorParameter);
                  }
                  if (cap.constructorParameterClassName == null) {
                      if (constructorParameter != null) {
                          throw new InvalidParameterException
                              ("constructorParameter not used with " + type
                              + " engines");
                      }
                      Class<?> clazz = getImplClass();
                      Class<?>[] empty = {};
                      Constructor<?> con = clazz.getConstructor(empty);
                      return con.newInstance();
                  } else {
                      Class<?> paramClass = cap.getConstructorParameterClass();
                      if (constructorParameter != null) {
                          Class<?> argClass = constructorParameter.getClass();
                          if (paramClass.isAssignableFrom(argClass) == false) {
                              throw new InvalidParameterException
                              ("constructorParameter must be instanceof "
                              + cap.constructorParameterClassName.replace('$', '.')
                              + " for engine type " + type);
                          }
                      }
                      Class<?> clazz = getImplClass();
                      Constructor<?> cons = clazz.getConstructor(paramClass);
                      return cons.newInstance(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);
              }
          }
  
          // return the implementation Class object for this service
          private Class<?> getImplClass() throws NoSuchAlgorithmException {
              try {
                  Reference<Class<?>> ref = classRef;
                  Class<?> clazz = (ref == null) ? null : 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.");
                      }
                      classRef = new WeakReference<Class<?>>(clazz);
                  }
                  return clazz;
              } catch (ClassNotFoundException e) {
                  throw new NoSuchAlgorithmException
                      ("class configured for " + type + " (provider: " +
                      provider.getName() + ") cannot be found.", e);
              }
          }
  
          /**
           * Generic code path for unknown engine types. Call the
           * no-args constructor if constructorParameter is null, otherwise
           * use the first matching constructor.
           */
          private Object newInstanceGeneric(Object constructorParameter)
                  throws Exception {
              Class<?> clazz = getImplClass();
              if (constructorParameter == null) {
                  // create instance with public no-arg constructor if it exists
                  try {
                      Class<?>[] empty = {};
                      Constructor<?> con = clazz.getConstructor(empty);
                      return con.newInstance();
                  } catch (NoSuchMethodException e) {
                      throw new NoSuchAlgorithmException("No public no-arg "
                          + "constructor found in class " + className);
                  }
              }
              Class<?> argClass = constructorParameter.getClass();
              Constructor[] cons = clazz.getConstructors();
              // find first public constructor that can take the
              // argument as parameter
              for (Constructor<?> con : cons) {
                  Class<?>[] paramTypes = con.getParameterTypes();
                  if (paramTypes.length != 1) {
                      continue;
                  }
                  if (paramTypes[0].isAssignableFrom(argClass) == false) {
                      continue;
                  }
                  return con.newInstance(constructorParameter);
              }
              throw new NoSuchAlgorithmException("No public constructor matching "
                  + argClass.getName() + " found in class " + className);
          }
  
          /**
           * Test whether this Service can use the specified parameter.
           * Returns false if this service cannot use the parameter. Returns
           * 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
           * <a href="../../../technotes/guides/security/crypto/CryptoSpec.html">
           * Java Cryptography Architecture API Specification &amp;
           * Reference</a>.
           * Security providers can override it to implement their own test.
           *
           * @param parameter the parameter to test
           *
           * @return false if this this service cannot use the specified
           * parameter; 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 = knownEngines.get(type);
              if (cap == null) {
                  // unknown engine type, return true by default
                  return true;
              }
              if (cap.supportsParameter == false) {
                  throw new InvalidParameterException("supportsParameter() not "
                      + "used with " + type + " engines");
              }
              // allow null for keys without attributes for compatibility
              if ((parameter != null) && (parameter instanceof Key == false)) {
                  throw new InvalidParameterException
                      ("Parameter must be instanceof Key for engine " + type);
              }
              if (hasKeyAttributes() == false) {
                  return true;
              }
              if (parameter == null) {
                  return false;
              }
              Key key = (Key)parameter;
              if (supportsKeyFormat(key)) {
                  return true;
              }
              if (supportsKeyClass(key)) {
                  return true;
              }
              return false;
          }
  
          /**
           * 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) {
                      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);
                      }
                      boolean bool = (supportedFormats != null)
                          || (supportedClasses != null);
                      b = Boolean.valueOf(bool);
                      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 String representation of this service.
           *
           * @return a 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";
          }
  
      }
  
      // BEGIN Android-added: Provider registration
      /**
       * @hide
       */
      public void setRegistered() {
          registered = true;
      }
  
      /**
       * @hide
       */
      public void setUnregistered() {
          registered = false;
      }
  
      /**
       * @hide
       */
      public boolean isRegistered() {
          return registered;
      }
  
      /**
       * Ensure the values cached by {@link #getServices} and {@link #getService} are already computed
       *
       * Used by the zygote so that initialization is performed during preload for the providers
       * available at that point.
       *
       * @hide
       */
      public synchronized void warmUpServiceProvision() {
          checkInitialized();
          // Further calls do nothing if the services didn't change. If not called here, it would
          // parse legacy strings the first time that a service is requested.
          ensureLegacyParsed();
          // This call to getServices will update fields so that further calls will just return a
          // stored field, if the services didn't change in the meantime.
          getServices();
      }
      // END Android-added: Provider registration
  }
  