001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2017-2018 microBean.
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
014 * implied.  See the License for the specific language governing
015 * permissions and limitations under the License.
016 */
017package org.microbean.configuration.api;
018
019import java.lang.reflect.Type;
020
021import java.util.Collection;
022import java.util.Collections; // for javadoc only
023import java.util.Iterator;
024import java.util.Map;
025import java.util.ServiceConfigurationError;
026import java.util.ServiceLoader;
027import java.util.Set;
028
029import java.util.logging.Level;
030import java.util.logging.Logger;
031
032/**
033 * A single source for configuration values suitable for an
034 * application.
035 *
036 * @author <a href="https://about.me/lairdnelson"
037 * target="_parent">Laird Nelson</a>
038 *
039 * @see #getValue(Map, String, Type, String)
040 */
041public abstract class Configurations {
042
043
044  /*
045   * Static fields.
046   */
047
048
049  private static volatile ServiceLoader<Configurations> configurationsLoader;
050
051
052  /*
053   * Instance fields.
054   */
055
056
057  /**
058   * A {@link Logger} for this {@link Configurations}.
059   *
060   * <p>This field is never {@code null}.</p>
061   *
062   * @see #createLogger()
063   */
064  protected final Logger logger;
065
066
067  /*
068   * Constructors.
069   */
070
071
072  /**
073   * Creates a new {@link Configurations}.
074   *
075   * @exception IllegalStateException if the {@link #createLogger()}
076   * method returns {@code null}
077   *
078   * @see #createLogger()
079   */
080  protected Configurations() {
081    super();
082    this.logger = this.createLogger();
083    if (this.logger == null) {
084      throw new IllegalStateException("createLogger() == null");
085    }
086  }
087
088
089  /*
090   * Instance methods.
091   */
092
093
094  /**
095   * Returns a {@link Logger} for use by this {@link Configurations}
096   * implementation.
097   *
098   * <p>This method never returns {@code null}.</p>
099   *
100   * <p>Overrides of this method must not return {@code null}.</p>
101   *
102   * @return a non-{@code null} {@link Logger}
103   */
104  protected Logger createLogger() {
105    return Logger.getLogger(this.getClass().getName());
106  }
107
108  /**
109   * Returns a non-{@code null}, {@linkplain
110   * Collections#unmodifiableSet(Set) immutable} {@link Set} of {@link
111   * Type}s representing all the types to which {@link String}-typed
112   * configuration values may be converted by this {@link
113   * Configurations} object.
114   *
115   * <p>This method never returns {@code null}.</p>
116   *
117   * @return a non-{@code null}, {@linkplain
118   * Collections#unmodifiableSet(Set) immutable} {@link Set} of {@link
119   * Type}s
120   *
121   * @see TypeLiteral
122   */
123  public abstract Set<Type> getConversionTypes();
124
125  /**
126   * Returns a {@link Map} of <em>configuration
127   * coordinates</em>&mdash;aspects and their values that define a
128   * location within which requests for configuration values may take
129   * place.
130   *
131   * <p>Implementations of this method may return {@code null}.</p>
132   *
133   * @return a {@link Map} of configuration coordinates; may be {@code
134   * null}
135   */
136  public abstract Map<String, String> getConfigurationCoordinates();
137
138  /**
139   * Returns a configuration value corresponding to the configuration
140   * property with the supplied {@code name}.
141   *
142   * <p>This method may return {@code null}.</p>
143   *
144   * @param name the name of the configuration property for which a
145   * value will be returned; must not be {@code null}
146   *
147   * @return the configuration value, or {@code null}
148   *
149   * @exception NullPointerException if {@code name} is {@code null}
150   *
151   * @exception AmbiguousConfigurationValuesException if two or more
152   * values were found that could be suitable
153   *
154   * @see #getValue(Map, String, Type, String)
155   */
156  public final String getValue(final String name) {
157    return this.getValue(this.getConfigurationCoordinates(), name, String.class, null);
158  }
159
160  /**
161   * Returns a configuration value corresponding to the configuration
162   * property with the supplied {@code name}, or the supplied {@code
163   * defaultValue} if otherwise {@code null} would be returned.
164   *
165   * <p>This method may return {@code null}.</p>
166   *
167   * @param name the name of the configuration property for which a
168   * value will be returned; must not be {@code null}
169   *
170   * @param defaultValue the value to return if otherwise {@code null}
171   * would be returned; may be {@code null}
172   *
173   * @return the configuration value, or {@code null}
174   *
175   * @exception NullPointerException if {@code name} is {@code null}
176   *
177   * @exception AmbiguousConfigurationValuesException if two or more
178   * values were found that could be suitable
179   *
180   * @see #getValue(Map, String, Type, String)
181   */
182  public final String getValue(final String name, final String defaultValue) {
183    return this.getValue(this.getConfigurationCoordinates(), name, String.class, defaultValue);
184  }
185
186  /**
187   * Returns a configuration value corresponding to the configuration
188   * property suitable for the supplied {@code
189   * configurationCoordinates} and {@code name}.
190   *
191   * <p>This method may return {@code null}.</p>
192   *
193   * @param configurationCoordinates a {@link Map} representing the
194   * configuration coordinates in effect for this request; may be
195   * {@code null}
196   *
197   * @param name the name of the configuration property for which a
198   * value will be returned; must not be {@code null}
199   *
200   * @return the configuration value, or {@code null}
201   *
202   * @exception NullPointerException if {@code name} is {@code null}
203   *
204   * @exception AmbiguousConfigurationValuesException if two or more
205   * values were found that could be suitable
206   *
207   * @see #getValue(Map, String, Type, String)
208   */
209  public final String getValue(final Map<String, String> configurationCoordinates, final String name) {
210    return this.getValue(configurationCoordinates, name, String.class, null);
211  }
212
213  /**
214   * Returns a configuration value corresponding to the configuration
215   * property suitable for the supplied {@code
216   * configurationCoordinates} and {@code name}, or the supplied
217   * {@code defaultValue} if otherwise {@code null} would be returned.
218   *
219   * <p>This method may return {@code null}.</p>
220   *
221   * @param configurationCoordinates a {@link Map} representing the
222   * configuration coordinates in effect for this request; may be
223   * {@code null}
224   *
225   * @param name the name of the configuration property for which a
226   * value will be returned; must not be {@code null}
227   *
228   * @param defaultValue the value to return if otherwise {@code null}
229   * would be returned; may be {@code null}
230   *
231   * @return the configuration value, or {@code null}
232   *
233   * @exception NullPointerException if {@code name} is {@code null}
234   *
235   * @exception AmbiguousConfigurationValuesException if two or more
236   * values were found that could be suitable
237   *
238   * @see #getValue(Map, String, Type, String)
239   */
240  public final String getValue(final Map<String, String> configurationCoordinates, final String name, final String defaultValue) {
241    return this.getValue(configurationCoordinates, name, String.class, defaultValue);
242  }
243
244  /**
245   * Returns a configuration value corresponding to the configuration
246   * property suitable for the supplied {@code name}, converted, if
247   * possible, to the supplied {@code type}.
248   *
249   * <p>This method may return {@code null}.</p>
250   *
251   * @param <T> the type to which a {@link String}-typed configuration
252   * value should be converted
253   *
254   * @param name the name of the configuration property for which a
255   * value will be returned; must not be {@code null}
256   *
257   * @param type a {@link Class} representing the type to which the
258   * configuration value will be converted; must not be {@code null}
259   *
260   * @return the configuration value, or {@code null}
261   *
262   * @exception NullPointerException if {@code name} or {@code type}
263   * is {@code null}
264   *
265   * @exception ConversionException if type conversion could not occur
266   * for any reason
267   *
268   * @exception AmbiguousConfigurationValuesException if two or more
269   * values were found that could be suitable
270   *
271   * @see #getValue(Map, String, Type, String)
272   */
273  public final <T> T getValue(final String name, final Class<T> type) {
274    return this.getValue(this.getConfigurationCoordinates(), name, type, null);
275  }
276
277  /**
278   * Returns a configuration value corresponding to the configuration
279   * property suitable for the supplied {@code name}, converted, if
280   * possible, to the supplied {@code type}.
281   *
282   * <p>This method may return {@code null}.</p>
283   *
284   * @param <T> the type to which a {@link String}-typed configuration
285   * value should be converted
286   *
287   * @param name the name of the configuration property for which a
288   * value will be returned; must not be {@code null}
289   *
290   * @param type a {@link Class} representing the type to which the
291   * configuration value will be converted; must not be {@code null}
292   *
293   * @param defaultValue the value that will be converted and returned
294   * if {@code null} would otherwise be returned; may be {@code null}
295   *
296   * @return the configuration value, or {@code null}
297   *
298   * @exception NullPointerException if {@code name} or {@code type}
299   * is {@code null}
300   *
301   * @exception ConversionException if type conversion could not occur
302   * for any reason
303   *
304   * @exception AmbiguousConfigurationValuesException if two or more
305   * values were found that could be suitable
306   *
307   * @see #getValue(Map, String, Type, String)
308   */
309  public final <T> T getValue(final String name, final Class<T> type, final String defaultValue) {
310    return this.getValue(this.getConfigurationCoordinates(), name, type, defaultValue);
311  }
312
313  /**
314   * Returns a configuration value corresponding to the configuration
315   * property suitable for the supplied {@code
316   * configurationCoordinates} and {@code name}, converted, if
317   * possible, to the supplied {@code type}.
318   *
319   * <p>This method may return {@code null}.</p>
320   *
321   * @param <T> the type to which a {@link String}-typed configuration
322   * value should be converted
323   *
324   * @param configurationCoordinates a {@link Map} representing the
325   * configuration coordinates in effect for this request; may be
326   * {@code null}
327   *
328   * @param name the name of the configuration property for which a
329   * value will be returned; must not be {@code null}
330   *
331   * @param type a {@link Class} representing the type to which the
332   * configuration value will be converted; must not be {@code null}
333   *
334   * @return the configuration value, or {@code null}
335   *
336   * @exception NullPointerException if {@code name} or {@code type}
337   * is {@code null}
338   *
339   * @exception ConversionException if type conversion could not occur
340   * for any reason
341   *
342   * @exception AmbiguousConfigurationValuesException if two or more
343   * values were found that could be suitable
344   *
345   * @see #getValue(Map, String, Type, String)
346   */
347  public final <T> T getValue(final Map<String, String> configurationCoordinates, final String name, final Class<T> type) {
348    return this.getValue(configurationCoordinates, name, (Type)type, null);
349  }
350
351  /**
352   * Returns a configuration value corresponding to the configuration
353   * property suitable for the supplied {@code
354   * configurationCoordinates} and {@code name}, or the supplied
355   * {@code defaultValue} if {@code null} would otherwise be returned,
356   * converted, if possible, to the supplied {@code type}.
357   *
358   * <p>This method may return {@code null}.</p>
359   *
360   * @param <T> the type to which a {@link String}-typed configuration
361   * value should be converted
362   *
363   * @param configurationCoordinates a {@link Map} representing the
364   * configuration coordinates in effect for this request; may be
365   * {@code null}
366   *
367   * @param name the name of the configuration property for which a
368   * value will be returned; must not be {@code null}
369   *
370   * @param type a {@link Class} representing the type to which the
371   * configuration value will be converted; must not be {@code null}
372   *
373   * @param defaultValue the value that will be converted and returned
374   * if {@code null} would otherwise be returned; may be {@code null}
375   *
376   * @return the configuration value, or {@code null}
377   *
378   * @exception NullPointerException if {@code name} or {@code type}
379   * is {@code null}
380   *
381   * @exception ConversionException if type conversion could not occur
382   * for any reason
383   *
384   * @exception AmbiguousConfigurationValuesException if two or more
385   * values were found that could be suitable
386   *
387   * @see #getValue(Map, String, Type, String)
388   */
389  public final <T> T getValue(final Map<String, String> configurationCoordinates, final String name, final Class<T> type, final String defaultValue) {
390    return this.getValue(configurationCoordinates, name, (Type)type, defaultValue);
391  }
392
393  /**
394   * Returns a configuration value corresponding to the configuration
395   * property suitable for the supplied {@code name}, converted, if
396   * possible, to the type represented by the supplied {@code
397   * typeLiteral}.
398   *
399   * <p>This method may return {@code null}.</p>
400   *
401   * @param <T> the type to which a {@link String}-typed configuration
402   * value should be converted
403   *
404   * @param name the name of the configuration property for which a
405   * value will be returned; must not be {@code null}
406   *
407   * @param typeLiteral a {@link TypeLiteral} representing the type to
408   * which the configuration value will be converted; must not be
409   * {@code null}
410   *
411   * @return the configuration value, or {@code null}
412   *
413   * @exception NullPointerException if {@code name} or {@code type}
414   * is {@code null}
415   *
416   * @exception ConversionException if type conversion could not occur
417   * for any reason
418   *
419   * @exception AmbiguousConfigurationValuesException if two or more
420   * values were found that could be suitable
421   *
422   * @see #getValue(Map, String, Type, String)
423   */
424  public final <T> T getValue(final String name, final TypeLiteral<T> typeLiteral) {
425    return this.getValue(this.getConfigurationCoordinates(), name, typeLiteral, null);
426  }
427
428  /**
429   * Returns a configuration value corresponding to the configuration
430   * property suitable for the supplied {@code name}, or the supplied
431   * {@code defaultValue} if {@code null} would otherwise be returned,
432   * converted, if possible, to the type represented by the supplied
433   * {@code typeLiteral}.
434   *
435   * <p>This method may return {@code null}.</p>
436   *
437   * @param <T> the type to which a {@link String}-typed configuration
438   * value should be converted
439   *
440   * @param name the name of the configuration property for which a
441   * value will be returned; must not be {@code null}
442   *
443   * @param typeLiteral a {@link TypeLiteral} representing the type to
444   * which the configuration value will be converted; must not be
445   * {@code null}
446   *
447   * @param defaultValue the value that will be converted and returned
448   * if {@code null} would otherwise be returned; may be {@code null}
449   *
450   * @return the configuration value, or {@code null}
451   *
452   * @exception NullPointerException if {@code name} or {@code type}
453   * is {@code null}
454   *
455   * @exception ConversionException if type conversion could not occur
456   * for any reason
457   *
458   * @exception AmbiguousConfigurationValuesException if two or more
459   * values were found that could be suitable
460   *
461   * @see #getValue(Map, String, Type, String)
462   */
463  public final <T> T getValue(final String name, final TypeLiteral<T> typeLiteral, final String defaultValue) {
464    return this.getValue(this.getConfigurationCoordinates(), name, typeLiteral, defaultValue);
465  }
466
467  /**
468   * Returns a configuration value corresponding to the configuration
469   * property suitable for the supplied {@code
470   * configurationCoordinates} and {@code name}, converted, if
471   * possible, to the type represented by the supplied {@code
472   * typeLiteral}.
473   *
474   * <p>This method may return {@code null}.</p>
475   *
476   * @param <T> the type to which a {@link String}-typed configuration
477   * value should be converted
478   *
479   * @param configurationCoordinates a {@link Map} representing the
480   * configuration coordinates in effect for this request; may be
481   * {@code null}
482   *
483   * @param name the name of the configuration property for which a
484   * value will be returned; must not be {@code null}
485   *
486   * @param typeLiteral a {@link TypeLiteral} representing the type to
487   * which the configuration value will be converted; must not be
488   * {@code null}
489   *
490   * @return the configuration value, or {@code null}
491   *
492   * @exception NullPointerException if {@code name} or {@code type}
493   * is {@code null}
494   *
495   * @exception ConversionException if type conversion could not occur
496   * for any reason
497   *
498   * @exception AmbiguousConfigurationValuesException if two or more
499   * values were found that could be suitable
500   *
501   * @see #getValue(Map, String, Type, String)
502   */
503  public final <T> T getValue(final Map<String, String> configurationCoordinates, final String name, final TypeLiteral<T> typeLiteral) {
504    return this.getValue(configurationCoordinates, name, typeLiteral == null ? (Type)null : typeLiteral.getType(), null);
505  }
506
507  /**
508   * Returns a configuration value corresponding to the configuration
509   * property suitable for the supplied {@code
510   * configurationCoordinates} and {@code name}, converted, if
511   * possible, to the type represented by the supplied {@code
512   * typeLiteral}.
513   *
514   * <p>This method may return {@code null}.</p>
515   *
516   * @param <T> the type to which a {@link String}-typed configuration
517   * value should be converted
518   *
519   * @param configurationCoordinates a {@link Map} representing the
520   * configuration coordinates in effect for this request; may be
521   * {@code null}
522   *
523   * @param name the name of the configuration property for which a
524   * value will be returned; must not be {@code null}
525   *
526   * @param typeLiteral a {@link TypeLiteral} representing the type to
527   * which the configuration value will be converted; must not be
528   * {@code null}
529   *
530   * @param defaultValue the value that will be converted and returned
531   * if {@code null} would otherwise be returned; may be {@code null}
532   *
533   * @return the configuration value, or {@code null}
534   *
535   * @exception NullPointerException if {@code name} or {@code type}
536   * is {@code null}
537   *
538   * @exception ConversionException if type conversion could not occur
539   * for any reason
540   *
541   * @exception AmbiguousConfigurationValuesException if two or more
542   * values were found that could be suitable
543   *
544   * @see #getValue(Map, String, Type, String)
545   */
546  public final <T> T getValue(final Map<String, String> configurationCoordinates, final String name, final TypeLiteral<T> typeLiteral, final String defaultValue) {
547    return this.getValue(configurationCoordinates, name, typeLiteral == null ? (Type)null : typeLiteral.getType(), defaultValue);
548  }
549
550  /**
551   * Returns a configuration value corresponding to the configuration
552   * property suitable for the supplied {@code name}, converted, if
553   * possible, to the type represented by the supplied {@code type}.
554   *
555   * <p>This method may return {@code null}.</p>
556   *
557   * @param <T> the type to which a {@link String}-typed configuration
558   * value should be converted
559   *
560   * @param name the name of the configuration property for which a
561   * value will be returned; must not be {@code null}
562   *
563   * @param type a {@link Type} representing the type to
564   * which the configuration value will be converted; must not be {@code null}
565   *
566   * @return the configuration value, or {@code null}
567   *
568   * @exception NullPointerException if {@code name} or {@code type}
569   * is {@code null}
570
571   *
572   * @exception ConversionException if type conversion could not occur
573   * for any reason
574   *
575   * @exception AmbiguousConfigurationValuesException if two or more
576   * values were found that could be suitable
577   *
578   * @see #getValue(Map, String, Type, String)
579   */
580  public final <T> T getValue(final String name, final Type type) {
581    return this.getValue(this.getConfigurationCoordinates(), name, type, null);
582  }
583
584  /**
585   * Returns a configuration value corresponding to the configuration
586   * property suitable for the supplied {@code name}, or the supplied
587   * {@code defaultValue} if otherwise {@code null} would be returned,
588   * converted, if possible, to the type represented by the supplied
589   * {@code type}.
590   *
591   * <p>This method may return {@code null}.</p>
592   *
593   * @param <T> the type to which a {@link String}-typed configuration
594   * value should be converted
595   *
596   * @param name the name of the configuration property for which a
597   * value will be returned; must not be {@code null}
598   *
599   * @param type a {@link Type} representing the type to
600   * which the configuration value will be converted; must not be {@code null}
601   *
602   * @param defaultValue the value that will be converted and returned
603   * if {@code null} would otherwise be returned; may be {@code null}
604   *
605   * @return the configuration value, or {@code null}
606   *
607   * @exception NullPointerException if {@code name} or {@code type}
608   * is {@code null}
609
610   *
611   * @exception ConversionException if type conversion could not occur
612   * for any reason
613   *
614   * @exception AmbiguousConfigurationValuesException if two or more
615   * values were found that could be suitable
616   *
617   * @see #getValue(Map, String, Type, String)
618   */
619  public final <T> T getValue(final String name, final Type type, final String defaultValue) {
620    return this.getValue(this.getConfigurationCoordinates(), name, type, defaultValue);
621  }
622
623  /**
624   * Returns a configuration value corresponding to the configuration
625   * property suitable for the supplied {@code
626   * configurationCoordinates} and {@code name}, converted, if
627   * possible, to the type represented by the supplied {@code type}.
628   *
629   * <p>This method may return {@code null}.</p>
630   *
631   * @param <T> the type to which a {@link String}-typed configuration
632   * value should be converted
633   *
634   * @param configurationCoordinates a {@link Map} representing the
635   * configuration coordinates in effect for this request; may be
636   * {@code null}
637   *
638   * @param name the name of the configuration property for which a
639   * value will be returned; must not be {@code null}
640   *
641   * @param type a {@link Type} representing the type to
642   * which the configuration value will be converted; must not be {@code null}
643   *
644   * @return the configuration value, or {@code null}
645   *
646   * @exception NullPointerException if {@code name} or {@code type}
647   * is {@code null}
648
649   *
650   * @exception ConversionException if type conversion could not occur
651   * for any reason
652   *
653   * @exception AmbiguousConfigurationValuesException if two or more
654   * values were found that could be suitable
655   *
656   * @see #getValue(Map, String, Type, String)
657   */
658  public final <T> T getValue(final Map<String, String> configurationCoordinates, final String name, final Type type) {
659    return this.getValue(configurationCoordinates, name, type, null);
660  }
661
662  /**
663   * Returns a configuration value corresponding to the configuration
664   * property suitable for the supplied {@code
665   * configurationCoordinates} and {@code name}, or the supplied
666   * {@code defaultValue} if {@code null} would otherwise be returned,
667   * converted, if possible, to the type represented by the supplied
668   * {@code type}.
669   *
670   * <p>This method may return {@code null}.</p>
671   *
672   * @param <T> the type to which a {@link String}-typed configuration
673   * value should be converted
674   *
675   * @param configurationCoordinates a {@link Map} representing the
676   * configuration coordinates in effect for this request; may be
677   * {@code null}
678   *
679   * @param name the name of the configuration property for which a
680   * value will be returned; must not be {@code null}
681   *
682   * @param type a {@link Type} representing the type to which the
683   * configuration value will be converted; must not be {@code null}
684   *
685   * @param defaultValue the value that will be converted and returned
686   * if {@code null} would otherwise be returned; may be {@code null}
687   *
688   * @return the configuration value, or {@code null}
689   *
690   * @exception NullPointerException if {@code name} or {@code type}
691   * is {@code null}
692   *
693   * @exception ConversionException if type conversion could not occur
694   * for any reason
695   *
696   * @exception AmbiguousConfigurationValuesException if two or more
697   * values were found that could be suitable
698   */
699  public abstract <T> T getValue(final Map<String, String> configurationCoordinates, final String name, final Type type, final String defaultValue);
700
701  /**
702   * Returns a configuration value corresponding to the configuration
703   * property suitable for the supplied {@code
704   * configurationCoordinates} and {@code name}, or the supplied
705   * {@code defaultValue} if {@code null} would otherwise be returned.
706   *
707   * <p>This method may return {@code null}.</p>
708   *
709   * @param configurationCoordinates a {@link Map} representing the
710   * configuration coordinates in effect for this request; may be
711   * {@code null}
712   *
713   * @param names names of configuration properties for which a value
714   * will be returned; each element will be tried in turn; must not be
715   * {@code null}
716   *
717   * @param defaultValue the value that will be returned if {@code
718   * null} would otherwise be returned; may be {@code null}
719   *
720   * @return the configuration value, or {@code null}
721   *
722   * @exception NullPointerException if {@code name} or {@code type}
723   * is {@code null}
724   *
725   * @exception AmbiguousConfigurationValuesException if two or more
726   * values were found that could be suitable
727   */
728  public final String getValue(final Map<String, String> configurationCoordinates, final Collection<String> names, final String defaultValue) {
729    return this.getValue(configurationCoordinates, names, String.class, defaultValue);
730  }
731
732  /**
733   * Returns a configuration value corresponding to the configuration
734   * property suitable for the supplied {@code
735   * configurationCoordinates} and {@code name}, or null.
736   *
737   * <p>This method may return {@code null}.</p>
738   *
739   * @param configurationCoordinates a {@link Map} representing the
740   * configuration coordinates in effect for this request; may be
741   * {@code null}
742   *
743   * @param names names of configuration properties for which a value
744   * will be returned; each element will be tried in turn; must not be
745   * {@code null}
746   *
747   * @return the configuration value, or {@code null}
748   *
749   * @exception NullPointerException if {@code names} or {@code type}
750   * is {@code null}
751   *
752   * @exception AmbiguousConfigurationValuesException if two or more
753   * values were found that could be suitable
754   */
755  public final String getValue(final Map<String, String> configurationCoordinates, final Collection<String> names) {
756    return this.getValue(configurationCoordinates, names, String.class, null);
757  }
758
759  /**
760   * Returns a configuration value corresponding to the configuration
761   * property suitable for the supplied {@code names}, or null.
762   *
763   * <p>This method may return {@code null}.</p>
764   *
765   * @param names names of configuration properties for which a value
766   * will be returned; each element will be tried in turn; must not be
767   * {@code null}
768   *
769   * @return the configuration value, or {@code null}
770   *
771   * @exception NullPointerException if {@code names} is {@code null}
772   *
773   * @exception AmbiguousConfigurationValuesException if two or more
774   * values were found that could be suitable
775   */
776  public final String getValue(final Collection<String> names) {
777    return this.getValue(this.getConfigurationCoordinates(), names, String.class, null);
778  }
779
780  /**
781   * Returns a configuration value corresponding to the configuration
782   * property suitable for the supplied {@code names}, or the supplied
783   * {@code defaultValue}.
784   *
785   * <p>This method may return {@code null}.</p>
786   *
787   * @param names names of configuration properties for which a value
788   * will be returned; each element will be tried in turn; must not be
789   * {@code null}
790   *
791   * @param defaultValue the value that will be returned if {@code
792   * null} would otherwise be returned; may be {@code null}
793   *
794   * @return the configuration value, or the supplied {@code
795   * defaultValue}
796   *
797   * @exception NullPointerException if {@code names} is {@code null}
798   *
799   * @exception AmbiguousConfigurationValuesException if two or more
800   * values were found that could be suitable
801   */
802  public final String getValue(final Collection<String> names, final String defaultValue) {
803    return this.getValue(this.getConfigurationCoordinates(), names, String.class, defaultValue);
804  }
805
806  /**
807   * Returns a configuration value corresponding to the configuration
808   * property suitable for the supplied {@code names}, converted, if
809   * possible, to the type represented by the supplied {@code
810   * type}, or the supplied {@code defaultValue}.
811   *
812   * <p>This method may return {@code null}.</p>
813   *
814   * @param <T> the type to which a {@link String}-typed configuration
815   * value should be converted
816   *
817   * @param names names of configuration properties for which a value
818   * will be returned; each element will be tried in turn; must not be
819   * {@code null}
820   *
821   * @param type a {@link Class} representing the type to which the
822   * configuration value will be converted; must not be {@code null}
823   *
824   * @param defaultValue the value that will be converted and returned
825   * if {@code null} would otherwise be returned; may be {@code null}
826   *
827   * @return the configuration value, or the supplied {@code
828   * defaultValue}
829   *
830   * @exception NullPointerException if {@code names} or {@code type}
831   * is {@code null}
832   *
833   * @exception ConversionException if type conversion could not occur
834   * for any reason
835   *
836   * @exception AmbiguousConfigurationValuesException if two or more
837   * values were found that could be suitable
838   *
839   * @see #getValue(Map, Collection, Type, String)
840   */
841  public final <T> T getValue(final Collection<String> names, final Class<T> type, final String defaultValue) {
842    return this.getValue(this.getConfigurationCoordinates(), names, type, defaultValue);
843  }
844
845  public final <T> T getValue(final Collection<String> names, final Type type, final String defaultValue) {
846    return this.getValue(this.getConfigurationCoordinates(), names, type, defaultValue);
847  }
848
849  public final <T> T getValue(final Collection<String> names, final TypeLiteral<T> typeLiteral, final String defaultValue) {
850    return this.getValue(this.getConfigurationCoordinates(), names, typeLiteral == null ? (Type)null : typeLiteral.getType(), defaultValue);
851  }
852
853  /**
854   * Returns a configuration value corresponding to the configuration
855   * property suitable for the supplied {@code names}, converted, if
856   * possible, to the type represented by the supplied {@code
857   * type}, or {@code null}.
858   *
859   * <p>This method may return {@code null}.</p>
860   *
861   * @param <T> the type to which a {@link String}-typed configuration
862   * value should be converted
863   *
864   * @param names names of configuration properties for which a value
865   * will be returned; each element will be tried in turn; must not be
866   * {@code null}
867   *
868   * @param type a {@link Class} representing the type to which the
869   * configuration value will be converted; must not be {@code null}
870   *
871   * @return the configuration value, or {@code null}
872   *
873   * @exception NullPointerException if {@code names} or {@code type}
874   * is {@code null}
875   *
876   * @exception ConversionException if type conversion could not occur
877   * for any reason
878   *
879   * @exception AmbiguousConfigurationValuesException if two or more
880   * values were found that could be suitable
881   *
882   * @see #getValue(Map, Collection, Type, String)
883   */
884  public final <T> T getValue(final Collection<String> names, final Class<T> type) {
885    return this.getValue(this.getConfigurationCoordinates(), names, type, null);
886  }
887
888  /**
889   * Returns a configuration value corresponding to the configuration
890   * property suitable for the supplied {@code names}, converted, if
891   * possible, to the type represented by the supplied {@code
892   * type}, or {@code null}.
893   *
894   * <p>This method may return {@code null}.</p>
895   *
896   * @param <T> the type to which a {@link String}-typed configuration
897   * value should be converted
898   *
899   * @param names names of configuration properties for which a value
900   * will be returned; each element will be tried in turn; must not be
901   * {@code null}
902   *
903   * @param type a {@link Type} representing the type to which the
904   * configuration value will be converted; must not be {@code null}
905   *
906   * @return the configuration value, or {@code null}
907   *
908   * @exception NullPointerException if {@code names} or {@code type}
909   * is {@code null}
910   *
911   * @exception ConversionException if type conversion could not occur
912   * for any reason
913   *
914   * @exception AmbiguousConfigurationValuesException if two or more
915   * values were found that could be suitable
916   *
917   * @see #getValue(Map, Collection, Type, String)
918   */
919  public final <T> T getValue(final Collection<String> names, final Type type) {
920    return this.getValue(this.getConfigurationCoordinates(), names, type, null);
921  }
922
923  /**
924   * Returns a configuration value corresponding to the configuration
925   * property suitable for the supplied {@code names}, converted, if
926   * possible, to the type represented by the supplied {@code
927   * typeLiteral}, or {@code null}.
928   *
929   * <p>This method may return {@code null}.</p>
930   *
931   * @param <T> the type to which a {@link String}-typed configuration
932   * value should be converted
933   *
934   * @param names names of configuration properties for which a value
935   * will be returned; each element will be tried in turn; must not be
936   * {@code null}
937   *
938   * @param typeLiteral a {@link TypeLiteral} representing the type to which the
939   * configuration value will be converted; must not be {@code null}
940   *
941   * @return the configuration value, or {@code null}
942   *
943   * @exception NullPointerException if {@code name} or {@code type}
944   * is {@code null}
945   *
946   * @exception ConversionException if type conversion could not occur
947   * for any reason
948   *
949   * @exception AmbiguousConfigurationValuesException if two or more
950   * values were found that could be suitable
951   *
952   * @see #getValue(Map, Collection, Type, String)
953   */
954  public final <T> T getValue(final Collection<String> names, final TypeLiteral<T> typeLiteral) {
955    return this.getValue(this.getConfigurationCoordinates(), names, typeLiteral == null ? (Type)null : typeLiteral.getType(), null);
956  }
957
958  /**
959   * Returns a configuration value corresponding to the configuration
960   * property suitable for the supplied {@code
961   * configurationCoordinates} and {@code names}, or the supplied
962   * {@code defaultValue} if {@code null} would otherwise be returned,
963   * converted, if possible, to the type represented by the supplied
964   * {@code type}.
965   *
966   * <p>This method may return {@code null}.</p>
967   *
968   * @param <T> the type to which a {@link String}-typed configuration
969   * value should be converted
970   *
971   * @param configurationCoordinates a {@link Map} representing the
972   * configuration coordinates in effect for this request; may be
973   * {@code null}
974   *
975   * @param names names of configuration properties for which a value
976   * will be returned; each element will be tried in turn; must not be
977   * {@code null}
978   *
979   * @param type a {@link Class} representing the type to which the
980   * configuration value will be converted; must not be {@code null}
981   *
982   * @param defaultValue the value that will be converted and returned
983   * if {@code null} would otherwise be returned; may be {@code null}
984   *
985   * @return the configuration value, or {@code null}
986   *
987   * @exception NullPointerException if {@code name} or {@code type}
988   * is {@code null}
989   *
990   * @exception ConversionException if type conversion could not occur
991   * for any reason
992   *
993   * @exception AmbiguousConfigurationValuesException if two or more
994   * values were found that could be suitable
995   *
996   * @see #getValue(Map, Collection, Type, String)
997   */
998  public final <T> T getValue(final Map<String, String> configurationCoordinates, final Collection<String> names, final Class<T> type, final String defaultValue) {
999    return this.getValue(configurationCoordinates, names, (Type)type, defaultValue);
1000  }
1001
1002   /**
1003   * Returns a configuration value corresponding to the configuration
1004   * property suitable for the supplied {@code
1005   * configurationCoordinates} and {@code names}, or the supplied
1006   * {@code defaultValue} if {@code null} would otherwise be returned,
1007   * converted, if possible, to the type represented by the supplied
1008   * {@code type}.
1009   *
1010   * <p>This method may return {@code null}.</p>
1011   *
1012   * @param <T> the type to which a {@link String}-typed configuration
1013   * value should be converted
1014   *
1015   * @param configurationCoordinates a {@link Map} representing the
1016   * configuration coordinates in effect for this request; may be
1017   * {@code null}
1018   *
1019   * @param names names of configuration properties for which a value
1020   * will be returned; each element will be tried in turn; must not be
1021   * {@code null}
1022   *
1023   * @param typeLiteral a {@link TypeLiteral} representing the type to which
1024   * the configuration value will be converted; must not be {@code
1025   * null}
1026   *
1027   * @param defaultValue the value that will be converted and returned
1028   * if {@code null} would otherwise be returned; may be {@code null}
1029   *
1030   * @return the configuration value, or {@code null}
1031   *
1032   * @exception NullPointerException if {@code name} or {@code type}
1033   * is {@code null}
1034   *
1035   * @exception ConversionException if type conversion could not occur
1036   * for any reason
1037   *
1038   * @exception AmbiguousConfigurationValuesException if two or more
1039   * values were found that could be suitable
1040   *
1041   * @see #getValue(Map, Collection, Type, String)
1042   */
1043  public final <T> T getValue(final Map<String, String> configurationCoordinates, final Collection<String> names, final TypeLiteral<T> typeLiteral, final String defaultValue) {
1044    return this.getValue(configurationCoordinates, names, typeLiteral == null ? (Type)null : typeLiteral.getType(), defaultValue);
1045  }
1046
1047  /**
1048   * Returns a configuration value corresponding to the configuration
1049   * property suitable for the supplied {@code
1050   * configurationCoordinates} and {@code names}, or the supplied
1051   * {@code defaultValue} if {@code null} would otherwise be returned,
1052   * converted, if possible, to the type represented by the supplied
1053   * {@code type}.
1054   *
1055   * <p>This method may return {@code null}.</p>
1056   *
1057   * @param <T> the type to which a {@link String}-typed configuration
1058   * value should be converted
1059   *
1060   * @param configurationCoordinates a {@link Map} representing the
1061   * configuration coordinates in effect for this request; may be
1062   * {@code null}
1063   *
1064   * @param names names of configuration properties for which a value
1065   * will be returned; each element will be tried in turn; must not be
1066   * {@code null}
1067   *
1068   * @param type a {@link Type} representing the type to which the
1069   * configuration value will be converted; must not be {@code null}
1070   *
1071   * @param defaultValue the value that will be converted and returned
1072   * if {@code null} would otherwise be returned; may be {@code null}
1073   *
1074   * @return the configuration value, or {@code null}
1075   *
1076   * @exception NullPointerException if {@code name} or {@code type}
1077   * is {@code null}
1078   *
1079   * @exception ConversionException if type conversion could not occur
1080   * for any reason
1081   *
1082   * @exception AmbiguousConfigurationValuesException if two or more
1083   * values were found that could be suitable
1084   *
1085   * @see #getValue(Map, String, Type, String)
1086   */
1087  public final <T> T getValue(final Map<String, String> configurationCoordinates, final Collection<String> names, final Type type, final String defaultValue) {
1088    final String cn = this.getClass().getName();
1089    final Logger logger = Logger.getLogger(cn);
1090    final String mn = "getValue";
1091    if (logger.isLoggable(Level.FINER)) {
1092      logger.entering(cn, mn, new Object[] { configurationCoordinates, names, type, defaultValue });
1093    }
1094
1095    T returnValue = null;
1096    if (names == null || names.isEmpty()) {
1097      returnValue = this.getValue(configurationCoordinates, (String)null, type, defaultValue);
1098    } else {
1099      // We need two passes.  The first pass will use null as a
1100      // default value and will keep going if null is returned by the
1101      // abstract getValue(Map, String, Type, String) implementation.
1102      for (final String name : names) {
1103        returnValue = this.getValue(configurationCoordinates, name, type, null);
1104        if (returnValue != null) {
1105          break;
1106        }
1107      }
1108      if (returnValue == null) {
1109        // We didn't find any values.  Do it again, but this time with
1110        // the defaultValue.
1111        for (final String name : names) {
1112          returnValue = this.getValue(configurationCoordinates, name, type, defaultValue);
1113          if (returnValue != null) {
1114            break;
1115          }
1116        }
1117      }
1118    }
1119
1120    if (logger.isLoggable(Level.FINER)) {
1121      logger.exiting(cn, mn, returnValue);
1122    }
1123    return returnValue;
1124  }
1125
1126  /**
1127   * Returns an {@linkplain Collections#unmodifiableSet(Set)
1128   * unmodifiable <code>Set</code>} of names of {@link
1129   * ConfigurationValue}s that might be returned by this {@link
1130   * Configurations} instance.
1131   *
1132   * <p>Implementations of this method must not return {@code
1133   * null}.</p>
1134   *
1135   * <p>Just because a name appears in the returned {@link Set} does
1136   * <em>not</em> mean that a {@link ConfigurationValue} <em>will</em>
1137   * be returned for it in a location in configuration space
1138   * identified by any arbitrary set of configuration coordinates.</p>
1139   *
1140   * @return a non-{@code null} {@link Set} of names of {@link
1141   * ConfigurationValue}s
1142   */
1143  public abstract Set<String> getNames();
1144
1145
1146  /*
1147   * Static methods.
1148   */
1149
1150
1151  /**
1152   * Returns a {@link Configurations} implementation found using the
1153   * standard {@link ServiceLoader} mechanism.
1154   *
1155   * <p>The first entry in the first classpath resource named {@code
1156   * META-INF/services/org.microbean.configuration.api.Configurations}
1157   * will be treated as a class name, loaded and instantiated, and
1158   * returned.</p>
1159   *
1160   * @return a non-{@code null} {@link Configurations} instance
1161   *
1162   * @exception ConfigurationException if there was no implementation
1163   * found
1164   *
1165   * @see ServiceLoader
1166   */
1167  public static final Configurations newInstance() {
1168    final String cn = Configurations.class.getName();
1169    final Logger logger = Logger.getLogger(cn);
1170    final String mn = "newInstance";
1171    if (logger.isLoggable(Level.FINER)) {
1172      logger.entering(cn, mn);
1173    }
1174    Configurations returnValue = null;
1175    ServiceLoader<Configurations> configurationsLoader = Configurations.configurationsLoader;
1176    if (configurationsLoader == null) {
1177      configurationsLoader = ServiceLoader.load(Configurations.class);
1178      assert configurationsLoader != null;
1179      Configurations.configurationsLoader = configurationsLoader;
1180    }
1181    final Iterator<Configurations> configurationsIterator = configurationsLoader.iterator();
1182    assert configurationsIterator != null;
1183    while (returnValue == null && configurationsIterator.hasNext()) {
1184      try {
1185        returnValue = configurationsIterator.next();
1186      } catch (final ServiceConfigurationError badServiceProviderFile) {
1187        throw new ConfigurationException(badServiceProviderFile);
1188      }
1189    }
1190    if (returnValue == null) {
1191      throw new ConfigurationException("No " + Configurations.class.getName() + " implementation found in any META-INF/services/" + Configurations.class.getName() + " service provider resources");
1192    }
1193    if (logger.isLoggable(Level.FINER)) {
1194      logger.exiting(cn, mn, returnValue);
1195    }
1196    return returnValue;
1197  }
1198
1199}