Provider.java revision c3a9db83a352d92d5a6e0098f22bde07e34a1d3b
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 * Copyright (c) 1996, 2011, Oracle and/or its affiliates. All rights reserved.
4 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5 *
6 * This code is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License version 2 only, as
8 * published by the Free Software Foundation.  Oracle designates this
9 * particular file as subject to the "Classpath" exception as provided
10 * by Oracle in the LICENSE file that accompanied this code.
11 *
12 * This code is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15 * version 2 for more details (a copy is included in the LICENSE file that
16 * accompanied this code).
17 *
18 * You should have received a copy of the GNU General Public License version
19 * 2 along with this work; if not, write to the Free Software Foundation,
20 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
21 *
22 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
23 * or visit www.oracle.com if you need additional information or have any
24 * questions.
25 */
26
27package java.security;
28
29import java.io.*;
30import java.util.*;
31import java.util.concurrent.atomic.AtomicBoolean;
32
33import static java.util.Locale.ENGLISH;
34
35import java.lang.ref.*;
36import java.lang.reflect.*;
37import java.security.Security;
38import java.security.cert.CertStoreParameters;
39import java.util.function.BiConsumer;
40
41import javax.security.auth.login.Configuration;
42
43/**
44 * This class represents a "provider" for the
45 * Java Security API, where a provider implements some or all parts of
46 * Java Security. Services that a provider may implement include:
47 *
48 * <ul>
49 *
50 * <li>Algorithms (such as DSA, RSA, MD5 or SHA-1).
51 *
52 * <li>Key generation, conversion, and management facilities (such as for
53 * algorithm-specific keys).
54 *
55 *</ul>
56 *
57 * <p>Each provider has a name and a version number, and is configured
58 * in each runtime it is installed in.
59 *
60 * <p>See <a href =
61 * "../../../technotes/guides/security/crypto/CryptoSpec.html#Provider">The Provider Class</a>
62 * in the "Java Cryptography Architecture API Specification &amp; Reference"
63 * for information about how a particular type of provider, the
64 * cryptographic service provider, works and is installed. However,
65 * please note that a provider can be used to implement any security
66 * service in Java that uses a pluggable architecture with a choice
67 * of implementations that fit underneath.
68 *
69 * <p>Some provider implementations may encounter unrecoverable internal
70 * errors during their operation, for example a failure to communicate with a
71 * security token. A {@link ProviderException} should be used to indicate
72 * such errors.
73 *
74 * <p>The service type <code>Provider</code> is reserved for use by the
75 * security framework. Services of this type cannot be added, removed,
76 * or modified by applications.
77 * The following attributes are automatically placed in each Provider object:
78 * <table cellspacing=4>
79 * <tr><th>Name</th><th>Value</th>
80 * <tr><td><code>Provider.id name</code></td>
81  *    <td><code>String.valueOf(provider.getName())</code></td>
82 * <tr><td><code>Provider.id version</code></td>
83 *     <td><code>String.valueOf(provider.getVersion())</code></td>
84 * <tr><td><code>Provider.id info</code></td>
85       <td><code>String.valueOf(provider.getInfo())</code></td>
86 * <tr><td><code>Provider.id className</code></td>
87 *     <td><code>provider.getClass().getName()</code></td>
88 * </table>
89 *
90 * @author Benjamin Renaud
91 * @author Andreas Sterbenz
92 */
93public abstract class Provider extends Properties {
94
95    // Declare serialVersionUID to be compatible with JDK1.1
96    static final long serialVersionUID = -4298000515446427739L;
97
98    private volatile boolean registered = false;
99
100    private static final sun.security.util.Debug debug =
101        sun.security.util.Debug.getInstance
102        ("provider", "Provider");
103
104    /**
105     * The provider name.
106     *
107     * @serial
108     */
109    private String name;
110
111    /**
112     * A description of the provider and its services.
113     *
114     * @serial
115     */
116    private String info;
117
118    /**
119     * The provider version number.
120     *
121     * @serial
122     */
123    private double version;
124
125
126    private transient Set<Map.Entry<Object,Object>> entrySet = null;
127    private transient int entrySetCallCount = 0;
128
129    private transient boolean initialized;
130
131    /**
132     * Constructs a provider with the specified name, version number,
133     * and information.
134     *
135     * @param name the provider name.
136     *
137     * @param version the provider version number.
138     *
139     * @param info a description of the provider and its services.
140     */
141    protected Provider(String name, double version, String info) {
142        this.name = name;
143        this.version = version;
144        this.info = info;
145        putId();
146        initialized = true;
147    }
148
149    /**
150     * Returns the name of this provider.
151     *
152     * @return the name of this provider.
153     */
154    public String getName() {
155        return name;
156    }
157
158    /**
159     * Returns the version number for this provider.
160     *
161     * @return the version number for this provider.
162     */
163    public double getVersion() {
164        return version;
165    }
166
167    /**
168     * Returns a human-readable description of the provider and its
169     * services.  This may return an HTML page, with relevant links.
170     *
171     * @return a description of the provider and its services.
172     */
173    public String getInfo() {
174        return info;
175    }
176
177    /**
178     * Returns a string with the name and the version number
179     * of this provider.
180     *
181     * @return the string with the name and the version number
182     * for this provider.
183     */
184    public String toString() {
185        return name + " version " + version;
186    }
187
188    /*
189     * override the following methods to ensure that provider
190     * information can only be changed if the caller has the appropriate
191     * permissions.
192     */
193
194    /**
195     * Clears this provider so that it no longer contains the properties
196     * used to look up facilities implemented by the provider.
197     *
198     * <p>First, if there is a security manager, its
199     * <code>checkSecurityAccess</code> method is called with the string
200     * <code>"clearProviderProperties."+name</code> (where <code>name</code>
201     * is the provider name) to see if it's ok to clear this provider.
202     * If the default implementation of <code>checkSecurityAccess</code>
203     * is used (that is, that method is not overriden), then this results in
204     * a call to the security manager's <code>checkPermission</code> method
205     * with a <code>SecurityPermission("clearProviderProperties."+name)</code>
206     * permission.
207     *
208     * @throws  SecurityException
209     *          if a security manager exists and its <code>{@link
210     *          java.lang.SecurityManager#checkSecurityAccess}</code> method
211     *          denies access to clear this provider
212     *
213     * @since 1.2
214     */
215    public synchronized void clear() {
216        check("clearProviderProperties."+name);
217        if (debug != null) {
218            debug.println("Remove " + name + " provider properties");
219        }
220        implClear();
221    }
222
223    /**
224     * Reads a property list (key and element pairs) from the input stream.
225     *
226     * @param inStream   the input stream.
227     * @exception  IOException  if an error occurred when reading from the
228     *               input stream.
229     * @see java.util.Properties#load
230     */
231    public synchronized void load(InputStream inStream) throws IOException {
232        check("putProviderProperty."+name);
233        if (debug != null) {
234            debug.println("Load " + name + " provider properties");
235        }
236        Properties tempProperties = new Properties();
237        tempProperties.load(inStream);
238        implPutAll(tempProperties);
239    }
240
241    /**
242     * Copies all of the mappings from the specified Map to this provider.
243     * These mappings will replace any properties that this provider had
244     * for any of the keys currently in the specified Map.
245     *
246     * @since 1.2
247     */
248    public synchronized void putAll(Map<?,?> t) {
249        check("putProviderProperty."+name);
250        if (debug != null) {
251            debug.println("Put all " + name + " provider properties");
252        }
253        implPutAll(t);
254    }
255
256    /**
257     * Returns an unmodifiable Set view of the property entries contained
258     * in this Provider.
259     *
260     * @see   java.util.Map.Entry
261     * @since 1.2
262     */
263    public synchronized Set<Map.Entry<Object,Object>> entrySet() {
264        checkInitialized();
265        if (entrySet == null) {
266            if (entrySetCallCount++ == 0)  // Initial call
267                entrySet = Collections.unmodifiableMap(this).entrySet();
268            else
269                return super.entrySet();   // Recursive call
270        }
271
272        // This exception will be thrown if the implementation of
273        // Collections.unmodifiableMap.entrySet() is changed such that it
274        // no longer calls entrySet() on the backing Map.  (Provider's
275        // entrySet implementation depends on this "implementation detail",
276        // which is unlikely to change.
277        if (entrySetCallCount != 2)
278            throw new RuntimeException("Internal error.");
279
280        return entrySet;
281    }
282
283    /**
284     * Returns an unmodifiable Set view of the property keys contained in
285     * this provider.
286     *
287     * @since 1.2
288     */
289    public Set<Object> keySet() {
290        checkInitialized();
291        return Collections.unmodifiableSet(super.keySet());
292    }
293
294    /**
295     * Returns an unmodifiable Collection view of the property values
296     * contained in this provider.
297     *
298     * @since 1.2
299     */
300    public Collection<Object> values() {
301        checkInitialized();
302        return Collections.unmodifiableCollection(super.values());
303    }
304
305    /**
306     * Sets the <code>key</code> property to have the specified
307     * <code>value</code>.
308     *
309     * <p>First, if there is a security manager, its
310     * <code>checkSecurityAccess</code> method is called with the string
311     * <code>"putProviderProperty."+name</code>, where <code>name</code> is the
312     * provider name, to see if it's ok to set this provider's property values.
313     * If the default implementation of <code>checkSecurityAccess</code>
314     * is used (that is, that method is not overriden), then this results in
315     * a call to the security manager's <code>checkPermission</code> method
316     * with a <code>SecurityPermission("putProviderProperty."+name)</code>
317     * permission.
318     *
319     * @param key the property key.
320     *
321     * @param value the property value.
322     *
323     * @return the previous value of the specified property
324     * (<code>key</code>), or null if it did not have one.
325     *
326     * @throws  SecurityException
327     *          if a security manager exists and its <code>{@link
328     *          java.lang.SecurityManager#checkSecurityAccess}</code> method
329     *          denies access to set property values.
330     *
331     * @since 1.2
332     */
333    public synchronized Object put(Object key, Object value) {
334        check("putProviderProperty."+name);
335        if (debug != null) {
336            debug.println("Set " + name + " provider property [" +
337                          key + "/" + value +"]");
338        }
339        return implPut(key, value);
340    }
341
342    /**
343     * Removes the <code>key</code> property (and its corresponding
344     * <code>value</code>).
345     *
346     * <p>First, if there is a security manager, its
347     * <code>checkSecurityAccess</code> method is called with the string
348     * <code>"removeProviderProperty."+name</code>, where <code>name</code> is
349     * the provider name, to see if it's ok to remove this provider's
350     * properties. If the default implementation of
351     * <code>checkSecurityAccess</code> is used (that is, that method is not
352     * overriden), then this results in a call to the security manager's
353     * <code>checkPermission</code> method with a
354     * <code>SecurityPermission("removeProviderProperty."+name)</code>
355     * permission.
356     *
357     * @param key the key for the property to be removed.
358     *
359     * @return the value to which the key had been mapped,
360     * or null if the key did not have a mapping.
361     *
362     * @throws  SecurityException
363     *          if a security manager exists and its <code>{@link
364     *          java.lang.SecurityManager#checkSecurityAccess}</code> method
365     *          denies access to remove this provider's properties.
366     *
367     * @since 1.2
368     */
369    public synchronized Object remove(Object key) {
370        check("removeProviderProperty."+name);
371        if (debug != null) {
372            debug.println("Remove " + name + " provider property " + key);
373        }
374        return implRemove(key);
375    }
376
377    // let javadoc show doc from superclass
378    public Object get(Object key) {
379        checkInitialized();
380        return super.get(key);
381    }
382
383    /**
384     * @since 1.8
385     */
386    @Override
387    public synchronized void forEach(BiConsumer<? super Object, ? super Object> action) {
388        checkInitialized();
389        super.forEach(action);
390    }
391
392    // let javadoc show doc from superclass
393    public Enumeration<Object> keys() {
394        checkInitialized();
395        return super.keys();
396    }
397
398    // let javadoc show doc from superclass
399    public Enumeration<Object> elements() {
400        checkInitialized();
401        return super.elements();
402    }
403
404    // let javadoc show doc from superclass
405    public String getProperty(String key) {
406        checkInitialized();
407        return super.getProperty(key);
408    }
409
410    private void checkInitialized() {
411        if (!initialized) {
412            throw new IllegalStateException();
413        }
414    }
415
416    private void check(String directive) {
417        checkInitialized();
418        SecurityManager security = System.getSecurityManager();
419        if (security != null) {
420            security.checkSecurityAccess(directive);
421        }
422    }
423
424    // legacy properties changed since last call to any services method?
425    private transient boolean legacyChanged;
426    // serviceMap changed since last call to getServices()
427    private transient boolean servicesChanged;
428
429    // Map<String,String>
430    private transient Map<String,String> legacyStrings;
431
432    // Map<ServiceKey,Service>
433    // used for services added via putService(), initialized on demand
434    private transient Map<ServiceKey,Service> serviceMap;
435
436    // Map<ServiceKey,Service>
437    // used for services added via legacy methods, init on demand
438    private transient Map<ServiceKey,Service> legacyMap;
439
440    // Set<Service>
441    // Unmodifiable set of all services. Initialized on demand.
442    private transient Set<Service> serviceSet;
443
444    // register the id attributes for this provider
445    // this is to ensure that equals() and hashCode() do not incorrectly
446    // report to different provider objects as the same
447    private void putId() {
448        // note: name and info may be null
449        super.put("Provider.id name", String.valueOf(name));
450        super.put("Provider.id version", String.valueOf(version));
451        super.put("Provider.id info", String.valueOf(info));
452        super.put("Provider.id className", this.getClass().getName());
453    }
454
455    private void readObject(ObjectInputStream in)
456                throws IOException, ClassNotFoundException {
457        registered = false;
458        Map<Object,Object> copy = new HashMap<>();
459        for (Map.Entry<Object,Object> entry : super.entrySet()) {
460            copy.put(entry.getKey(), entry.getValue());
461        }
462        defaults = null;
463        in.defaultReadObject();
464        implClear();
465        initialized = true;
466        putAll(copy);
467    }
468
469    /**
470     * Copies all of the mappings from the specified Map to this provider.
471     * Internal method to be called AFTER the security check has been
472     * performed.
473     */
474    private void implPutAll(Map t) {
475        for (Map.Entry e : ((Map<?,?>)t).entrySet()) {
476            implPut(e.getKey(), e.getValue());
477        }
478        if (registered) {
479            Security.increaseVersion();
480        }
481    }
482
483    private Object implRemove(Object key) {
484        if (registered) {
485            Security.increaseVersion();
486        }
487        if (key instanceof String) {
488            String keyString = (String)key;
489            if (keyString.startsWith("Provider.")) {
490                return null;
491            }
492            legacyChanged = true;
493            if (legacyStrings == null) {
494                legacyStrings = new LinkedHashMap<String,String>();
495            }
496            legacyStrings.remove(keyString);
497        }
498        return super.remove(key);
499    }
500
501    private Object implPut(Object key, Object value) {
502        if ((key instanceof String) && (value instanceof String)) {
503            String keyString = (String)key;
504            if (keyString.startsWith("Provider.")) {
505                return null;
506            }
507            if (registered) {
508                Security.increaseVersion();
509            }
510            legacyChanged = true;
511            if (legacyStrings == null) {
512                legacyStrings = new LinkedHashMap<String,String>();
513            }
514            legacyStrings.put(keyString, (String)value);
515        }
516        return super.put(key, value);
517    }
518
519    private void implClear() {
520        if (legacyStrings != null) {
521            legacyStrings.clear();
522        }
523        if (legacyMap != null) {
524            legacyMap.clear();
525        }
526        if (serviceMap != null) {
527            serviceMap.clear();
528        }
529        legacyChanged = false;
530        servicesChanged = false;
531        serviceSet = null;
532        super.clear();
533        putId();
534        if (registered) {
535          Security.increaseVersion();
536        }
537    }
538
539    // used as key in the serviceMap and legacyMap HashMaps
540    private static class ServiceKey {
541        private final String type;
542        private final String algorithm;
543        private final String originalAlgorithm;
544        private ServiceKey(String type, String algorithm, boolean intern) {
545            this.type = type;
546            this.originalAlgorithm = algorithm;
547            algorithm = algorithm.toUpperCase(ENGLISH);
548            this.algorithm = intern ? algorithm.intern() : algorithm;
549        }
550        public int hashCode() {
551            return type.hashCode() + algorithm.hashCode();
552        }
553        public boolean equals(Object obj) {
554            if (this == obj) {
555                return true;
556            }
557            if (obj instanceof ServiceKey == false) {
558                return false;
559            }
560            ServiceKey other = (ServiceKey)obj;
561            return this.type.equals(other.type)
562                && this.algorithm.equals(other.algorithm);
563        }
564        boolean matches(String type, String algorithm) {
565            return (this.type == type) && (this.originalAlgorithm == algorithm);
566        }
567    }
568
569    /**
570     * Ensure all the legacy String properties are fully parsed into
571     * service objects.
572     */
573    private void ensureLegacyParsed() {
574        if ((legacyChanged == false) || (legacyStrings == null)) {
575            return;
576        }
577        serviceSet = null;
578        if (legacyMap == null) {
579            legacyMap = new LinkedHashMap<ServiceKey,Service>();
580        } else {
581            legacyMap.clear();
582        }
583        for (Map.Entry<String,String> entry : legacyStrings.entrySet()) {
584            parseLegacyPut(entry.getKey(), entry.getValue());
585        }
586        removeInvalidServices(legacyMap);
587        legacyChanged = false;
588    }
589
590    /**
591     * Remove all invalid services from the Map. Invalid services can only
592     * occur if the legacy properties are inconsistent or incomplete.
593     */
594    private void removeInvalidServices(Map<ServiceKey,Service> map) {
595        for (Iterator t = map.entrySet().iterator(); t.hasNext(); ) {
596            Map.Entry entry = (Map.Entry)t.next();
597            Service s = (Service)entry.getValue();
598            if (s.isValid() == false) {
599                t.remove();
600            }
601        }
602    }
603
604    private String[] getTypeAndAlgorithm(String key) {
605        int i = key.indexOf(".");
606        if (i < 1) {
607            if (debug != null) {
608                debug.println("Ignoring invalid entry in provider "
609                        + name + ":" + key);
610            }
611            return null;
612        }
613        String type = key.substring(0, i);
614        String alg = key.substring(i + 1);
615        return new String[] {type, alg};
616    }
617
618    private final static String ALIAS_PREFIX = "Alg.Alias.";
619    private final static String ALIAS_PREFIX_LOWER = "alg.alias.";
620    private final static int ALIAS_LENGTH = ALIAS_PREFIX.length();
621
622    private void parseLegacyPut(String name, String value) {
623        if (name.toLowerCase(ENGLISH).startsWith(ALIAS_PREFIX_LOWER)) {
624            // e.g. put("Alg.Alias.MessageDigest.SHA", "SHA-1");
625            // aliasKey ~ MessageDigest.SHA
626            String stdAlg = value;
627            String aliasKey = name.substring(ALIAS_LENGTH);
628            String[] typeAndAlg = getTypeAndAlgorithm(aliasKey);
629            if (typeAndAlg == null) {
630                return;
631            }
632            String type = getEngineName(typeAndAlg[0]);
633            String aliasAlg = typeAndAlg[1].intern();
634            ServiceKey key = new ServiceKey(type, stdAlg, true);
635            Service s = legacyMap.get(key);
636            if (s == null) {
637                s = new Service(this);
638                s.type = type;
639                s.algorithm = stdAlg;
640                legacyMap.put(key, s);
641            }
642            legacyMap.put(new ServiceKey(type, aliasAlg, true), s);
643            s.addAlias(aliasAlg);
644        } else {
645            String[] typeAndAlg = getTypeAndAlgorithm(name);
646            if (typeAndAlg == null) {
647                return;
648            }
649            int i = typeAndAlg[1].indexOf(' ');
650            if (i == -1) {
651                // e.g. put("MessageDigest.SHA-1", "sun.security.provider.SHA");
652                String type = getEngineName(typeAndAlg[0]);
653                String stdAlg = typeAndAlg[1].intern();
654                String className = value;
655                ServiceKey key = new ServiceKey(type, stdAlg, true);
656                Service s = legacyMap.get(key);
657                if (s == null) {
658                    s = new Service(this);
659                    s.type = type;
660                    s.algorithm = stdAlg;
661                    legacyMap.put(key, s);
662                }
663                s.className = className;
664            } else { // attribute
665                // e.g. put("MessageDigest.SHA-1 ImplementedIn", "Software");
666                String attributeValue = value;
667                String type = getEngineName(typeAndAlg[0]);
668                String attributeString = typeAndAlg[1];
669                String stdAlg = attributeString.substring(0, i).intern();
670                String attributeName = attributeString.substring(i + 1);
671                // kill additional spaces
672                while (attributeName.startsWith(" ")) {
673                    attributeName = attributeName.substring(1);
674                }
675                attributeName = attributeName.intern();
676                ServiceKey key = new ServiceKey(type, stdAlg, true);
677                Service s = legacyMap.get(key);
678                if (s == null) {
679                    s = new Service(this);
680                    s.type = type;
681                    s.algorithm = stdAlg;
682                    legacyMap.put(key, s);
683                }
684                s.addAttribute(attributeName, attributeValue);
685            }
686        }
687    }
688
689    /**
690     * Get the service describing this Provider's implementation of the
691     * specified type of this algorithm or alias. If no such
692     * implementation exists, this method returns null. If there are two
693     * matching services, one added to this provider using
694     * {@link #putService putService()} and one added via {@link #put put()},
695     * the service added via {@link #putService putService()} is returned.
696     *
697     * @param type the type of {@link Service service} requested
698     * (for example, <code>MessageDigest</code>)
699     * @param algorithm the case insensitive algorithm name (or alternate
700     * alias) of the service requested (for example, <code>SHA-1</code>)
701     *
702     * @return the service describing this Provider's matching service
703     * or null if no such service exists
704     *
705     * @throws NullPointerException if type or algorithm is null
706     *
707     * @since 1.5
708     */
709    public synchronized Service getService(String type, String algorithm) {
710        checkInitialized();
711        // avoid allocating a new key object if possible
712        ServiceKey key = previousKey;
713        if (key.matches(type, algorithm) == false) {
714            key = new ServiceKey(type, algorithm, false);
715            previousKey = key;
716        }
717        if (serviceMap != null) {
718            Service service = serviceMap.get(key);
719            if (service != null) {
720                return service;
721            }
722        }
723        ensureLegacyParsed();
724        return (legacyMap != null) ? legacyMap.get(key) : null;
725    }
726
727    // ServiceKey from previous getService() call
728    // by re-using it if possible we avoid allocating a new object
729    // and the toUpperCase() call.
730    // re-use will occur e.g. as the framework traverses the provider
731    // list and queries each provider with the same values until it finds
732    // a matching service
733    private static volatile ServiceKey previousKey =
734                                            new ServiceKey("", "", false);
735
736    /**
737     * Get an unmodifiable Set of all services supported by
738     * this Provider.
739     *
740     * @return an unmodifiable Set of all services supported by
741     * this Provider
742     *
743     * @since 1.5
744     */
745    public synchronized Set<Service> getServices() {
746        checkInitialized();
747        if (legacyChanged || servicesChanged) {
748            serviceSet = null;
749        }
750        if (serviceSet == null) {
751            ensureLegacyParsed();
752            Set<Service> set = new LinkedHashSet<>();
753            if (serviceMap != null) {
754                set.addAll(serviceMap.values());
755            }
756            if (legacyMap != null) {
757                set.addAll(legacyMap.values());
758            }
759            serviceSet = Collections.unmodifiableSet(set);
760            servicesChanged = false;
761        }
762        return serviceSet;
763    }
764
765    /**
766     * Add a service. If a service of the same type with the same algorithm
767     * name exists and it was added using {@link #putService putService()},
768     * it is replaced by the new service.
769     * This method also places information about this service
770     * in the provider's Hashtable values in the format described in the
771     * <a href="../../../technotes/guides/security/crypto/CryptoSpec.html">
772     * Java Cryptography Architecture API Specification &amp; Reference </a>.
773     *
774     * <p>Also, if there is a security manager, its
775     * <code>checkSecurityAccess</code> method is called with the string
776     * <code>"putProviderProperty."+name</code>, where <code>name</code> is
777     * the provider name, to see if it's ok to set this provider's property
778     * values. If the default implementation of <code>checkSecurityAccess</code>
779     * is used (that is, that method is not overriden), then this results in
780     * a call to the security manager's <code>checkPermission</code> method with
781     * a <code>SecurityPermission("putProviderProperty."+name)</code>
782     * permission.
783     *
784     * @param s the Service to add
785     *
786     * @throws SecurityException
787     *      if a security manager exists and its <code>{@link
788     *      java.lang.SecurityManager#checkSecurityAccess}</code> method denies
789     *      access to set property values.
790     * @throws NullPointerException if s is null
791     *
792     * @since 1.5
793     */
794    protected synchronized void putService(Service s) {
795        check("putProviderProperty." + name);
796        if (debug != null) {
797            debug.println(name + ".putService(): " + s);
798        }
799        if (s == null) {
800            throw new NullPointerException();
801        }
802        if (s.getProvider() != this) {
803            throw new IllegalArgumentException
804                    ("service.getProvider() must match this Provider object");
805        }
806        if (serviceMap == null) {
807            serviceMap = new LinkedHashMap<ServiceKey,Service>();
808        }
809        servicesChanged = true;
810        String type = s.getType();
811        String algorithm = s.getAlgorithm();
812        ServiceKey key = new ServiceKey(type, algorithm, true);
813        // remove existing service
814        implRemoveService(serviceMap.get(key));
815        serviceMap.put(key, s);
816        for (String alias : s.getAliases()) {
817            serviceMap.put(new ServiceKey(type, alias, true), s);
818        }
819        putPropertyStrings(s);
820    }
821
822    /**
823     * Put the string properties for this Service in this Provider's
824     * Hashtable.
825     */
826    private void putPropertyStrings(Service s) {
827        String type = s.getType();
828        String algorithm = s.getAlgorithm();
829        // use super() to avoid permission check and other processing
830        super.put(type + "." + algorithm, s.getClassName());
831        for (String alias : s.getAliases()) {
832            super.put(ALIAS_PREFIX + type + "." + alias, algorithm);
833        }
834        for (Map.Entry<UString,String> entry : s.attributes.entrySet()) {
835            String key = type + "." + algorithm + " " + entry.getKey();
836            super.put(key, entry.getValue());
837        }
838        if (registered) {
839            Security.increaseVersion();
840        }
841    }
842
843    /**
844     * Remove the string properties for this Service from this Provider's
845     * Hashtable.
846     */
847    private void removePropertyStrings(Service s) {
848        String type = s.getType();
849        String algorithm = s.getAlgorithm();
850        // use super() to avoid permission check and other processing
851        super.remove(type + "." + algorithm);
852        for (String alias : s.getAliases()) {
853            super.remove(ALIAS_PREFIX + type + "." + alias);
854        }
855        for (Map.Entry<UString,String> entry : s.attributes.entrySet()) {
856            String key = type + "." + algorithm + " " + entry.getKey();
857            super.remove(key);
858        }
859        if (registered) {
860          Security.increaseVersion();
861        }
862    }
863
864    /**
865     * Remove a service previously added using
866     * {@link #putService putService()}. The specified service is removed from
867     * this provider. It will no longer be returned by
868     * {@link #getService getService()} and its information will be removed
869     * from this provider's Hashtable.
870     *
871     * <p>Also, if there is a security manager, its
872     * <code>checkSecurityAccess</code> method is called with the string
873     * <code>"removeProviderProperty."+name</code>, where <code>name</code> is
874     * the provider name, to see if it's ok to remove this provider's
875     * properties. If the default implementation of
876     * <code>checkSecurityAccess</code> is used (that is, that method is not
877     * overriden), then this results in a call to the security manager's
878     * <code>checkPermission</code> method with a
879     * <code>SecurityPermission("removeProviderProperty."+name)</code>
880     * permission.
881     *
882     * @param s the Service to be removed
883     *
884     * @throws  SecurityException
885     *          if a security manager exists and its <code>{@link
886     *          java.lang.SecurityManager#checkSecurityAccess}</code> method denies
887     *          access to remove this provider's properties.
888     * @throws NullPointerException if s is null
889     *
890     * @since 1.5
891     */
892    protected synchronized void removeService(Service s) {
893        check("removeProviderProperty." + name);
894        if (debug != null) {
895            debug.println(name + ".removeService(): " + s);
896        }
897        if (s == null) {
898            throw new NullPointerException();
899        }
900        implRemoveService(s);
901    }
902
903    private void implRemoveService(Service s) {
904        if ((s == null) || (serviceMap == null)) {
905            return;
906        }
907        String type = s.getType();
908        String algorithm = s.getAlgorithm();
909        ServiceKey key = new ServiceKey(type, algorithm, false);
910        Service oldService = serviceMap.get(key);
911        if (s != oldService) {
912            return;
913        }
914        servicesChanged = true;
915        serviceMap.remove(key);
916        for (String alias : s.getAliases()) {
917            serviceMap.remove(new ServiceKey(type, alias, false));
918        }
919        removePropertyStrings(s);
920    }
921
922    // Wrapped String that behaves in a case insensitive way for equals/hashCode
923    private static class UString {
924        final String string;
925        final String lowerString;
926
927        UString(String s) {
928            this.string = s;
929            this.lowerString = s.toLowerCase(ENGLISH);
930        }
931
932        public int hashCode() {
933            return lowerString.hashCode();
934        }
935
936        public boolean equals(Object obj) {
937            if (this == obj) {
938                return true;
939            }
940            if (obj instanceof UString == false) {
941                return false;
942            }
943            UString other = (UString)obj;
944            return lowerString.equals(other.lowerString);
945        }
946
947        public String toString() {
948            return string;
949        }
950    }
951
952    // describe relevant properties of a type of engine
953    private static class EngineDescription {
954        final String name;
955        final boolean supportsParameter;
956        final String constructorParameterClassName;
957        private volatile Class constructorParameterClass;
958
959        EngineDescription(String name, boolean sp, String paramName) {
960            this.name = name;
961            this.supportsParameter = sp;
962            this.constructorParameterClassName = paramName;
963        }
964        Class getConstructorParameterClass() throws ClassNotFoundException {
965            Class clazz = constructorParameterClass;
966            if (clazz == null) {
967                clazz = Class.forName(constructorParameterClassName);
968                constructorParameterClass = clazz;
969            }
970            return clazz;
971        }
972    }
973
974    // built in knowledge of the engine types shipped as part of the JDK
975    private static final Map<String,EngineDescription> knownEngines;
976
977    private static void addEngine(String name, boolean sp, String paramName) {
978        EngineDescription ed = new EngineDescription(name, sp, paramName);
979        // also index by canonical name to avoid toLowerCase() for some lookups
980        knownEngines.put(name.toLowerCase(ENGLISH), ed);
981        knownEngines.put(name, ed);
982    }
983
984    static {
985        knownEngines = new HashMap<String,EngineDescription>();
986        // JCA
987        addEngine("AlgorithmParameterGenerator",        false, null);
988        addEngine("AlgorithmParameters",                false, null);
989        addEngine("KeyFactory",                         false, null);
990        addEngine("KeyPairGenerator",                   false, null);
991        addEngine("KeyStore",                           false, null);
992        addEngine("MessageDigest",                      false, null);
993        addEngine("SecureRandom",                       false, null);
994        addEngine("Signature",                          true,  null);
995        addEngine("CertificateFactory",                 false, null);
996        addEngine("CertPathBuilder",                    false, null);
997        addEngine("CertPathValidator",                  false, null);
998        addEngine("CertStore",                          false,
999                            "java.security.cert.CertStoreParameters");
1000        // JCE
1001        addEngine("Cipher",                             true,  null);
1002        addEngine("ExemptionMechanism",                 false, null);
1003        addEngine("Mac",                                true,  null);
1004        addEngine("KeyAgreement",                       true,  null);
1005        addEngine("KeyGenerator",                       false, null);
1006        addEngine("SecretKeyFactory",                   false, null);
1007        // JSSE
1008        addEngine("KeyManagerFactory",                  false, null);
1009        addEngine("SSLContext",                         false, null);
1010        addEngine("TrustManagerFactory",                false, null);
1011        // JGSS
1012        addEngine("GssApiMechanism",                    false, null);
1013        // SASL
1014        addEngine("SaslClientFactory",                  false, null);
1015        addEngine("SaslServerFactory",                  false, null);
1016        // POLICY
1017        addEngine("Policy",                             false,
1018                            "java.security.Policy$Parameters");
1019        // CONFIGURATION
1020        addEngine("Configuration",                      false,
1021                            "javax.security.auth.login.Configuration$Parameters");
1022        // XML DSig
1023        addEngine("XMLSignatureFactory",                false, null);
1024        addEngine("KeyInfoFactory",                     false, null);
1025        addEngine("TransformService",                   false, null);
1026        // Smart Card I/O
1027        addEngine("TerminalFactory",                    false,
1028                            "java.lang.Object");
1029    }
1030
1031    // get the "standard" (mixed-case) engine name for arbitary case engine name
1032    // if there is no known engine by that name, return s
1033    private static String getEngineName(String s) {
1034        // try original case first, usually correct
1035        EngineDescription e = knownEngines.get(s);
1036        if (e == null) {
1037            e = knownEngines.get(s.toLowerCase(ENGLISH));
1038        }
1039        return (e == null) ? s : e.name;
1040    }
1041
1042    /**
1043     * The description of a security service. It encapsulates the properties
1044     * of a service and contains a factory method to obtain new implementation
1045     * instances of this service.
1046     *
1047     * <p>Each service has a provider that offers the service, a type,
1048     * an algorithm name, and the name of the class that implements the
1049     * service. Optionally, it also includes a list of alternate algorithm
1050     * names for this service (aliases) and attributes, which are a map of
1051     * (name, value) String pairs.
1052     *
1053     * <p>This class defines the methods {@link #supportsParameter
1054     * supportsParameter()} and {@link #newInstance newInstance()}
1055     * which are used by the Java security framework when it searches for
1056     * suitable services and instantes them. The valid arguments to those
1057     * methods depend on the type of service. For the service types defined
1058     * within Java SE, see the
1059     * <a href="../../../technotes/guides/security/crypto/CryptoSpec.html">
1060     * Java Cryptography Architecture API Specification &amp; Reference </a>
1061     * for the valid values.
1062     * Note that components outside of Java SE can define additional types of
1063     * services and their behavior.
1064     *
1065     * <p>Instances of this class are immutable.
1066     *
1067     * @since 1.5
1068     */
1069    public static class Service {
1070
1071        private String type, algorithm, className;
1072        private final Provider provider;
1073        private List<String> aliases;
1074        private Map<UString,String> attributes;
1075
1076        // Reference to the cached implementation Class object
1077        private volatile Reference<Class> classRef;
1078
1079        // flag indicating whether this service has its attributes for
1080        // supportedKeyFormats or supportedKeyClasses set
1081        // if null, the values have not been initialized
1082        // if TRUE, at least one of supportedFormats/Classes is non null
1083        private volatile Boolean hasKeyAttributes;
1084
1085        // supported encoding formats
1086        private String[] supportedFormats;
1087
1088        // names of the supported key (super) classes
1089        private Class[] supportedClasses;
1090
1091        // whether this service has been registered with the Provider
1092        private boolean registered;
1093
1094        private static final Class[] CLASS0 = new Class[0];
1095
1096        // this constructor and these methods are used for parsing
1097        // the legacy string properties.
1098
1099        private Service(Provider provider) {
1100            this.provider = provider;
1101            aliases = Collections.<String>emptyList();
1102            attributes = Collections.<UString,String>emptyMap();
1103        }
1104
1105        private boolean isValid() {
1106            return (type != null) && (algorithm != null) && (className != null);
1107        }
1108
1109        private void addAlias(String alias) {
1110            if (aliases.isEmpty()) {
1111                aliases = new ArrayList<String>(2);
1112            }
1113            aliases.add(alias);
1114        }
1115
1116        void addAttribute(String type, String value) {
1117            if (attributes.isEmpty()) {
1118                attributes = new HashMap<UString,String>(8);
1119            }
1120            attributes.put(new UString(type), value);
1121        }
1122
1123        /**
1124         * Construct a new service.
1125         *
1126         * @param provider the provider that offers this service
1127         * @param type the type of this service
1128         * @param algorithm the algorithm name
1129         * @param className the name of the class implementing this service
1130         * @param aliases List of aliases or null if algorithm has no aliases
1131         * @param attributes Map of attributes or null if this implementation
1132         *                   has no attributes
1133         *
1134         * @throws NullPointerException if provider, type, algorithm, or
1135         * className is null
1136         */
1137        public Service(Provider provider, String type, String algorithm,
1138                String className, List<String> aliases,
1139                Map<String,String> attributes) {
1140            if ((provider == null) || (type == null) ||
1141                    (algorithm == null) || (className == null)) {
1142                throw new NullPointerException();
1143            }
1144            this.provider = provider;
1145            this.type = getEngineName(type);
1146            this.algorithm = algorithm;
1147            this.className = className;
1148            if (aliases == null) {
1149                this.aliases = Collections.<String>emptyList();
1150            } else {
1151                this.aliases = new ArrayList<String>(aliases);
1152            }
1153            if (attributes == null) {
1154                this.attributes = Collections.<UString,String>emptyMap();
1155            } else {
1156                this.attributes = new HashMap<UString,String>();
1157                for (Map.Entry<String,String> entry : attributes.entrySet()) {
1158                    this.attributes.put(new UString(entry.getKey()), entry.getValue());
1159                }
1160            }
1161        }
1162
1163        /**
1164         * Get the type of this service. For example, <code>MessageDigest</code>.
1165         *
1166         * @return the type of this service
1167         */
1168        public final String getType() {
1169            return type;
1170        }
1171
1172        /**
1173         * Return the name of the algorithm of this service. For example,
1174         * <code>SHA-1</code>.
1175         *
1176         * @return the algorithm of this service
1177         */
1178        public final String getAlgorithm() {
1179            return algorithm;
1180        }
1181
1182        /**
1183         * Return the Provider of this service.
1184         *
1185         * @return the Provider of this service
1186         */
1187        public final Provider getProvider() {
1188            return provider;
1189        }
1190
1191        /**
1192         * Return the name of the class implementing this service.
1193         *
1194         * @return the name of the class implementing this service
1195         */
1196        public final String getClassName() {
1197            return className;
1198        }
1199
1200        // internal only
1201        private final List<String> getAliases() {
1202            return aliases;
1203        }
1204
1205        /**
1206         * Return the value of the specified attribute or null if this
1207         * attribute is not set for this Service.
1208         *
1209         * @param name the name of the requested attribute
1210         *
1211         * @return the value of the specified attribute or null if the
1212         *         attribute is not present
1213         *
1214         * @throws NullPointerException if name is null
1215         */
1216        public final String getAttribute(String name) {
1217            if (name == null) {
1218                throw new NullPointerException();
1219            }
1220            return attributes.get(new UString(name));
1221        }
1222
1223        /**
1224         * Return a new instance of the implementation described by this
1225         * service. The security provider framework uses this method to
1226         * construct implementations. Applications will typically not need
1227         * to call it.
1228         *
1229         * <p>The default implementation uses reflection to invoke the
1230         * standard constructor for this type of service.
1231         * Security providers can override this method to implement
1232         * instantiation in a different way.
1233         * For details and the values of constructorParameter that are
1234         * valid for the various types of services see the
1235         * <a href="../../../technotes/guides/security/crypto/CryptoSpec.html">
1236         * Java Cryptography Architecture API Specification &amp;
1237         * Reference</a>.
1238         *
1239         * @param constructorParameter the value to pass to the constructor,
1240         * or null if this type of service does not use a constructorParameter.
1241         *
1242         * @return a new implementation of this service
1243         *
1244         * @throws InvalidParameterException if the value of
1245         * constructorParameter is invalid for this type of service.
1246         * @throws NoSuchAlgorithmException if instantation failed for
1247         * any other reason.
1248         */
1249        public Object newInstance(Object constructorParameter)
1250                throws NoSuchAlgorithmException {
1251            if (registered == false) {
1252                if (provider.getService(type, algorithm) != this) {
1253                    throw new NoSuchAlgorithmException
1254                        ("Service not registered with Provider "
1255                        + provider.getName() + ": " + this);
1256                }
1257                registered = true;
1258            }
1259            try {
1260                EngineDescription cap = knownEngines.get(type);
1261                if (cap == null) {
1262                    // unknown engine type, use generic code
1263                    // this is the code path future for non-core
1264                    // optional packages
1265                    return newInstanceGeneric(constructorParameter);
1266                }
1267                if (cap.constructorParameterClassName == null) {
1268                    if (constructorParameter != null) {
1269                        throw new InvalidParameterException
1270                            ("constructorParameter not used with " + type
1271                            + " engines");
1272                    }
1273                    Class clazz = getImplClass();
1274                    return clazz.newInstance();
1275                } else {
1276                    Class paramClass = cap.getConstructorParameterClass();
1277                    if (constructorParameter != null) {
1278                        Class argClass = constructorParameter.getClass();
1279                        if (paramClass.isAssignableFrom(argClass) == false) {
1280                            throw new InvalidParameterException
1281                            ("constructorParameter must be instanceof "
1282                            + cap.constructorParameterClassName.replace('$', '.')
1283                            + " for engine type " + type);
1284                        }
1285                    }
1286                    Class clazz = getImplClass();
1287                    Constructor cons = clazz.getConstructor(paramClass);
1288                    return cons.newInstance(constructorParameter);
1289                }
1290            } catch (NoSuchAlgorithmException e) {
1291                throw e;
1292            } catch (InvocationTargetException e) {
1293                throw new NoSuchAlgorithmException
1294                    ("Error constructing implementation (algorithm: "
1295                    + algorithm + ", provider: " + provider.getName()
1296                    + ", class: " + className + ")", e.getCause());
1297            } catch (Exception e) {
1298                throw new NoSuchAlgorithmException
1299                    ("Error constructing implementation (algorithm: "
1300                    + algorithm + ", provider: " + provider.getName()
1301                    + ", class: " + className + ")", e);
1302            }
1303        }
1304
1305        // return the implementation Class object for this service
1306        private Class getImplClass() throws NoSuchAlgorithmException {
1307            try {
1308                Reference<Class> ref = classRef;
1309                Class clazz = (ref == null) ? null : ref.get();
1310                if (clazz == null) {
1311                    ClassLoader cl = provider.getClass().getClassLoader();
1312                    if (cl == null) {
1313                        clazz = Class.forName(className);
1314                    } else {
1315                        clazz = cl.loadClass(className);
1316                    }
1317                    classRef = new WeakReference<Class>(clazz);
1318                }
1319                return clazz;
1320            } catch (ClassNotFoundException e) {
1321                throw new NoSuchAlgorithmException
1322                    ("class configured for " + type + "(provider: " +
1323                    provider.getName() + ")" + "cannot be found.", e);
1324            }
1325        }
1326
1327        /**
1328         * Generic code path for unknown engine types. Call the
1329         * no-args constructor if constructorParameter is null, otherwise
1330         * use the first matching constructor.
1331         */
1332        private Object newInstanceGeneric(Object constructorParameter)
1333                throws Exception {
1334            Class clazz = getImplClass();
1335            if (constructorParameter == null) {
1336                Object o = clazz.newInstance();
1337                return o;
1338            }
1339            Class argClass = constructorParameter.getClass();
1340            Constructor[] cons = clazz.getConstructors();
1341            // find first public constructor that can take the
1342            // argument as parameter
1343            for (int i = 0; i < cons.length; i++) {
1344                Constructor con = cons[i];
1345                Class[] paramTypes = con.getParameterTypes();
1346                if (paramTypes.length != 1) {
1347                    continue;
1348                }
1349                if (paramTypes[0].isAssignableFrom(argClass) == false) {
1350                    continue;
1351                }
1352                Object o = con.newInstance(new Object[] {constructorParameter});
1353                return o;
1354            }
1355            throw new NoSuchAlgorithmException("No constructor matching "
1356                + argClass.getName() + " found in class " + className);
1357        }
1358
1359        /**
1360         * Test whether this Service can use the specified parameter.
1361         * Returns false if this service cannot use the parameter. Returns
1362         * true if this service can use the parameter, if a fast test is
1363         * infeasible, or if the status is unknown.
1364         *
1365         * <p>The security provider framework uses this method with
1366         * some types of services to quickly exclude non-matching
1367         * implementations for consideration.
1368         * Applications will typically not need to call it.
1369         *
1370         * <p>For details and the values of parameter that are valid for the
1371         * various types of services see the top of this class and the
1372         * <a href="../../../technotes/guides/security/crypto/CryptoSpec.html">
1373         * Java Cryptography Architecture API Specification &amp;
1374         * Reference</a>.
1375         * Security providers can override it to implement their own test.
1376         *
1377         * @param parameter the parameter to test
1378         *
1379         * @return false if this this service cannot use the specified
1380         * parameter; true if it can possibly use the parameter
1381         *
1382         * @throws InvalidParameterException if the value of parameter is
1383         * invalid for this type of service or if this method cannot be
1384         * used with this type of service
1385         */
1386        public boolean supportsParameter(Object parameter) {
1387            EngineDescription cap = knownEngines.get(type);
1388            if (cap == null) {
1389                // unknown engine type, return true by default
1390                return true;
1391            }
1392            if (cap.supportsParameter == false) {
1393                throw new InvalidParameterException("supportsParameter() not "
1394                    + "used with " + type + " engines");
1395            }
1396            // allow null for keys without attributes for compatibility
1397            if ((parameter != null) && (parameter instanceof Key == false)) {
1398                throw new InvalidParameterException
1399                    ("Parameter must be instanceof Key for engine " + type);
1400            }
1401            if (hasKeyAttributes() == false) {
1402                return true;
1403            }
1404            if (parameter == null) {
1405                return false;
1406            }
1407            Key key = (Key)parameter;
1408            if (supportsKeyFormat(key)) {
1409                return true;
1410            }
1411            if (supportsKeyClass(key)) {
1412                return true;
1413            }
1414            return false;
1415        }
1416
1417        /**
1418         * Return whether this service has its Supported* properties for
1419         * keys defined. Parses the attributes if not yet initialized.
1420         */
1421        private boolean hasKeyAttributes() {
1422            Boolean b = hasKeyAttributes;
1423            if (b == null) {
1424                synchronized (this) {
1425                    String s;
1426                    s = getAttribute("SupportedKeyFormats");
1427                    if (s != null) {
1428                        supportedFormats = s.split("\\|");
1429                    }
1430                    s = getAttribute("SupportedKeyClasses");
1431                    if (s != null) {
1432                        String[] classNames = s.split("\\|");
1433                        List<Class> classList =
1434                            new ArrayList<>(classNames.length);
1435                        for (String className : classNames) {
1436                            Class clazz = getKeyClass(className);
1437                            if (clazz != null) {
1438                                classList.add(clazz);
1439                            }
1440                        }
1441                        supportedClasses = classList.toArray(CLASS0);
1442                    }
1443                    boolean bool = (supportedFormats != null)
1444                        || (supportedClasses != null);
1445                    b = Boolean.valueOf(bool);
1446                    hasKeyAttributes = b;
1447                }
1448            }
1449            return b.booleanValue();
1450        }
1451
1452        // get the key class object of the specified name
1453        private Class getKeyClass(String name) {
1454            try {
1455                return Class.forName(name);
1456            } catch (ClassNotFoundException e) {
1457                // ignore
1458            }
1459            try {
1460                ClassLoader cl = provider.getClass().getClassLoader();
1461                if (cl != null) {
1462                    return cl.loadClass(name);
1463                }
1464            } catch (ClassNotFoundException e) {
1465                // ignore
1466            }
1467            return null;
1468        }
1469
1470        private boolean supportsKeyFormat(Key key) {
1471            if (supportedFormats == null) {
1472                return false;
1473            }
1474            String format = key.getFormat();
1475            if (format == null) {
1476                return false;
1477            }
1478            for (String supportedFormat : supportedFormats) {
1479                if (supportedFormat.equals(format)) {
1480                    return true;
1481                }
1482            }
1483            return false;
1484        }
1485
1486        private boolean supportsKeyClass(Key key) {
1487            if (supportedClasses == null) {
1488                return false;
1489            }
1490            Class keyClass = key.getClass();
1491            for (Class clazz : supportedClasses) {
1492                if (clazz.isAssignableFrom(keyClass)) {
1493                    return true;
1494                }
1495            }
1496            return false;
1497        }
1498
1499        /**
1500         * Return a String representation of this service.
1501         *
1502         * @return a String representation of this service.
1503         */
1504        public String toString() {
1505            String aString = aliases.isEmpty()
1506                ? "" : "\r\n  aliases: " + aliases.toString();
1507            String attrs = attributes.isEmpty()
1508                ? "" : "\r\n  attributes: " + attributes.toString();
1509            return provider.getName() + ": " + type + "." + algorithm
1510                + " -> " + className + aString + attrs + "\r\n";
1511        }
1512
1513    }
1514
1515    /**
1516     * @hide
1517     */
1518    public void setRegistered() {
1519        registered = true;
1520    }
1521
1522    /**
1523     * @hide
1524     */
1525    public void setUnregistered() {
1526        registered = false;
1527    }
1528
1529    /**
1530     * @hide
1531     */
1532    public boolean isRegistered() {
1533        return registered;
1534    }
1535
1536}
1537