1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17
18package java.security;
19
20import java.io.IOException;
21import java.io.InputStream;
22import java.io.NotActiveException;
23import java.util.ArrayList;
24import java.util.Collection;
25import java.util.Collections;
26import java.util.Enumeration;
27import java.util.HashMap;
28import java.util.HashSet;
29import java.util.Iterator;
30import java.util.LinkedHashMap;
31import java.util.LinkedHashSet;
32import java.util.List;
33import java.util.Locale;
34import java.util.Map;
35import java.util.Properties;
36import java.util.Set;
37import org.apache.harmony.security.fortress.Services;
38
39/**
40 * {@code Provider} is the abstract superclass for all security providers in the
41 * Java security infrastructure.
42 */
43public abstract class Provider extends Properties {
44    private static final long serialVersionUID = -4298000515446427739L;
45
46    private String name;
47
48    private double version;
49
50    // String representation of the provider version number.
51    private transient String versionString;
52
53    private String info;
54
55    //The provider preference order number.
56    // Equals -1 for non registered provider.
57    private transient int providerNumber = -1;
58
59    // Contains "Service.Algorithm" and Provider.Service classes added using
60    // putService()
61    private transient LinkedHashMap<String, Service> serviceTable;
62
63    // Contains "Service.Alias" and Provider.Service classes added using
64    // putService()
65    private transient LinkedHashMap<String, Service> aliasTable;
66
67    // Contains "Service.Algorithm" and Provider.Service classes added using
68    // put()
69    private transient LinkedHashMap<String, Service> propertyServiceTable;
70
71    // Contains "Service.Alias" and Provider.Service classes added using put()
72    private transient LinkedHashMap<String, Service> propertyAliasTable;
73
74    // The properties changed via put()
75    private transient Properties changedProperties;
76
77    // For getService(String type, String algorithm) optimization:
78    // previous result
79    private transient Provider.Service returnedService;
80    // previous parameters
81    private transient String lastAlgorithm;
82    // last name
83    private transient String lastServiceName;
84
85    // For getServices() optimization:
86    private transient Set<Service> lastServicesSet;
87
88    // For getService(String type) optimization:
89    private transient String lastType;
90    // last Service found by type
91    private transient Provider.Service lastServicesByType;
92
93    /**
94     * Constructs a new instance of {@code Provider} with its name, version and
95     * description.
96     *
97     * @param name
98     *            the name of the provider.
99     * @param version
100     *            the version of the provider.
101     * @param info
102     *            a description of the provider.
103     */
104    protected Provider(String name, double version, String info) {
105        this.name = name;
106        this.version = version;
107        this.info = info;
108        versionString = String.valueOf(version);
109        putProviderInfo();
110    }
111
112    /**
113     * Returns the name of this provider.
114     *
115     * @return the name of this provider.
116     */
117    public String getName() {
118        return name;
119    }
120
121    /**
122     * Returns the version number for the services being provided.
123     *
124     * @return the version number for the services being provided.
125     */
126    public double getVersion() {
127        return version;
128    }
129
130    /**
131     * Returns a description of the services being provided.
132     *
133     * @return a description of the services being provided.
134     */
135    public String getInfo() {
136        return info;
137    }
138
139    /**
140     * Returns a string containing a concise, human-readable description of
141     * this {@code Provider} including its name and its version.
142     *
143     * @return a printable representation for this {@code Provider}.
144     */
145    @Override
146    public String toString() {
147        return name + " version " + version;
148    }
149
150    /**
151     * Clears all properties used to look up services implemented by this
152     * {@code Provider}.
153     */
154    @Override
155    public synchronized void clear() {
156        super.clear();
157        if (serviceTable != null) {
158            serviceTable.clear();
159        }
160        if (propertyServiceTable != null) {
161            propertyServiceTable.clear();
162        }
163        if (aliasTable != null) {
164            aliasTable.clear();
165        }
166        if (propertyAliasTable != null) {
167            propertyAliasTable.clear();
168        }
169        changedProperties = null;
170        putProviderInfo();
171        if (providerNumber != -1) {
172            // if registered then refresh Services
173            Services.setNeedRefresh();
174        }
175        servicesChanged();
176    }
177
178    @Override
179    public synchronized void load(InputStream inStream) throws IOException {
180        Properties tmp = new Properties();
181        tmp.load(inStream);
182        myPutAll(tmp);
183    }
184
185    /**
186     * Copies all from the provided map to this {@code Provider}.
187     * @param t
188     *            the mappings to copy to this provider.
189     */
190    @Override
191    public synchronized void putAll(Map<?,?> t) {
192        myPutAll(t);
193    }
194
195    private void myPutAll(Map<?,?> t) {
196        if (changedProperties == null) {
197            changedProperties = new Properties();
198        }
199        Iterator<? extends Map.Entry<?, ?>> it = t.entrySet().iterator();
200        Object key;
201        Object value;
202        while (it.hasNext()) {
203            Map.Entry<?, ?> entry = it.next();
204            key = entry.getKey();
205            if (key instanceof String && ((String) key).startsWith("Provider.")) {
206                // Provider service type is reserved
207                continue;
208            }
209            value = entry.getValue();
210            super.put(key, value);
211            if (changedProperties.remove(key) == null) {
212                removeFromPropertyServiceTable(key);
213            }
214            changedProperties.put(key, value);
215        }
216        if (providerNumber != -1) {
217            // if registered then refresh Services
218            Services.setNeedRefresh();
219        }
220    }
221
222    @Override
223    public synchronized Set<Map.Entry<Object,Object>> entrySet() {
224        return Collections.unmodifiableSet(super.entrySet());
225    }
226
227    @Override
228    public Set<Object> keySet() {
229        return Collections.unmodifiableSet(super.keySet());
230    }
231
232    @Override
233    public Collection<Object> values() {
234        return Collections.unmodifiableCollection(super.values());
235    }
236
237    /**
238     * Maps the specified {@code key} property name to the specified {@code
239     * value}.
240     *
241     * @param key
242     *            the name of the property.
243     * @param value
244     *            the value of the property.
245     * @return the value that was previously mapped to the specified {@code key}
246     *         ,or {@code null} if it did not have one.
247     */
248    @Override
249    public synchronized Object put(Object key, Object value) {
250        if (key instanceof String && ((String) key).startsWith("Provider.")) {
251            // Provider service type is reserved
252            return null;
253        }
254        if (providerNumber != -1) {
255            // if registered then refresh Services
256            Services.setNeedRefresh();
257        }
258        if (changedProperties != null && changedProperties.remove(key) == null) {
259            removeFromPropertyServiceTable(key);
260        }
261        if (changedProperties == null) {
262            changedProperties = new Properties();
263        }
264        changedProperties.put(key, value);
265        return super.put(key, value);
266    }
267
268    /**
269     * Removes the specified {@code key} and its associated value from this
270     * {@code Provider}.
271     *
272     * @param key
273     *            the name of the property
274     * @return the value that was mapped to the specified {@code key} ,or
275     *         {@code null} if no mapping was present
276     */
277    @Override
278    public synchronized Object remove(Object key) {
279        if (key instanceof String && ((String) key).startsWith("Provider.")) {
280            // Provider service type is reserved
281            return null;
282        }
283        if (providerNumber != -1) {
284            // if registered then refresh Services
285            Services.setNeedRefresh();
286        }
287        if (changedProperties != null && changedProperties.remove(key) == null) {
288            removeFromPropertyServiceTable(key);
289            if (changedProperties.size() == 0) {
290                changedProperties = null;
291            }
292        }
293        return super.remove(key);
294    }
295
296    /**
297     * Returns true if this provider implements the given algorithm. Caller
298     * must specify the cryptographic service and specify constraints via the
299     * attribute name and value.
300     *
301     * @param serv
302     *            Crypto service.
303     * @param alg
304     *            Algorithm or type.
305     * @param attribute
306     *            The attribute name or {@code null}.
307     * @param val
308     *            The attribute value.
309     * @return
310     */
311    boolean implementsAlg(String serv, String alg, String attribute, String val) {
312        String servAlg = serv + "." + alg;
313        String prop = getPropertyIgnoreCase(servAlg);
314        if (prop == null) {
315            alg = getPropertyIgnoreCase("Alg.Alias." + servAlg);
316            if (alg != null) {
317                servAlg = serv + "." + alg;
318                prop = getPropertyIgnoreCase(servAlg);
319            }
320        }
321        if (prop != null) {
322            if (attribute == null) {
323                return true;
324            }
325            return checkAttribute(servAlg, attribute, val);
326        }
327        return false;
328    }
329
330    /**
331     * Returns true if this provider has the same value as is given for the
332     * given attribute
333     */
334    private boolean checkAttribute(String servAlg, String attribute, String val) {
335
336        String attributeValue = getPropertyIgnoreCase(servAlg + ' ' + attribute);
337        if (attributeValue != null) {
338            if (attribute.equalsIgnoreCase("KeySize")) {
339                if (Integer.parseInt(attributeValue) >= Integer.parseInt(val)) {
340                    return true;
341                }
342            } else { // other attributes
343                if (attributeValue.equalsIgnoreCase(val)) {
344                    return true;
345                }
346            }
347        }
348        return false;
349    }
350
351    /**
352     *
353     * Set the provider preference order number.
354     *
355     * @param n
356     */
357    void setProviderNumber(int n) {
358        providerNumber = n;
359    }
360
361    /**
362     *
363     * Get the provider preference order number.
364     *
365     * @return
366     */
367    int getProviderNumber() {
368        return providerNumber;
369    }
370
371    /**
372     * Get the service of the specified type
373     *
374     */
375    synchronized Provider.Service getService(String type) {
376        updatePropertyServiceTable();
377        if (lastServicesByType != null && type.equals(lastType)) {
378            return lastServicesByType;
379        }
380        Provider.Service service;
381        for (Iterator<Service> it = getServices().iterator(); it.hasNext();) {
382            service = it.next();
383            if (type.equals(service.type)) {
384                lastType = type;
385                lastServicesByType = service;
386                return service;
387            }
388        }
389        return null;
390    }
391
392    /**
393     * Returns the service with the specified {@code type} implementing the
394     * specified {@code algorithm}, or {@code null} if no such implementation
395     * exists.
396     * <p>
397     * If two services match the requested type and algorithm, the one added
398     * with the {@link #putService(Service)} is returned (as opposed to the one
399     * added via {@link #put(Object, Object)}.
400     *
401     * @param type
402     *            the type of the service (for example {@code KeyPairGenerator})
403     * @param algorithm
404     *            the algorithm name (case insensitive)
405     * @return the requested service, or {@code null} if no such implementation
406     *         exists
407     */
408    public synchronized Provider.Service getService(String type,
409            String algorithm) {
410        if (type == null) {
411            throw new NullPointerException("type == null");
412        } else if (algorithm == null) {
413            throw new NullPointerException("algorithm == null");
414        }
415
416        if (type.equals(lastServiceName) && algorithm.equalsIgnoreCase(lastAlgorithm)) {
417            return returnedService;
418        }
419
420        String key = key(type, algorithm);
421        Object o = null;
422        if (serviceTable != null) {
423            o = serviceTable.get(key);
424        }
425        if (o == null && aliasTable != null) {
426            o = aliasTable.get(key);
427        }
428        if (o == null) {
429            updatePropertyServiceTable();
430        }
431        if (o == null && propertyServiceTable != null) {
432            o = propertyServiceTable.get(key);
433        }
434        if (o == null && propertyAliasTable != null) {
435            o = propertyAliasTable.get(key);
436        }
437
438        if (o != null) {
439            lastServiceName = type;
440            lastAlgorithm = algorithm;
441            returnedService = (Provider.Service) o;
442            return returnedService;
443        }
444        return null;
445    }
446
447    /**
448     * Returns an unmodifiable {@code Set} of all services registered by this
449     * provider.
450     *
451     * @return an unmodifiable {@code Set} of all services registered by this
452     *         provider
453     */
454    public synchronized Set<Provider.Service> getServices() {
455        updatePropertyServiceTable();
456        if (lastServicesSet != null) {
457            return lastServicesSet;
458        }
459        if (serviceTable != null) {
460            lastServicesSet = new LinkedHashSet<Service>(serviceTable.values());
461        } else {
462            lastServicesSet = new LinkedHashSet<Service>();
463        }
464        if (propertyServiceTable != null) {
465            lastServicesSet.addAll(propertyServiceTable.values());
466        }
467        lastServicesSet = Collections.unmodifiableSet(lastServicesSet);
468        return lastServicesSet;
469    }
470
471    /**
472     * Adds a {@code Service} to this {@code Provider}. If a service with the
473     * same name was registered via this method, it is replace.
474     *
475     * @param s
476     *            the {@code Service} to register
477     */
478    protected synchronized void putService(Provider.Service s) {
479        if (s == null) {
480            throw new NullPointerException("s == null");
481        }
482        if ("Provider".equals(s.getType())) { // Provider service type cannot be added
483            return;
484        }
485        servicesChanged();
486        if (serviceTable == null) {
487            serviceTable = new LinkedHashMap<String, Service>(128);
488        }
489        serviceTable.put(key(s.type, s.algorithm), s);
490        if (s.aliases != null) {
491            if (aliasTable == null) {
492                aliasTable = new LinkedHashMap<String, Service>(256);
493            }
494            for (String alias : s.getAliases()) {
495                aliasTable.put(key(s.type, alias), s);
496            }
497        }
498        serviceInfoToProperties(s);
499    }
500
501    /**
502     * Removes a previously registered {@code Service} from this {@code
503     * Provider}.
504     *
505     * @param s
506     *            the {@code Service} to remove
507     * @throws NullPointerException
508     *             if {@code s} is {@code null}
509     */
510    protected synchronized void removeService(Provider.Service s) {
511        if (s == null) {
512            throw new NullPointerException("s == null");
513        }
514        servicesChanged();
515        if (serviceTable != null) {
516            serviceTable.remove(key(s.type, s.algorithm));
517        }
518        if (aliasTable != null && s.aliases != null) {
519            for (String alias: s.getAliases()) {
520                aliasTable.remove(key(s.type, alias));
521            }
522        }
523        serviceInfoFromProperties(s);
524    }
525
526    /**
527     * Add Service information to the provider's properties.
528     */
529    private void serviceInfoToProperties(Provider.Service s) {
530        super.put(s.type + "." + s.algorithm, s.className);
531        if (s.aliases != null) {
532            for (Iterator<String> i = s.aliases.iterator(); i.hasNext();) {
533                super.put("Alg.Alias." + s.type + "." + i.next(), s.algorithm);
534            }
535        }
536        if (s.attributes != null) {
537            for (Map.Entry<String, String> entry : s.attributes.entrySet()) {
538                super.put(s.type + "." + s.algorithm + " " + entry.getKey(),
539                        entry.getValue());
540            }
541        }
542        if (providerNumber != -1) {
543            // if registered then refresh Services
544            Services.setNeedRefresh();
545        }
546    }
547
548    /**
549     * Remove Service information from the provider's properties.
550     */
551    private void serviceInfoFromProperties(Provider.Service s) {
552        super.remove(s.type + "." + s.algorithm);
553        if (s.aliases != null) {
554            for (Iterator<String> i = s.aliases.iterator(); i.hasNext();) {
555                super.remove("Alg.Alias." + s.type + "." + i.next());
556            }
557        }
558        if (s.attributes != null) {
559            for (Map.Entry<String, String> entry : s.attributes.entrySet()) {
560                super.remove(s.type + "." + s.algorithm + " " + entry.getKey());
561            }
562        }
563        if (providerNumber != -1) {
564            // if registered then refresh Services
565            Services.setNeedRefresh();
566        }
567    }
568
569    // Remove property information from provider Services
570    private void removeFromPropertyServiceTable(Object key) {
571        if (key == null || !(key instanceof String)) {
572            return;
573        }
574        String k = (String) key;
575        if (k.startsWith("Provider.")) { // Provider service type is reserved
576            return;
577        }
578        Provider.Service s;
579        String serviceName;
580        String algorithm = null;
581        String attribute = null;
582        int i;
583        if (k.startsWith("Alg.Alias.")) { // Alg.Alias.<crypto_service>.<aliasName>=<standardName>
584            String aliasName;
585            String service_alias = k.substring(10);
586            i = service_alias.indexOf('.');
587            serviceName = service_alias.substring(0, i);
588            aliasName = service_alias.substring(i + 1);
589            if (propertyAliasTable != null) {
590                propertyAliasTable.remove(key(serviceName, aliasName));
591            }
592            if (propertyServiceTable != null) {
593                for (Iterator<Service> it = propertyServiceTable.values().iterator(); it
594                        .hasNext();) {
595                    s = it.next();
596                    if (s.aliases.contains(aliasName)) {
597                        s.aliases.remove(aliasName);
598                        return;
599                    }
600                }
601            }
602            return;
603        }
604        int j = k.indexOf('.');
605        if (j == -1) { // unknown format
606            return;
607        }
608
609        i = k.indexOf(' ');
610        if (i == -1) { // <crypto_service>.<algorithm_or_type>=<className>
611            serviceName = k.substring(0, j);
612            algorithm = k.substring(j + 1);
613            if (propertyServiceTable != null) {
614                Provider.Service ser = propertyServiceTable.remove(key(serviceName, algorithm));
615                if (ser != null && propertyAliasTable != null
616                        && ser.aliases != null) {
617                    for (String alias : ser.aliases) {
618                        propertyAliasTable.remove(key(serviceName, alias));
619                    }
620                }
621            }
622        } else {
623            // <crypto_service>.<algorithm_or_type>
624            // <attribute_name>=<attrValue>
625            attribute = k.substring(i + 1);
626            serviceName = k.substring(0, j);
627            algorithm = k.substring(j + 1, i);
628            if (propertyServiceTable != null) {
629                Object o = propertyServiceTable.get(key(serviceName, algorithm));
630                if (o != null) {
631                    s = (Provider.Service) o;
632                    s.attributes.remove(attribute);
633                }
634            }
635        }
636    }
637
638    // Update provider Services if the properties was changed
639    private void updatePropertyServiceTable() {
640        Object _key;
641        Object _value;
642        Provider.Service s;
643        String serviceName;
644        String algorithm;
645        if (changedProperties == null || changedProperties.isEmpty()) {
646            return;
647        }
648        for (Iterator<Map.Entry<Object, Object>> it = changedProperties.entrySet().iterator(); it
649                .hasNext();) {
650            Map.Entry<Object, Object> entry = it.next();
651            _key = entry.getKey();
652            _value = entry.getValue();
653            if (_key == null || _value == null || !(_key instanceof String)
654                    || !(_value instanceof String)) {
655                continue;
656            }
657            String key = (String) _key;
658            String value = (String) _value;
659            if (key.startsWith("Provider")) {
660                // Provider service type is reserved
661                continue;
662            }
663            int i;
664            if (key.startsWith("Alg.Alias.")) {
665                // Alg.Alias.<crypto_service>.<aliasName>=<standardName>
666                String aliasName;
667                String service_alias = key.substring(10);
668                i = service_alias.indexOf('.');
669                serviceName = service_alias.substring(0, i);
670                aliasName = service_alias.substring(i + 1);
671                algorithm = value;
672                String propertyServiceTableKey = key(serviceName, algorithm);
673                Object o = null;
674                if (propertyServiceTable == null) {
675                    propertyServiceTable = new LinkedHashMap<String, Service>(128);
676                } else {
677                    o = propertyServiceTable.get(propertyServiceTableKey);
678                }
679                if (o != null) {
680                    s = (Provider.Service) o;
681                    s.addAlias(aliasName);
682                    if (propertyAliasTable == null) {
683                        propertyAliasTable = new LinkedHashMap<String, Service>(256);
684                    }
685                    propertyAliasTable.put(key(serviceName, aliasName), s);
686                } else {
687                    String className = (String) changedProperties
688                            .get(serviceName + "." + algorithm);
689                    if (className != null) {
690                        List<String> l = new ArrayList<String>();
691                        l.add(aliasName);
692                        s = new Provider.Service(this, serviceName, algorithm,
693                                className, l, new HashMap<String, String>());
694                        propertyServiceTable.put(propertyServiceTableKey, s);
695                        if (propertyAliasTable == null) {
696                            propertyAliasTable = new LinkedHashMap<String, Service>(256);
697                        }
698                        propertyAliasTable.put(key(serviceName, aliasName), s);
699                    }
700                }
701                continue;
702            }
703            int j = key.indexOf('.');
704            if (j == -1) { // unknown format
705                continue;
706            }
707            i = key.indexOf(' ');
708            if (i == -1) { // <crypto_service>.<algorithm_or_type>=<className>
709                serviceName = key.substring(0, j);
710                algorithm = key.substring(j + 1);
711                String propertyServiceTableKey = key(serviceName, algorithm);
712                Object o = null;
713                if (propertyServiceTable != null) {
714                    o = propertyServiceTable.get(propertyServiceTableKey);
715                }
716                if (o != null) {
717                    s = (Provider.Service) o;
718                    s.className = value;
719                } else {
720                    s = new Provider.Service(this, serviceName, algorithm,
721                            value, Collections.<String>emptyList(),
722                            Collections.<String,String>emptyMap());
723                    if (propertyServiceTable == null) {
724                        propertyServiceTable = new LinkedHashMap<String, Service>(128);
725                    }
726                    propertyServiceTable.put(propertyServiceTableKey, s);
727
728                }
729            } else {
730                // <crypto_service>.<algorithm_or_type> <attribute_name>=<attrValue>
731                serviceName = key.substring(0, j);
732                algorithm = key.substring(j + 1, i);
733                String attribute = key.substring(i + 1);
734                String propertyServiceTableKey = key(serviceName, algorithm);
735                Object o = null;
736                if (propertyServiceTable != null) {
737                    o = propertyServiceTable.get(propertyServiceTableKey);
738                }
739                if (o != null) {
740                    s = (Provider.Service) o;
741                    s.putAttribute(attribute, value);
742                } else {
743                    String className = (String) changedProperties
744                            .get(serviceName + "." + algorithm);
745                    if (className != null) {
746                        Map<String, String> m = new HashMap<String, String>();
747                        m.put(attribute, value);
748                        s = new Provider.Service(this, serviceName, algorithm,
749                                className, new ArrayList<String>(), m);
750                        if (propertyServiceTable == null) {
751                            propertyServiceTable = new LinkedHashMap<String, Service>(128);
752                        }
753                        propertyServiceTable.put(propertyServiceTableKey, s);
754                    }
755                }
756            }
757        }
758        servicesChanged();
759        changedProperties = null;
760    }
761
762    private void servicesChanged() {
763        lastServicesByType = null;
764        lastServiceName = null;
765        lastServicesSet = null;
766    }
767
768    /**
769     * These attributes should be placed in each Provider object:
770     * Provider.id name, Provider.id version, Provider.id info,
771     * Provider.id className
772     */
773    private void putProviderInfo() {
774        super.put("Provider.id name", (name != null) ? name : "null");
775        super.put("Provider.id version", versionString);
776        super.put("Provider.id info", (info != null) ? info : "null");
777        super.put("Provider.id className", this.getClass().getName());
778    }
779
780    /**
781     * Returns the property with the specified key in the provider properties.
782     * The name is not case-sensitive.
783     */
784    private String getPropertyIgnoreCase(String key) {
785        String res = getProperty(key);
786        if (res != null) {
787            return res;
788        }
789        for (Enumeration<?> e = propertyNames(); e.hasMoreElements(); ) {
790            String propertyName = (String) e.nextElement();
791            if (key.equalsIgnoreCase(propertyName)) {
792                return getProperty(propertyName);
793            }
794        }
795        return null;
796    }
797
798    private static String key(String type, String algorithm) {
799        return type + '.' + algorithm.toUpperCase(Locale.US);
800    }
801
802    /**
803     * {@code Service} represents a service in the Java Security infrastructure.
804     * Each service describes its type, the algorithm it implements, to which
805     * provider it belongs and other properties.
806     */
807    public static class Service {
808        // The provider
809        private Provider provider;
810
811        // The type of this service
812        private String type;
813
814        // The algorithm name
815        private String algorithm;
816
817        // The class implementing this service
818        private String className;
819
820        // The aliases
821        private List<String> aliases;
822
823        // The attributes
824        private Map<String,String> attributes;
825
826        // Service implementation
827        private Class<?> implementation;
828
829        // For newInstance() optimization
830        private String lastClassName;
831
832        /**
833         * Constructs a new instance of {@code Service} with the given
834         * attributes.
835         *
836         * @param provider
837         *            the provider to which this service belongs.
838         * @param type
839         *            the type of this service (for example {@code
840         *            KeyPairGenerator}).
841         * @param algorithm
842         *            the algorithm this service implements.
843         * @param className
844         *            the name of the class implementing this service.
845         * @param aliases
846         *            {@code List} of aliases for the algorithm name, or {@code
847         *            null} if the implemented algorithm has no aliases.
848         * @param attributes
849         *            {@code Map} of additional attributes, or {@code null} if
850         *            this {@code Service} has no attributed.
851         * @throws NullPointerException
852         *             if {@code provider, type, algorithm} or {@code className}
853         *             is {@code null}.
854         */
855        public Service(Provider provider, String type, String algorithm,
856                String className, List<String> aliases, Map<String, String> attributes) {
857            if (provider == null) {
858                throw new NullPointerException("provider == null");
859            } else if (type == null) {
860                throw new NullPointerException("type == null");
861            } else if (algorithm == null) {
862                throw new NullPointerException("algorithm == null");
863            } else if (className == null) {
864                throw new NullPointerException("className == null");
865            }
866            this.provider = provider;
867            this.type = type;
868            this.algorithm = algorithm;
869            this.className = className;
870            this.aliases = ((aliases != null) && (aliases.size() == 0))
871                    ? Collections.<String>emptyList() : aliases;
872            this.attributes =
873                    ((attributes != null) && (attributes.size() == 0))
874                    ? Collections.<String,String>emptyMap() : attributes;
875        }
876
877        /**
878         * Adds an alias.
879         *
880         * @param alias the alias to add
881         */
882        /*package*/ void addAlias(String alias) {
883            if ((aliases == null) || (aliases.size() == 0)) {
884                aliases = new ArrayList<String>();
885            }
886            aliases.add(alias);
887        }
888
889        /**
890         * Puts a new attribute mapping.
891         *
892         * @param name the attribute name.
893         * @param value the attribute value.
894         */
895        /*package*/ void putAttribute(String name, String value) {
896            if ((attributes == null) || (attributes.size() == 0)) {
897                attributes = new HashMap<String,String>();
898            }
899            attributes.put(name, value);
900        }
901
902        /**
903         * Returns the type of this {@code Service}. For example {@code
904         * KeyPairGenerator}.
905         *
906         * @return the type of this {@code Service}.
907         */
908        public final String getType() {
909            return type;
910        }
911
912        /**
913         * Returns the name of the algorithm implemented by this {@code
914         * Service}.
915         *
916         * @return the name of the algorithm implemented by this {@code
917         *         Service}.
918         */
919        public final String getAlgorithm() {
920            return algorithm;
921        }
922
923        /**
924         * Returns the {@code Provider} this {@code Service} belongs to.
925         *
926         * @return the {@code Provider} this {@code Service} belongs to.
927         */
928        public final Provider getProvider() {
929            return provider;
930        }
931
932        /**
933         * Returns the name of the class implementing this {@code Service}.
934         *
935         * @return the name of the class implementing this {@code Service}.
936         */
937        public final String getClassName() {
938            return className;
939        }
940
941        /**
942         * Returns the value of the attribute with the specified {@code name}.
943         *
944         * @param name
945         *            the name of the attribute.
946         * @return the value of the attribute, or {@code null} if no attribute
947         *         with the given name is set.
948         * @throws NullPointerException
949         *             if {@code name} is {@code null}.
950         */
951        public final String getAttribute(String name) {
952            if (name == null) {
953                throw new NullPointerException("name == null");
954            }
955            if (attributes == null) {
956                return null;
957            }
958            return attributes.get(name);
959        }
960
961        List<String> getAliases() {
962            if (aliases == null){
963                aliases = new ArrayList<String>(0);
964            }
965            return aliases;
966        }
967
968        /**
969         * Creates and returns a new instance of the implementation described by
970         * this {@code Service}.
971         *
972         * @param constructorParameter
973         *            the parameter that is used by the constructor, or {@code
974         *            null} if the implementation does not declare a constructor
975         *            parameter.
976         * @return a new instance of the implementation described by this
977         *         {@code Service}.
978         * @throws NoSuchAlgorithmException
979         *             if the instance could not be constructed.
980         * @throws InvalidParameterException
981         *             if the implementation does not support the specified
982         *             {@code constructorParameter}.
983         */
984        public Object newInstance(Object constructorParameter) throws NoSuchAlgorithmException {
985            if (implementation == null || !className.equals(lastClassName)) {
986                ClassLoader cl = provider.getClass().getClassLoader();
987                if (cl == null) {
988                    cl = ClassLoader.getSystemClassLoader();
989                }
990                try {
991                    implementation = Class.forName(className, true, cl);
992                    lastClassName = className;
993                } catch (Exception e) {
994                    throw new NoSuchAlgorithmException(type + " " + algorithm + " implementation not found: " + e);
995                }
996            }
997            if (constructorParameter == null) {
998                try {
999                    return implementation.newInstance();
1000                } catch (Exception e) {
1001                    throw new NoSuchAlgorithmException(
1002                            type + " " + algorithm + " implementation not found", e);
1003                }
1004            }
1005            if (!supportsParameter(constructorParameter)) {
1006                throw new InvalidParameterException(type + ": service cannot use the parameter");
1007            }
1008
1009            Class[] parameterTypes = new Class[1];
1010            Object[] initargs = { constructorParameter };
1011            try {
1012                if (type.equalsIgnoreCase("CertStore")) {
1013                    parameterTypes[0] = Class.forName("java.security.cert.CertStoreParameters");
1014                } else {
1015                    parameterTypes[0] = constructorParameter.getClass();
1016                }
1017                return implementation.getConstructor(parameterTypes)
1018                        .newInstance(initargs);
1019            } catch (Exception e) {
1020                throw new NoSuchAlgorithmException(type + " " + algorithm
1021                        + " implementation not found", e);
1022            }
1023        }
1024
1025        /**
1026         * Indicates whether this {@code Service} supports the specified
1027         * constructor parameter.
1028         *
1029         * @param parameter
1030         *            the parameter to test.
1031         * @return {@code true} if this {@code Service} supports the specified
1032         *         constructor parameter, {@code false} otherwise.
1033         */
1034        public boolean supportsParameter(Object parameter) {
1035            return true;
1036        }
1037
1038        /**
1039         * Returns a string containing a concise, human-readable description of
1040         * this {@code Service}.
1041         *
1042         * @return a printable representation for this {@code Service}.
1043         */
1044        @Override
1045        public String toString() {
1046            String result = "Provider " + provider.getName() + " Service "
1047                    + type + "." + algorithm + " " + className;
1048            if (aliases != null) {
1049                result = result + "\nAliases " + aliases.toString();
1050            }
1051            if (attributes != null) {
1052                result = result + "\nAttributes " + attributes.toString();
1053            }
1054            return result;
1055        }
1056    }
1057
1058    private void readObject(java.io.ObjectInputStream in)
1059            throws NotActiveException, IOException, ClassNotFoundException {
1060        in.defaultReadObject();
1061        versionString = String.valueOf(version);
1062        providerNumber = -1;
1063    }
1064}
1065