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.Iterator;
29import java.util.LinkedHashMap;
30import java.util.LinkedHashSet;
31import java.util.List;
32import java.util.Locale;
33import java.util.Map;
34import java.util.Properties;
35import java.util.Set;
36import org.apache.harmony.security.fortress.Services;
37
38/**
39 * {@code Provider} is the abstract superclass for all security providers in the
40 * Java security infrastructure.
41 */
42public abstract class Provider extends Properties {
43    private static final long serialVersionUID = -4298000515446427739L;
44
45    private String name;
46
47    private double version;
48
49    // String representation of the provider version number.
50    private transient String versionString;
51
52    private String info;
53
54    //The provider preference order number.
55    // Equals -1 for non registered provider.
56    private transient int providerNumber = -1;
57
58    // Contains "Service.Algorithm" and Provider.Service classes added using
59    // putService()
60    private transient LinkedHashMap<String, Service> serviceTable;
61
62    // Contains "Service.Alias" and Provider.Service classes added using
63    // putService()
64    private transient LinkedHashMap<String, Service> aliasTable;
65
66    // Contains "Service.Algorithm" and Provider.Service classes added using
67    // put()
68    private transient LinkedHashMap<String, Service> propertyServiceTable;
69
70    // Contains "Service.Alias" and Provider.Service classes added using put()
71    private transient LinkedHashMap<String, Service> propertyAliasTable;
72
73    // The properties changed via put()
74    private transient LinkedHashMap<Object, Object> changedProperties;
75
76    // For getService(String type, String algorithm) optimization:
77    // previous result
78    private transient Provider.Service returnedService;
79    // previous parameters
80    private transient String lastAlgorithm;
81    // last name
82    private transient String lastServiceName;
83
84    // For getServices() optimization:
85    private transient Set<Service> lastServicesSet;
86
87    // For getService(String type) optimization:
88    private transient String lastType;
89    // last Service found by type
90    private transient Provider.Service lastServicesByType;
91
92    /**
93     * Constructs a new instance of {@code Provider} with its name, version and
94     * description.
95     *
96     * @param name
97     *            the name of the provider.
98     * @param version
99     *            the version of the provider.
100     * @param info
101     *            a description of the provider.
102     */
103    protected Provider(String name, double version, String info) {
104        this.name = name;
105        this.version = version;
106        this.info = info;
107        versionString = String.valueOf(version);
108        putProviderInfo();
109    }
110
111    /**
112     * Returns the name of this provider.
113     *
114     * @return the name of this provider.
115     */
116    public String getName() {
117        return name;
118    }
119
120    /**
121     * Returns the version number for the services being provided.
122     *
123     * @return the version number for the services being provided.
124     */
125    public double getVersion() {
126        return version;
127    }
128
129    /**
130     * Returns a description of the services being provided.
131     *
132     * @return a description of the services being provided.
133     */
134    public String getInfo() {
135        return info;
136    }
137
138    /**
139     * Returns a string containing a concise, human-readable description of
140     * this {@code Provider} including its name and its version.
141     *
142     * @return a printable representation for this {@code Provider}.
143     */
144    @Override
145    public String toString() {
146        return name + " version " + version;
147    }
148
149    /**
150     * Clears all properties used to look up services implemented by this
151     * {@code Provider}.
152     */
153    @Override
154    public synchronized void clear() {
155        super.clear();
156        if (serviceTable != null) {
157            serviceTable.clear();
158        }
159        if (propertyServiceTable != null) {
160            propertyServiceTable.clear();
161        }
162        if (aliasTable != null) {
163            aliasTable.clear();
164        }
165        if (propertyAliasTable != null) {
166            propertyAliasTable.clear();
167        }
168        changedProperties = null;
169        putProviderInfo();
170        if (providerNumber != -1) {
171            // if registered then refresh Services
172            Services.setNeedRefresh();
173        }
174        servicesChanged();
175    }
176
177    @Override
178    public synchronized void load(InputStream inStream) throws IOException {
179        Properties tmp = new Properties();
180        tmp.load(inStream);
181        myPutAll(tmp);
182    }
183
184    /**
185     * Copies all from the provided map to this {@code Provider}.
186     * @param t
187     *            the mappings to copy to this provider.
188     */
189    @Override
190    public synchronized void putAll(Map<?,?> t) {
191        myPutAll(t);
192    }
193
194    private void myPutAll(Map<?,?> t) {
195        if (changedProperties == null) {
196            changedProperties = new LinkedHashMap<Object, Object>();
197        }
198        Iterator<? extends Map.Entry<?, ?>> it = t.entrySet().iterator();
199        Object key;
200        Object value;
201        while (it.hasNext()) {
202            Map.Entry<?, ?> entry = it.next();
203            key = entry.getKey();
204            if (key instanceof String && ((String) key).startsWith("Provider.")) {
205                // Provider service type is reserved
206                continue;
207            }
208            value = entry.getValue();
209            super.put(key, value);
210            if (changedProperties.remove(key) == null) {
211                removeFromPropertyServiceTable(key);
212            }
213            changedProperties.put(key, value);
214        }
215        if (providerNumber != -1) {
216            // if registered then refresh Services
217            Services.setNeedRefresh();
218        }
219    }
220
221    @Override
222    public synchronized Set<Map.Entry<Object,Object>> entrySet() {
223        return Collections.unmodifiableSet(super.entrySet());
224    }
225
226    @Override
227    public Set<Object> keySet() {
228        return Collections.unmodifiableSet(super.keySet());
229    }
230
231    @Override
232    public Collection<Object> values() {
233        return Collections.unmodifiableCollection(super.values());
234    }
235
236    /**
237     * Maps the specified {@code key} property name to the specified {@code
238     * value}.
239     *
240     * @param key
241     *            the name of the property.
242     * @param value
243     *            the value of the property.
244     * @return the value that was previously mapped to the specified {@code key}
245     *         ,or {@code null} if it did not have one.
246     */
247    @Override
248    public synchronized Object put(Object key, Object value) {
249        if (key instanceof String && ((String) key).startsWith("Provider.")) {
250            // Provider service type is reserved
251            return null;
252        }
253        if (providerNumber != -1) {
254            // if registered then refresh Services
255            Services.setNeedRefresh();
256        }
257        if (changedProperties != null && changedProperties.remove(key) == null) {
258            removeFromPropertyServiceTable(key);
259        }
260        if (changedProperties == null) {
261            changedProperties = new LinkedHashMap<Object, Object>();
262        }
263        changedProperties.put(key, value);
264        return super.put(key, value);
265    }
266
267    /**
268     * Removes the specified {@code key} and its associated value from this
269     * {@code Provider}.
270     *
271     * @param key
272     *            the name of the property
273     * @return the value that was mapped to the specified {@code key} ,or
274     *         {@code null} if no mapping was present
275     */
276    @Override
277    public synchronized Object remove(Object key) {
278        if (key instanceof String && ((String) key).startsWith("Provider.")) {
279            // Provider service type is reserved
280            return null;
281        }
282        if (providerNumber != -1) {
283            // if registered then refresh Services
284            Services.setNeedRefresh();
285        }
286        if (changedProperties != null && changedProperties.remove(key) == null) {
287            removeFromPropertyServiceTable(key);
288            if (changedProperties.size() == 0) {
289                changedProperties = null;
290            }
291        }
292        return super.remove(key);
293    }
294
295    /**
296     * Returns true if this provider implements the given algorithm. Caller
297     * must specify the cryptographic service and specify constraints via the
298     * attribute name and value.
299     *
300     * @param serv
301     *            Crypto service.
302     * @param alg
303     *            Algorithm or type.
304     * @param attribute
305     *            The attribute name or {@code null}.
306     * @param val
307     *            The attribute value.
308     * @return
309     */
310    boolean implementsAlg(String serv, String alg, String attribute, String val) {
311        String servAlg = serv + "." + alg;
312        String prop = getPropertyIgnoreCase(servAlg);
313        if (prop == null) {
314            alg = getPropertyIgnoreCase("Alg.Alias." + servAlg);
315            if (alg != null) {
316                servAlg = serv + "." + alg;
317                prop = getPropertyIgnoreCase(servAlg);
318            }
319        }
320        if (prop != null) {
321            if (attribute == null) {
322                return true;
323            }
324            return checkAttribute(servAlg, attribute, val);
325        }
326        return false;
327    }
328
329    /**
330     * Returns true if this provider has the same value as is given for the
331     * given attribute
332     */
333    private boolean checkAttribute(String servAlg, String attribute, String val) {
334
335        String attributeValue = getPropertyIgnoreCase(servAlg + ' ' + attribute);
336        if (attributeValue != null) {
337            if (attribute.equalsIgnoreCase("KeySize")) {
338                if (Integer.parseInt(attributeValue) >= Integer.parseInt(val)) {
339                    return true;
340                }
341            } else { // other attributes
342                if (attributeValue.equalsIgnoreCase(val)) {
343                    return true;
344                }
345            }
346        }
347        return false;
348    }
349
350    /**
351     *
352     * Set the provider preference order number.
353     *
354     * @param n
355     */
356    void setProviderNumber(int n) {
357        providerNumber = n;
358    }
359
360    /**
361     *
362     * Get the provider preference order number.
363     *
364     * @return
365     */
366    int getProviderNumber() {
367        return providerNumber;
368    }
369
370    /**
371     * Get the service of the specified type
372     *
373     */
374    synchronized Provider.Service getService(String type) {
375        updatePropertyServiceTable();
376        if (lastServicesByType != null && type.equals(lastType)) {
377            return lastServicesByType;
378        }
379        Provider.Service service;
380        for (Iterator<Service> it = getServices().iterator(); it.hasNext();) {
381            service = it.next();
382            if (type.equals(service.type)) {
383                lastType = type;
384                lastServicesByType = service;
385                return service;
386            }
387        }
388        return null;
389    }
390
391    /**
392     * Returns the service with the specified {@code type} implementing the
393     * specified {@code algorithm}, or {@code null} if no such implementation
394     * exists.
395     * <p>
396     * If two services match the requested type and algorithm, the one added
397     * with the {@link #putService(Service)} is returned (as opposed to the one
398     * added via {@link #put(Object, Object)}.
399     *
400     * @param type
401     *            the type of the service (for example {@code KeyPairGenerator})
402     * @param algorithm
403     *            the algorithm name (case insensitive)
404     * @return the requested service, or {@code null} if no such implementation
405     *         exists
406     */
407    public synchronized Provider.Service getService(String type,
408            String algorithm) {
409        if (type == null) {
410            throw new NullPointerException("type == null");
411        } else if (algorithm == null) {
412            throw new NullPointerException("algorithm == null");
413        }
414
415        if (type.equals(lastServiceName) && algorithm.equalsIgnoreCase(lastAlgorithm)) {
416            return returnedService;
417        }
418
419        String key = key(type, algorithm);
420        Object o = null;
421        if (serviceTable != null) {
422            o = serviceTable.get(key);
423        }
424        if (o == null && aliasTable != null) {
425            o = aliasTable.get(key);
426        }
427        if (o == null) {
428            updatePropertyServiceTable();
429        }
430        if (o == null && propertyServiceTable != null) {
431            o = propertyServiceTable.get(key);
432        }
433        if (o == null && propertyAliasTable != null) {
434            o = propertyAliasTable.get(key);
435        }
436
437        if (o != null) {
438            lastServiceName = type;
439            lastAlgorithm = algorithm;
440            returnedService = (Provider.Service) o;
441            return returnedService;
442        }
443        return null;
444    }
445
446    /**
447     * Returns an unmodifiable {@code Set} of all services registered by this
448     * provider.
449     *
450     * @return an unmodifiable {@code Set} of all services registered by this
451     *         provider
452     */
453    public synchronized Set<Provider.Service> getServices() {
454        updatePropertyServiceTable();
455        if (lastServicesSet != null) {
456            return lastServicesSet;
457        }
458        if (serviceTable != null) {
459            lastServicesSet = new LinkedHashSet<Service>(serviceTable.values());
460        } else {
461            lastServicesSet = new LinkedHashSet<Service>();
462        }
463        if (propertyServiceTable != null) {
464            lastServicesSet.addAll(propertyServiceTable.values());
465        }
466        lastServicesSet = Collections.unmodifiableSet(lastServicesSet);
467        return lastServicesSet;
468    }
469
470    /**
471     * Adds a {@code Service} to this {@code Provider}. If a service with the
472     * same name was registered via this method, it is replace.
473     *
474     * @param s
475     *            the {@code Service} to register
476     */
477    protected synchronized void putService(Provider.Service s) {
478        if (s == null) {
479            throw new NullPointerException("s == null");
480        }
481        if ("Provider".equals(s.getType())) { // Provider service type cannot be added
482            return;
483        }
484        servicesChanged();
485        if (serviceTable == null) {
486            serviceTable = new LinkedHashMap<String, Service>(128);
487        }
488        serviceTable.put(key(s.type, s.algorithm), s);
489        if (s.aliases != null) {
490            if (aliasTable == null) {
491                aliasTable = new LinkedHashMap<String, Service>(256);
492            }
493            for (String alias : s.getAliases()) {
494                aliasTable.put(key(s.type, alias), s);
495            }
496        }
497        serviceInfoToProperties(s);
498    }
499
500    /**
501     * Removes a previously registered {@code Service} from this {@code
502     * Provider}.
503     *
504     * @param s
505     *            the {@code Service} to remove
506     * @throws NullPointerException
507     *             if {@code s} is {@code null}
508     */
509    protected synchronized void removeService(Provider.Service s) {
510        if (s == null) {
511            throw new NullPointerException("s == null");
512        }
513        servicesChanged();
514        if (serviceTable != null) {
515            serviceTable.remove(key(s.type, s.algorithm));
516        }
517        if (aliasTable != null && s.aliases != null) {
518            for (String alias: s.getAliases()) {
519                aliasTable.remove(key(s.type, alias));
520            }
521        }
522        serviceInfoFromProperties(s);
523    }
524
525    /**
526     * Add Service information to the provider's properties.
527     */
528    private void serviceInfoToProperties(Provider.Service s) {
529        super.put(s.type + "." + s.algorithm, s.className);
530        if (s.aliases != null) {
531            for (Iterator<String> i = s.aliases.iterator(); i.hasNext();) {
532                super.put("Alg.Alias." + s.type + "." + i.next(), s.algorithm);
533            }
534        }
535        if (s.attributes != null) {
536            for (Map.Entry<String, String> entry : s.attributes.entrySet()) {
537                super.put(s.type + "." + s.algorithm + " " + entry.getKey(),
538                        entry.getValue());
539            }
540        }
541        if (providerNumber != -1) {
542            // if registered then refresh Services
543            Services.setNeedRefresh();
544        }
545    }
546
547    /**
548     * Remove Service information from the provider's properties.
549     */
550    private void serviceInfoFromProperties(Provider.Service s) {
551        super.remove(s.type + "." + s.algorithm);
552        if (s.aliases != null) {
553            for (Iterator<String> i = s.aliases.iterator(); i.hasNext();) {
554                super.remove("Alg.Alias." + s.type + "." + i.next());
555            }
556        }
557        if (s.attributes != null) {
558            for (Map.Entry<String, String> entry : s.attributes.entrySet()) {
559                super.remove(s.type + "." + s.algorithm + " " + entry.getKey());
560            }
561        }
562        if (providerNumber != -1) {
563            // if registered then refresh Services
564            Services.setNeedRefresh();
565        }
566    }
567
568    // Remove property information from provider Services
569    private void removeFromPropertyServiceTable(Object key) {
570        if (key == null || !(key instanceof String)) {
571            return;
572        }
573        String k = (String) key;
574        if (k.startsWith("Provider.")) { // Provider service type is reserved
575            return;
576        }
577        Provider.Service s;
578        String serviceName;
579        String algorithm = null;
580        String attribute = null;
581        int i;
582        if (k.startsWith("Alg.Alias.")) { // Alg.Alias.<crypto_service>.<aliasName>=<standardName>
583            String aliasName;
584            String service_alias = k.substring(10);
585            i = service_alias.indexOf('.');
586            serviceName = service_alias.substring(0, i);
587            aliasName = service_alias.substring(i + 1);
588            if (propertyAliasTable != null) {
589                propertyAliasTable.remove(key(serviceName, aliasName));
590            }
591            if (propertyServiceTable != null) {
592                for (Iterator<Service> it = propertyServiceTable.values().iterator(); it
593                        .hasNext();) {
594                    s = it.next();
595                    if (s.aliases.contains(aliasName)) {
596                        s.aliases.remove(aliasName);
597                        return;
598                    }
599                }
600            }
601            return;
602        }
603        int j = k.indexOf('.');
604        if (j == -1) { // unknown format
605            return;
606        }
607
608        i = k.indexOf(' ');
609        if (i == -1) { // <crypto_service>.<algorithm_or_type>=<className>
610            serviceName = k.substring(0, j);
611            algorithm = k.substring(j + 1);
612            if (propertyServiceTable != null) {
613                Provider.Service ser = propertyServiceTable.remove(key(serviceName, algorithm));
614                if (ser != null && propertyAliasTable != null
615                        && ser.aliases != null) {
616                    for (String alias : ser.aliases) {
617                        propertyAliasTable.remove(key(serviceName, alias));
618                    }
619                }
620            }
621        } else {
622            // <crypto_service>.<algorithm_or_type>
623            // <attribute_name>=<attrValue>
624            attribute = k.substring(i + 1);
625            serviceName = k.substring(0, j);
626            algorithm = k.substring(j + 1, i);
627            if (propertyServiceTable != null) {
628                Object o = propertyServiceTable.get(key(serviceName, algorithm));
629                if (o != null) {
630                    s = (Provider.Service) o;
631                    s.attributes.remove(attribute);
632                }
633            }
634        }
635    }
636
637    // Update provider Services if the properties was changed
638    private void updatePropertyServiceTable() {
639        Object _key;
640        Object _value;
641        Provider.Service s;
642        String serviceName;
643        String algorithm;
644        if (changedProperties == null || changedProperties.isEmpty()) {
645            return;
646        }
647        for (Iterator<Map.Entry<Object, Object>> it = changedProperties.entrySet().iterator(); it
648                .hasNext();) {
649            Map.Entry<Object, Object> entry = it.next();
650            _key = entry.getKey();
651            _value = entry.getValue();
652            if (_key == null || _value == null || !(_key instanceof String)
653                    || !(_value instanceof String)) {
654                continue;
655            }
656            String key = (String) _key;
657            String value = (String) _value;
658            if (key.startsWith("Provider")) {
659                // Provider service type is reserved
660                continue;
661            }
662            int i;
663            if (key.startsWith("Alg.Alias.")) {
664                // Alg.Alias.<crypto_service>.<aliasName>=<standardName>
665                String aliasName;
666                String service_alias = key.substring(10);
667                i = service_alias.indexOf('.');
668                serviceName = service_alias.substring(0, i);
669                aliasName = service_alias.substring(i + 1);
670                algorithm = value;
671                String propertyServiceTableKey = key(serviceName, algorithm);
672                Object o = null;
673                if (propertyServiceTable == null) {
674                    propertyServiceTable = new LinkedHashMap<String, Service>(128);
675                } else {
676                    o = propertyServiceTable.get(propertyServiceTableKey);
677                }
678                if (o != null) {
679                    s = (Provider.Service) o;
680                    s.addAlias(aliasName);
681                    if (propertyAliasTable == null) {
682                        propertyAliasTable = new LinkedHashMap<String, Service>(256);
683                    }
684                    propertyAliasTable.put(key(serviceName, aliasName), s);
685                } else {
686                    String className = (String) changedProperties
687                            .get(serviceName + "." + algorithm);
688                    if (className != null) {
689                        List<String> l = new ArrayList<String>();
690                        l.add(aliasName);
691                        s = new Provider.Service(this, serviceName, algorithm,
692                                className, l, new HashMap<String, String>());
693                        propertyServiceTable.put(propertyServiceTableKey, s);
694                        if (propertyAliasTable == null) {
695                            propertyAliasTable = new LinkedHashMap<String, Service>(256);
696                        }
697                        propertyAliasTable.put(key(serviceName, aliasName), s);
698                    }
699                }
700                continue;
701            }
702            int j = key.indexOf('.');
703            if (j == -1) { // unknown format
704                continue;
705            }
706            i = key.indexOf(' ');
707            if (i == -1) { // <crypto_service>.<algorithm_or_type>=<className>
708                serviceName = key.substring(0, j);
709                algorithm = key.substring(j + 1);
710                String propertyServiceTableKey = key(serviceName, algorithm);
711                Object o = null;
712                if (propertyServiceTable != null) {
713                    o = propertyServiceTable.get(propertyServiceTableKey);
714                }
715                if (o != null) {
716                    s = (Provider.Service) o;
717                    s.className = value;
718                } else {
719                    s = new Provider.Service(this, serviceName, algorithm,
720                            value, Collections.<String>emptyList(),
721                            Collections.<String,String>emptyMap());
722                    if (propertyServiceTable == null) {
723                        propertyServiceTable = new LinkedHashMap<String, Service>(128);
724                    }
725                    propertyServiceTable.put(propertyServiceTableKey, s);
726
727                }
728            } else {
729                // <crypto_service>.<algorithm_or_type> <attribute_name>=<attrValue>
730                serviceName = key.substring(0, j);
731                algorithm = key.substring(j + 1, i);
732                String attribute = key.substring(i + 1);
733                String propertyServiceTableKey = key(serviceName, algorithm);
734                Object o = null;
735                if (propertyServiceTable != null) {
736                    o = propertyServiceTable.get(propertyServiceTableKey);
737                }
738                if (o != null) {
739                    s = (Provider.Service) o;
740                    s.putAttribute(attribute, value);
741                } else {
742                    String className = (String) changedProperties
743                            .get(serviceName + "." + algorithm);
744                    if (className != null) {
745                        Map<String, String> m = new HashMap<String, String>();
746                        m.put(attribute, value);
747                        s = new Provider.Service(this, serviceName, algorithm,
748                                className, new ArrayList<String>(), m);
749                        if (propertyServiceTable == null) {
750                            propertyServiceTable = new LinkedHashMap<String, Service>(128);
751                        }
752                        propertyServiceTable.put(propertyServiceTableKey, s);
753                    }
754                }
755            }
756        }
757        servicesChanged();
758        changedProperties = null;
759    }
760
761    private void servicesChanged() {
762        lastServicesByType = null;
763        lastServiceName = null;
764        lastServicesSet = null;
765    }
766
767    /**
768     * These attributes should be placed in each Provider object:
769     * Provider.id name, Provider.id version, Provider.id info,
770     * Provider.id className
771     */
772    private void putProviderInfo() {
773        super.put("Provider.id name", (name != null) ? name : "null");
774        super.put("Provider.id version", versionString);
775        super.put("Provider.id info", (info != null) ? info : "null");
776        super.put("Provider.id className", this.getClass().getName());
777    }
778
779    /**
780     * Returns the property with the specified key in the provider properties.
781     * The name is not case-sensitive.
782     */
783    private String getPropertyIgnoreCase(String key) {
784        String res = getProperty(key);
785        if (res != null) {
786            return res;
787        }
788        for (Enumeration<?> e = propertyNames(); e.hasMoreElements(); ) {
789            String propertyName = (String) e.nextElement();
790            if (key.equalsIgnoreCase(propertyName)) {
791                return getProperty(propertyName);
792            }
793        }
794        return null;
795    }
796
797    private static String key(String type, String algorithm) {
798        return type + '.' + algorithm.toUpperCase(Locale.US);
799    }
800
801    /**
802     * {@code Service} represents a service in the Java Security infrastructure.
803     * Each service describes its type, the algorithm it implements, to which
804     * provider it belongs and other properties.
805     */
806    public static class Service {
807        /** Attribute name of supported key classes. */
808        private static final String ATTR_SUPPORTED_KEY_CLASSES = "SupportedKeyClasses";
809
810        /** Attribute name of supported key formats. */
811        private static final String ATTR_SUPPORTED_KEY_FORMATS = "SupportedKeyFormats";
812
813        /** Whether this type supports calls to {@link #supportsParameter(Object)}. */
814        private static final HashMap<String, Boolean> supportsParameterTypes
815                = new HashMap<String, Boolean>();
816        static {
817            // Does not support parameter
818            supportsParameterTypes.put("AlgorithmParameterGenerator", false);
819            supportsParameterTypes.put("AlgorithmParameters", false);
820            supportsParameterTypes.put("CertificateFactory", false);
821            supportsParameterTypes.put("CertPathBuilder", false);
822            supportsParameterTypes.put("CertPathValidator", false);
823            supportsParameterTypes.put("CertStore", false);
824            supportsParameterTypes.put("KeyFactory", false);
825            supportsParameterTypes.put("KeyGenerator", false);
826            supportsParameterTypes.put("KeyManagerFactory", false);
827            supportsParameterTypes.put("KeyPairGenerator", false);
828            supportsParameterTypes.put("KeyStore", false);
829            supportsParameterTypes.put("MessageDigest", false);
830            supportsParameterTypes.put("SecretKeyFactory", false);
831            supportsParameterTypes.put("SecureRandom", false);
832            supportsParameterTypes.put("SSLContext", false);
833            supportsParameterTypes.put("TrustManagerFactory", false);
834
835            // Supports parameter
836            supportsParameterTypes.put("Cipher", true);
837            supportsParameterTypes.put("KeyAgreement", true);
838            supportsParameterTypes.put("Mac", true);
839            supportsParameterTypes.put("Signature", true);
840        }
841
842        /** Constructor argument classes for calls to {@link #newInstance(Object)}. */
843        private static final HashMap<String, Class<?>> constructorParameterClasses = new HashMap<String, Class<?>>();
844        static {
845            // Types that take a parameter to newInstance
846            constructorParameterClasses.put("CertStore",
847                    loadClassOrThrow("java.security.cert.CertStoreParameters"));
848
849            // Types that do not take any kind of parameter
850            constructorParameterClasses.put("AlgorithmParameterGenerator", null);
851            constructorParameterClasses.put("AlgorithmParameters", null);
852            constructorParameterClasses.put("CertificateFactory", null);
853            constructorParameterClasses.put("CertPathBuilder", null);
854            constructorParameterClasses.put("CertPathValidator", null);
855            constructorParameterClasses.put("KeyFactory", null);
856            constructorParameterClasses.put("KeyGenerator", null);
857            constructorParameterClasses.put("KeyManagerFactory", null);
858            constructorParameterClasses.put("KeyPairGenerator", null);
859            constructorParameterClasses.put("KeyStore", null);
860            constructorParameterClasses.put("MessageDigest", null);
861            constructorParameterClasses.put("SecretKeyFactory", null);
862            constructorParameterClasses.put("SecureRandom", null);
863            constructorParameterClasses.put("SSLContext", null);
864            constructorParameterClasses.put("TrustManagerFactory", null);
865            constructorParameterClasses.put("Cipher", null);
866            constructorParameterClasses.put("KeyAgreement", null);
867            constructorParameterClasses.put("Mac", null);
868            constructorParameterClasses.put("Signature", null);
869        }
870
871        /** Called to load a class if it's critical that the class exists. */
872        private static Class<?> loadClassOrThrow(String className) {
873            try {
874                return Provider.class.getClassLoader().loadClass(className);
875            } catch (Exception e) {
876                throw new AssertionError(e);
877            }
878        }
879
880        // The provider
881        private Provider provider;
882
883        // The type of this service
884        private String type;
885
886        // The algorithm name
887        private String algorithm;
888
889        // The class implementing this service
890        private String className;
891
892        // The aliases
893        private List<String> aliases;
894
895        // The attributes
896        private Map<String,String> attributes;
897
898        // Service implementation
899        private Class<?> implementation;
900
901        // For newInstance() optimization
902        private String lastClassName;
903
904        /** Indicates whether supportedKeyClasses and supportedKeyFormats. */
905        private volatile boolean supportedKeysInitialized;
906
907        /** List of classes that this service supports. */
908        private Class<?>[] keyClasses;
909
910        /** List of key formats this service supports. */
911        private String[] keyFormats;
912
913        /**
914         * Constructs a new instance of {@code Service} with the given
915         * attributes.
916         *
917         * @param provider
918         *            the provider to which this service belongs.
919         * @param type
920         *            the type of this service (for example {@code
921         *            KeyPairGenerator}).
922         * @param algorithm
923         *            the algorithm this service implements.
924         * @param className
925         *            the name of the class implementing this service.
926         * @param aliases
927         *            {@code List} of aliases for the algorithm name, or {@code
928         *            null} if the implemented algorithm has no aliases.
929         * @param attributes
930         *            {@code Map} of additional attributes, or {@code null} if
931         *            this {@code Service} has no attributed.
932         * @throws NullPointerException
933         *             if {@code provider, type, algorithm} or {@code className}
934         *             is {@code null}.
935         */
936        public Service(Provider provider, String type, String algorithm,
937                String className, List<String> aliases, Map<String, String> attributes) {
938            if (provider == null) {
939                throw new NullPointerException("provider == null");
940            } else if (type == null) {
941                throw new NullPointerException("type == null");
942            } else if (algorithm == null) {
943                throw new NullPointerException("algorithm == null");
944            } else if (className == null) {
945                throw new NullPointerException("className == null");
946            }
947            this.provider = provider;
948            this.type = type;
949            this.algorithm = algorithm;
950            this.className = className;
951            this.aliases = ((aliases != null) && (aliases.size() == 0))
952                    ? Collections.<String>emptyList() : aliases;
953            this.attributes =
954                    ((attributes != null) && (attributes.size() == 0))
955                    ? Collections.<String,String>emptyMap() : attributes;
956        }
957
958        /**
959         * Adds an alias.
960         *
961         * @param alias the alias to add
962         */
963        /*package*/ void addAlias(String alias) {
964            if ((aliases == null) || (aliases.size() == 0)) {
965                aliases = new ArrayList<String>();
966            }
967            aliases.add(alias);
968        }
969
970        /**
971         * Puts a new attribute mapping.
972         *
973         * @param name the attribute name.
974         * @param value the attribute value.
975         */
976        /*package*/ void putAttribute(String name, String value) {
977            if ((attributes == null) || (attributes.size() == 0)) {
978                attributes = new HashMap<String,String>();
979            }
980            attributes.put(name, value);
981        }
982
983        /**
984         * Returns the type of this {@code Service}. For example {@code
985         * KeyPairGenerator}.
986         *
987         * @return the type of this {@code Service}.
988         */
989        public final String getType() {
990            return type;
991        }
992
993        /**
994         * Returns the name of the algorithm implemented by this {@code
995         * Service}.
996         *
997         * @return the name of the algorithm implemented by this {@code
998         *         Service}.
999         */
1000        public final String getAlgorithm() {
1001            return algorithm;
1002        }
1003
1004        /**
1005         * Returns the {@code Provider} this {@code Service} belongs to.
1006         *
1007         * @return the {@code Provider} this {@code Service} belongs to.
1008         */
1009        public final Provider getProvider() {
1010            return provider;
1011        }
1012
1013        /**
1014         * Returns the name of the class implementing this {@code Service}.
1015         *
1016         * @return the name of the class implementing this {@code Service}.
1017         */
1018        public final String getClassName() {
1019            return className;
1020        }
1021
1022        /**
1023         * Returns the value of the attribute with the specified {@code name}.
1024         *
1025         * @param name
1026         *            the name of the attribute.
1027         * @return the value of the attribute, or {@code null} if no attribute
1028         *         with the given name is set.
1029         * @throws NullPointerException
1030         *             if {@code name} is {@code null}.
1031         */
1032        public final String getAttribute(String name) {
1033            if (name == null) {
1034                throw new NullPointerException("name == null");
1035            }
1036            if (attributes == null) {
1037                return null;
1038            }
1039            return attributes.get(name);
1040        }
1041
1042        List<String> getAliases() {
1043            if (aliases == null){
1044                aliases = new ArrayList<String>(0);
1045            }
1046            return aliases;
1047        }
1048
1049        /**
1050         * Creates and returns a new instance of the implementation described by
1051         * this {@code Service}.
1052         *
1053         * @param constructorParameter
1054         *            the parameter that is used by the constructor, or {@code
1055         *            null} if the implementation does not declare a constructor
1056         *            parameter.
1057         * @return a new instance of the implementation described by this
1058         *         {@code Service}.
1059         * @throws NoSuchAlgorithmException
1060         *             if the instance could not be constructed.
1061         * @throws InvalidParameterException
1062         *             if the implementation does not support the specified
1063         *             {@code constructorParameter}.
1064         */
1065        public Object newInstance(Object constructorParameter) throws NoSuchAlgorithmException {
1066            if (implementation == null || !className.equals(lastClassName)) {
1067                ClassLoader cl = provider.getClass().getClassLoader();
1068                if (cl == null) {
1069                    cl = ClassLoader.getSystemClassLoader();
1070                }
1071                try {
1072                    implementation = Class.forName(className, true, cl);
1073                    lastClassName = className;
1074                } catch (Exception e) {
1075                    throw new NoSuchAlgorithmException(type + " " + algorithm + " implementation not found: " + e);
1076                }
1077            }
1078
1079            // We don't know whether this takes a parameter or not.
1080            if (!constructorParameterClasses.containsKey(type)) {
1081                if (constructorParameter == null) {
1082                    return newInstanceNoParameter();
1083                } else {
1084                    return newInstanceWithParameter(constructorParameter,
1085                            constructorParameter.getClass());
1086                }
1087            }
1088
1089            // A known type, but it's not required to have a parameter even if a
1090            // class is specified.
1091            if (constructorParameter == null) {
1092                return newInstanceNoParameter();
1093            }
1094
1095            // Make sure the provided constructor class is valid.
1096            final Class<?> expectedClass = constructorParameterClasses.get(type);
1097            if (expectedClass == null) {
1098                throw new IllegalArgumentException("Constructor parameter not supported for "
1099                        + type);
1100            }
1101            if (!expectedClass.isAssignableFrom(constructorParameter.getClass())) {
1102                throw new IllegalArgumentException("Expecting constructor parameter of type "
1103                        + expectedClass.getName() + " but was "
1104                        + constructorParameter.getClass().getName());
1105            }
1106            return newInstanceWithParameter(constructorParameter, expectedClass);
1107        }
1108
1109        private Object newInstanceWithParameter(Object constructorParameter,
1110                Class<?> parameterClass) throws NoSuchAlgorithmException {
1111            try {
1112                Class<?>[] parameterTypes = { parameterClass };
1113                Object[] initargs = { constructorParameter };
1114                return implementation.getConstructor(parameterTypes).newInstance(initargs);
1115            } catch (Exception e) {
1116                throw new NoSuchAlgorithmException(type + " " + algorithm
1117                        + " implementation not found", e);
1118            }
1119        }
1120
1121        private Object newInstanceNoParameter() throws NoSuchAlgorithmException {
1122            try {
1123                return implementation.newInstance();
1124            } catch (Exception e) {
1125                throw new NoSuchAlgorithmException(type + " " + algorithm
1126                        + " implementation not found", e);
1127            }
1128        }
1129
1130        /**
1131         * Indicates whether this {@code Service} supports the specified
1132         * constructor parameter.
1133         *
1134         * @param parameter
1135         *            the parameter to test.
1136         * @return {@code true} if this {@code Service} supports the specified
1137         *         constructor parameter, {@code false} otherwise.
1138         */
1139        public boolean supportsParameter(Object parameter) {
1140            Boolean supportsParameter = supportsParameterTypes.get(type);
1141            if (supportsParameter == null) {
1142                return true;
1143            }
1144            if (!supportsParameter) {
1145                throw new InvalidParameterException("Cannot use a parameter with " + type);
1146            }
1147
1148            /*
1149             * Only Key parameters are allowed, but allow null since there might
1150             * not be any listed classes or formats for this instance.
1151             */
1152            if (parameter != null && !(parameter instanceof Key)) {
1153                throw new InvalidParameterException("Parameter should be of type Key");
1154            }
1155
1156            ensureSupportedKeysInitialized();
1157
1158            // No restriction specified by Provider registration.
1159            if (keyClasses == null && keyFormats == null) {
1160                return true;
1161            }
1162
1163            // Restriction specified by registration, so null is not acceptable.
1164            if (parameter == null) {
1165                return false;
1166            }
1167
1168            Key keyParam = (Key) parameter;
1169            if (keyClasses != null && isInArray(keyClasses, keyParam.getClass())) {
1170                return true;
1171            }
1172            if (keyFormats != null && isInArray(keyFormats, keyParam.getFormat())) {
1173                return true;
1174            }
1175
1176            return false;
1177        }
1178
1179        /**
1180         * Initialize the list of supported key classes and formats.
1181         */
1182        private void ensureSupportedKeysInitialized() {
1183            if (supportedKeysInitialized) {
1184                return;
1185            }
1186
1187            final String supportedClassesString = getAttribute(ATTR_SUPPORTED_KEY_CLASSES);
1188            if (supportedClassesString != null) {
1189                String[] keyClassNames = supportedClassesString.split("\\|");
1190                ArrayList<Class<?>> supportedClassList = new ArrayList<Class<?>>(
1191                        keyClassNames.length);
1192                final ClassLoader classLoader = getProvider().getClass().getClassLoader();
1193                for (String keyClassName : keyClassNames) {
1194                    try {
1195                        Class<?> keyClass = classLoader.loadClass(keyClassName);
1196                        if (Key.class.isAssignableFrom(keyClass)) {
1197                            supportedClassList.add(keyClass);
1198                        }
1199                    } catch (ClassNotFoundException ignored) {
1200                    }
1201                }
1202                keyClasses = supportedClassList.toArray(new Class<?>[supportedClassList.size()]);
1203            }
1204
1205            final String supportedFormatString = getAttribute(ATTR_SUPPORTED_KEY_FORMATS);
1206            if (supportedFormatString != null) {
1207                keyFormats = supportedFormatString.split("\\|");
1208            }
1209
1210            supportedKeysInitialized = true;
1211        }
1212
1213        /**
1214         * Check if an item is in the array. The array of supported key classes
1215         * and formats is usually just a length of 1, so a simple array is
1216         * faster than a Set.
1217         */
1218        private static <T> boolean isInArray(T[] itemList, T target) {
1219            if (target == null) {
1220                return false;
1221            }
1222            for (T item : itemList) {
1223                if (target.equals(item)) {
1224                    return true;
1225                }
1226            }
1227            return false;
1228        }
1229
1230        /**
1231         * Check if an item is in the array. The array of supported key classes
1232         * and formats is usually just a length of 1, so a simple array is
1233         * faster than a Set.
1234         */
1235        private static boolean isInArray(Class<?>[] itemList, Class<?> target) {
1236            if (target == null) {
1237                return false;
1238            }
1239            for (Class<?> item : itemList) {
1240                if (item.isAssignableFrom(target)) {
1241                    return true;
1242                }
1243            }
1244            return false;
1245        }
1246
1247        /**
1248         * Returns a string containing a concise, human-readable description of
1249         * this {@code Service}.
1250         *
1251         * @return a printable representation for this {@code Service}.
1252         */
1253        @Override
1254        public String toString() {
1255            String result = "Provider " + provider.getName() + " Service "
1256                    + type + "." + algorithm + " " + className;
1257            if (aliases != null) {
1258                result = result + "\nAliases " + aliases.toString();
1259            }
1260            if (attributes != null) {
1261                result = result + "\nAttributes " + attributes.toString();
1262            }
1263            return result;
1264        }
1265    }
1266
1267    private void readObject(java.io.ObjectInputStream in)
1268            throws NotActiveException, IOException, ClassNotFoundException {
1269        in.defaultReadObject();
1270        versionString = String.valueOf(version);
1271        providerNumber = -1;
1272    }
1273}
1274