001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2017–2019 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;
018
019import java.beans.FeatureDescriptor;
020
021import java.lang.reflect.Type;
022
023import java.util.Collection;
024import java.util.Collections;
025import java.util.Comparator;
026import java.util.HashMap;
027import java.util.HashSet;
028import java.util.Iterator;
029import java.util.LinkedList;
030import java.util.Map;
031import java.util.Objects;
032import java.util.PriorityQueue;
033import java.util.Queue;
034import java.util.ServiceLoader;
035import java.util.Set;
036import java.util.TreeSet;
037
038import java.util.function.Function;
039
040import java.util.logging.Level;
041import java.util.logging.Logger;
042
043import java.util.stream.Collectors;
044
045import javax.el.ELContext;
046import javax.el.ELResolver;
047import javax.el.ExpressionFactory;
048import javax.el.PropertyNotFoundException;
049import javax.el.StandardELContext;
050import javax.el.ValueExpression;
051
052import org.microbean.configuration.api.AmbiguousConfigurationValuesException;
053import org.microbean.configuration.api.ConfigurationException;
054import org.microbean.configuration.api.ConfigurationValue;
055import org.microbean.configuration.api.ConversionException;
056import org.microbean.configuration.api.TypeLiteral;
057
058import org.microbean.configuration.spi.Arbiter;
059import org.microbean.configuration.spi.Configuration;
060import org.microbean.configuration.spi.Converter;
061
062/**
063 * An implementation of the {@link
064 * org.microbean.configuration.api.Configurations} class that serves
065 * as a single source for configuration values suitable for an
066 * application.
067 *
068 * @author <a href="https://about.me/lairdnelson"
069 * target="_parent">Laird Nelson</a>
070 *
071 * @see Configuration
072 */
073public class Configurations extends org.microbean.configuration.api.Configurations {
074
075
076  /*
077   * Static fields.
078   */
079
080  
081  /**
082   * A {@link Comparator} of {@link ConfigurationValue}s that reverse
083   * sorts them according to their {@linkplain
084   * ConfigurationValue#specificity() specificity}.
085   *
086   * <p>This field is never {@code null}.</p>
087   */
088  private static final Comparator<ConfigurationValue> configurationValueComparator = Comparator.<ConfigurationValue>comparingInt(v -> v.specificity()).reversed();
089
090  /**
091   * A {@link ThreadLocal} tracking a {@link Set} of {@link
092   * Configuration}s that are currently in the process of executing
093   * their {@link Configuration#getValue(Map, String)} methods.
094   *
095   * <p>This field is never {@code null} but its contents might be
096   * {@code null}.</p>
097   *
098   * @see #getValue(Map, String, Converter, String)
099   *
100   * @see #activate(Configuration)
101   *
102   * @see #deactivate(Configuration)
103   *
104   * @see #isActive(Configuration)
105   */
106  private static final ThreadLocal<Map<Configurations, Set<Configuration>>> staticCurrentlyActiveConfigurations = ThreadLocal.withInitial(() -> new HashMap<>());
107  
108  /**
109   * A {@link ServiceLoader} instance used by the {@link
110   * #loadConfigurations()} method.
111   *
112   * <p>This field may be {@code null}.</p>
113   *
114   * @see #loadConfigurations()
115   *
116   * @see ServiceLoader
117   */
118  private static volatile ServiceLoader<Configuration> configurationLoader;
119
120  /**
121   * A {@link ServiceLoader} instance used by the {@link
122   * #loadConverters()} method.
123   *
124   * <p>This field may be {@code null}.</p>
125   *
126   * @see #loadConverters()
127   *
128   * @see ServiceLoader
129   */
130  @SuppressWarnings("rawtypes")
131  private static volatile ServiceLoader<Converter> converterLoader;
132
133  /**
134   * A {@link ServiceLoader} instance used by the {@link
135   * #loadArbiters()} method.
136   *
137   * <p>This field may be {@code null}.</p>
138   *
139   * @see #loadArbiters()
140   *
141   * @see ServiceLoader
142   */
143  @SuppressWarnings("rawtypes")
144  private static volatile ServiceLoader<Arbiter> arbiterLoader;
145  
146  /**
147   * The name of the configuration property whose value is a {@link
148   * Map} of <em>configuration coordinates</em> for the current
149   * application.
150   *
151   * <p>This field is never {@code null}.</p>
152   *
153   * <p>A request is made via the {@link #getValue(Map, String, Type)}
154   * method with {@code null} as the value of its first parameter and
155   * the value of this field as its second parameter and {@link Map
156   * Map.class} as the value of its third parameter.  The returned
157   * {@link Map} is cached for the lifetime of this {@link
158   * Configurations} object and is returned by the {@link
159   * #getConfigurationCoordinates()} method.</p>
160   *
161   * @see #getValue(Map, String, Type)
162   *
163   * @see #getConfigurationCoordinates()
164   */
165  public static final String CONFIGURATION_COORDINATES = "configurationCoordinates";
166
167  /**
168   * An {@linkplain Collections#unmodifiableMap(Map) immutable} {@link
169   * Map} of "wrapper" {@link Class} instances indexed by their
170   * {@linkplain Class#isPrimitive() primitive} counterparts.
171   *
172   * <p>This field is never {@code null}.</p>
173   */
174  private static final Map<Class<?>, Class<?>> wrapperTypes;
175
176  static {
177    final Map<Class<?>, Class<?>> map = new HashMap<>();
178    map.put(boolean.class, Boolean.class);
179    map.put(byte.class, Byte.class);
180    map.put(char.class, Character.class);
181    map.put(double.class, Double.class);
182    map.put(float.class, Float.class);
183    map.put(int.class, Integer.class);
184    map.put(long.class, Long.class);
185    map.put(short.class, Short.class);
186    map.put(void.class, Void.class);
187    wrapperTypes = Collections.unmodifiableMap(map);
188  }
189
190
191  /*
192   * Instance fields.
193   */
194  
195  
196  /**
197   * Whether this {@link Configurations} has been initialized.
198   *
199   * @see #Configurations(Collection, Collection, Collection)
200   */
201  private final boolean initialized;
202
203  /**
204   * The {@link Collection} of {@link Configuration} instances that
205   * can {@linkplain Configuration#getValue(Map, String) provide}
206   * configuration values.
207   *
208   * <p>This field is never {@code null}.</p>
209   *
210   * @see #Configurations(Collection, Collection, Collection)
211   */
212  private final Collection<Configuration> configurations;
213
214  /**
215   * The {@link Collection} of {@link Arbiter}s that can resolve
216   * otherwise ambiguous configuration values.
217   *
218   * <p>This field is never {@code null}.</p>
219   *
220   * @see #Configurations(Collection, Collection, Collection)
221   */
222  private final Collection<Arbiter> arbiters;
223
224  /**
225   * A {@link Map} of {@link Converter} instances, indexed under
226   * {@linkplain Converter#getType() their <code>Type</code>}.
227   *
228   * <p>This field is never {@code null}.</p>
229   *
230   * @see #Configurations(Collection, Collection, Collection)
231   */
232  private final Map<Type, Converter<?>> converters;
233
234  /**
235   * A {@link Map} representing the <em>configuration coordinates</em>
236   * of the application using this {@link Configurations}.
237   *
238   * <p>This field may be {@code null}.</p>
239   *
240   * @see #getConfigurationCoordinates()
241   */
242  private final Map<String, String> configurationCoordinates;
243
244  /**
245   * An {@link ELContext} used for Expression Language evaluation.
246   *
247   * <p>This field is never {@code null}.</p>
248   */
249  private final ELContext elContext;
250
251  /**
252   * An {@link ExpressionFactory} used for Expression Language
253   * evaluation.
254   *
255   * <p>This field is never {@code null}.</p>
256   */
257  private final ExpressionFactory expressionFactory;
258
259
260  /*
261   * Constructors.
262   */
263
264
265  /**
266   * Creates a new {@link Configurations}.
267   *
268   * <p>The {@link #loadConfigurations()}, {@link #loadConverters()}
269   * and {@link #loadArbiters()} methods will be invoked during
270   * construction.</p>
271   *
272   * @see #loadConfigurations()
273   *
274   * @see #loadConverters()
275   *
276   * @see #loadArbiters()
277   *
278   * @see #Configurations(Collection, Collection, Collection)
279   */
280  public Configurations() {
281    this(null, null, null);
282  }
283
284  /**
285   * Creates a new {@link Configurations}.
286   *
287   * <p>The {@link #loadConverters()} and {@link #loadArbiters()}
288   * methods will be invoked during construction.  IF the supplied
289   * {@code configurations} is {@code null}, then the {@link
290   * #loadConfigurations()} method will be invoked during
291   * construction.</p>
292   *
293   * @param configurations a {@link Collection} of {@link
294   * Configuration} instances; if {@code null} then the return value
295   * of the {@link #loadConfigurations()} method will be used instead
296   *
297   * @see #loadConfigurations()
298   *
299   * @see #loadConverters()
300   *
301   * @see #loadArbiters()
302   *
303   * @see #Configurations(Collection, Collection, Collection)
304   */
305  public Configurations(final Collection<? extends Configuration> configurations) {
306    this(configurations, null, null);
307  }
308
309  /**
310   * Creates a new {@link Configurations}.
311   *
312   * <p>The {@link #loadConverters()} and {@link #loadArbiters()}
313   * methods will be invoked during construction.  IF the supplied
314   * {@code configurations} is {@code null}, then the {@link
315   * #loadConfigurations()} method will be invoked during
316   * construction.</p>
317   *
318   * @param configurations a {@link Collection} of {@link
319   * Configuration} instances; if {@code null} then the return value
320   * of the {@link #loadConfigurations()} method will be used instead
321   *
322   * @param converters a {@link Collection} of {@link Converter}
323   * instances; if {@code null} then the return value of the {@link
324   * #loadConverters()} method will be used instead
325   *
326   * @param arbiters a {@link Collection} of {@link Arbiter}
327   * instances; if {@code null} then the return value of the {@link
328   * #loadArbiters()} method will be used instead
329   *
330   * @see #loadConfigurations()
331   *
332   * @see #loadConverters()
333   *
334   * @see #loadArbiters()
335   */
336  public Configurations(Collection<? extends Configuration> configurations,
337                        Collection<? extends Converter<?>> converters,
338                        Collection<? extends Arbiter> arbiters) {
339    super();
340
341    this.expressionFactory = ExpressionFactory.newInstance();
342    assert this.expressionFactory != null;
343    final StandardELContext standardElContext = new StandardELContext(this.expressionFactory);
344    standardElContext.addELResolver(new ConfigurationELResolver());
345    this.elContext = standardElContext;
346    
347    if (configurations == null) {
348      configurations = this.loadConfigurations();
349    }
350    if (configurations == null || configurations.isEmpty()) {
351      this.configurations = Collections.emptySet();
352    } else {
353      this.configurations = Collections.unmodifiableCollection(new LinkedList<>(configurations));
354    }
355    for (final Configuration configuration : configurations) {
356      if (configuration != null) {
357        configuration.setConfigurations(this);
358      }
359    }
360
361    if (converters == null) {
362      converters = this.loadConverters();
363    }
364    if (converters == null || converters.isEmpty()) {
365      converters = Collections.emptySet();
366    } else {
367      converters = Collections.unmodifiableCollection(new LinkedList<>(converters));
368    }
369    assert converters != null;
370    this.converters = Collections.unmodifiableMap(converters.stream().collect(Collectors.toMap(c -> c.getType(), Function.identity())));
371
372    if (arbiters == null) {
373      arbiters = this.loadArbiters();
374    }
375    if (arbiters == null || arbiters.isEmpty()) {
376      this.arbiters = Collections.emptySet();
377    } else {
378      this.arbiters = Collections.unmodifiableCollection(new LinkedList<>(arbiters));
379    }
380
381    this.initialized = true;
382    
383    final Map<String, String> coordinates = this.getValue(null, CONFIGURATION_COORDINATES, new TypeLiteral<Map<String, String>>() {
384        private static final long serialVersionUID = 1L; }.getType());
385    if (coordinates == null || coordinates.isEmpty()) {
386      this.configurationCoordinates = Collections.emptyMap();
387    } else {
388      this.configurationCoordinates = Collections.unmodifiableMap(coordinates);
389    }
390
391  }
392
393
394  /*
395   * Instance methods.
396   */
397  
398
399  /**
400   * Loads a {@link Collection} of {@link Configuration} objects and
401   * returns it.
402   *
403   * <p>This method never returns {@code null}.</p>
404   *
405   * <p>Overrides of this method must not return {@code null}.</p>
406   *
407   * <p>The default implementation of this method uses the {@link
408   * ServiceLoader} mechanism to load {@link Configuration}
409   * instances.</p>
410   *
411   * @return a non-{@code null}, {@link Collection} of {@link
412   * Configuration} instances
413   *
414   * @see ServiceLoader#load(Class)
415   */
416  protected Collection<? extends Configuration> loadConfigurations() {
417    final String cn = this.getClass().getName();
418    final String mn = "loadConfigurations";
419    if (this.logger.isLoggable(Level.FINER)) {
420      this.logger.entering(cn, mn);
421    }
422    final Collection<Configuration> returnValue = new LinkedList<>();
423    ServiceLoader<Configuration> configurationLoader = Configurations.configurationLoader;
424    if (configurationLoader == null) {
425      configurationLoader = ServiceLoader.load(Configuration.class);
426      assert configurationLoader != null;
427      Configurations.configurationLoader = configurationLoader;
428    }
429    final Iterator<Configuration> configurationIterator = configurationLoader.iterator();
430    assert configurationIterator != null;
431    while (configurationIterator.hasNext()) {
432      final Configuration configuration = configurationIterator.next();
433      assert configuration != null;
434      returnValue.add(configuration);
435    }
436    if (this.logger.isLoggable(Level.FINER)) {
437      this.logger.exiting(cn, mn, returnValue);
438    }
439    return returnValue;
440  }
441
442  /**
443   * Loads a {@link Collection} of {@link Converter} objects and
444   * returns it.
445   *
446   * <p>This method never returns {@code null}.</p>
447   *
448   * <p>Overrides of this method must not return {@code null}.</p>
449   *
450   * <p>The default implementation of this method uses the {@link
451   * ServiceLoader} mechanism to load {@link Converter} instances.</p>
452   *
453   * @return a non-{@code null}, {@link Collection} of {@link
454   * Converter} instances
455   *
456   * @see ServiceLoader#load(Class)
457   */
458  protected Collection<? extends Converter<?>> loadConverters() {
459    final String cn = this.getClass().getName();
460    final String mn = "loadConverters";
461    if (this.logger.isLoggable(Level.FINER)) {
462      this.logger.entering(cn, mn);
463    }
464    final Collection<Converter<?>> returnValue = new LinkedList<>();
465    @SuppressWarnings("rawtypes")
466    ServiceLoader<Converter> converterLoader = Configurations.converterLoader;
467    if (converterLoader == null) {
468      converterLoader = ServiceLoader.load(Converter.class);
469      assert converterLoader != null;
470      Configurations.converterLoader = converterLoader;
471    }
472    @SuppressWarnings("rawtypes")
473    final Iterator<Converter> converterIterator = converterLoader.iterator();
474    assert converterIterator != null;
475    while (converterIterator.hasNext()) {
476      final Converter<?> converter = converterIterator.next();
477      assert converter != null;
478      returnValue.add(converter);
479    }
480    if (this.logger.isLoggable(Level.FINER)) {
481      this.logger.exiting(cn, mn, returnValue);
482    }
483    return returnValue;
484  }
485
486  /**
487   * Loads a {@link Collection} of {@link Arbiter} objects and returns
488   * it.
489   *
490   * <p>This method never returns {@code null}.</p>
491   *
492   * <p>Overrides of this method must not return {@code null}.</p>
493   *
494   * <p>The default implementation of this method uses the {@link
495   * ServiceLoader} mechanism to load {@link Arbiter} instances.</p>
496   *
497   * @return a non-{@code null}, {@link Collection} of {@link Arbiter}
498   * instances
499   *
500   * @see ServiceLoader#load(Class)
501   */
502  protected Collection<? extends Arbiter> loadArbiters() {
503    final String cn = this.getClass().getName();
504    final String mn = "loadArbiters";
505    if (this.logger.isLoggable(Level.FINER)) {
506      this.logger.entering(cn, mn);
507    }
508    final Collection<Arbiter> returnValue = new LinkedList<>();
509    ServiceLoader<Arbiter> arbiterLoader = Configurations.arbiterLoader;
510    if (arbiterLoader == null) {
511      arbiterLoader = ServiceLoader.load(Arbiter.class);
512      assert arbiterLoader != null;
513      Configurations.arbiterLoader = arbiterLoader;
514    }
515    final Iterator<Arbiter> arbiterIterator = arbiterLoader.iterator();
516    assert arbiterIterator != null;
517    while (arbiterIterator.hasNext()) {
518      final Arbiter arbiter = arbiterIterator.next();
519      assert arbiter != null;
520      returnValue.add(arbiter);
521    }
522    if (this.logger.isLoggable(Level.FINER)) {
523      this.logger.exiting(cn, mn, returnValue);
524    }
525    return returnValue;
526  }
527
528  /**
529   * Returns a {@link Map} of <em>configuration
530   * coordinates</em>&mdash;aspects and their values that define a
531   * location within which requests for configuration values may take
532   * place.
533   *
534   * <p>This method may return {@code null}.</p>
535   *
536   * <p>Overrides of this method may return {@code null}.</p>
537   *
538   * <p>The default implementation of this method returns
539   * configuration coordinates that are discovered at {@linkplain
540   * #Configurations() construction time} and cached for the lifetime
541   * of this {@link Configurations} object.</p>
542   *
543   * @return a {@link Map} of configuration coordinates; may be {@code
544   * null}
545   */
546  @Override
547  public Map<String, String> getConfigurationCoordinates() {
548    return this.configurationCoordinates;
549  }
550
551  /**
552   * Returns a non-{@code null}, {@linkplain
553   * Collections#unmodifiableSet(Set) immutable} {@link Set} of {@link
554   * Type}s representing all the types to which {@link String}
555   * configuration values may be converted by the {@linkplain
556   * #loadConverters() <code>Converter</code>s loaded} by this {@link
557   * Configurations} object.
558   *
559   * <p>This method never returns {@code null}.</p>
560   *
561   * @return a non-{@code null}, {@linkplain
562   * Collections#unmodifiableSet(Set) immutable} {@link Set} of {@link
563   * Type}s
564   */
565  @Override
566  public final Set<Type> getConversionTypes() {
567    this.checkState();
568    return this.converters.keySet();
569  }
570
571  /**
572   * Returns a configuration value corresponding to the configuration
573   * property suitable for the supplied {@code
574   * configurationCoordinates} and {@code name}, or the supplied
575   * {@code defaultValue} if {@code null} would otherwise be returned,
576   * converted, if possible, to the type represented by the supplied
577   * {@code type}.
578   *
579   * <p>This method may return {@code null}.</p>
580   *
581   * @param <T> the type to which a {@link String}-typed configuration
582   * value should be converted
583   *
584   * @param configurationCoordinates a {@link Map} representing the
585   * configuration coordinates in effect for this request; may be
586   * {@code null}
587   *
588   * @param name the name of the configuration property for which a
589   * value will be returned; must not be {@code null}
590   *
591   * @param type a {@link Type} representing the type to
592   * which the configuration value will be {@linkplain
593   * Converter#convert(String) converted}; must not be {@code null}
594   *
595   * @param defaultValue the value that will be converted if {@code
596   * null} would otherwise be returned; may be {@code null}
597   *
598   * @return the configuration value, or {@code null}
599   *
600   * @exception NullPointerException if {@code name} or {@code type}
601   * is {@code null}
602   *
603   * @exception NoSuchConverterException if there is no {@link
604   * Converter} available that {@linkplain Converter#getType()
605   * handles} the {@link Type} represented by the supplied {@code
606   * type}
607   *
608   * @exception ConversionException if type conversion could not occur
609   * for any reason
610   *
611   * @exception AmbiguousConfigurationValuesException if two or more
612   * values were found that could be suitable and arbitration
613   * {@linkplain #performArbitration(Map, String, Collection) was
614   * performed} but could not resolve the dispute
615   *
616   * @exception ConfigurationException if any other
617   * configuration-related error occurs
618   *
619   * @see #getValue(Map, String, Converter, String)
620   */
621  @Override
622  public final <T> T getValue(final Map<String, String> configurationCoordinates, final String name, Type type, final String defaultValue) {
623    final String cn = this.getClass().getName();
624    final String mn = "getValue";
625    if (this.logger.isLoggable(Level.FINER)) {
626      this.logger.entering(cn, mn, new Object[] { configurationCoordinates, name, type, defaultValue });
627    }
628    if (type instanceof Class) {
629      final Class<?> c = (Class<?>)type;
630      if (c.isPrimitive()) {
631        type = wrapperTypes.get(c);
632      }
633    }
634    @SuppressWarnings("unchecked")
635    final Converter<T> converter = (Converter<T>)this.converters.get(type);
636    if (converter == null) {
637      throw new NoSuchConverterException(type);
638    }
639    if (this.logger.isLoggable(Level.FINE)) {
640      this.logger.logp(Level.FINE, cn, mn, "Using {0} to convert String to {1}", new Object[] { converter, type });
641    }
642    final T returnValue = this.getValue(configurationCoordinates, name, converter, defaultValue);
643    if (this.logger.isLoggable(Level.FINER)) {
644      this.logger.exiting(cn, mn, returnValue);
645    }
646    return returnValue;
647  }
648
649  /**
650   * Returns a configuration value corresponding to the configuration
651   * property suitable for the supplied {@code name}, as {@linkplain
652   * Converter#convert(String) converted} by the supplied {@link
653   * Converter}.
654   *
655   * <p>This method may return {@code null}.</p>
656   *
657   * @param <T> the type to which a {@link String}-typed configuration
658   * value should be converted
659   *
660   * @param name the name of the configuration property for which a
661   * value will be returned; must not be {@code null}
662   *
663   * @param converter a {@link Converter} instance that will convert
664   * any {@link String} configuration value into the type of object
665   * that this method will return; must not be {@code null}
666   *
667   * @return the configuration value, or {@code null}
668   *
669   * @exception NullPointerException if {@code name} or {@code
670   * converter} is {@code null}
671   *
672   * @exception ConversionException if type conversion could not occur
673   * for any reason
674   *
675   * @exception AmbiguousConfigurationValuesException if two or more
676   * values were found that could be suitable and arbitration
677   * {@linkplain #performArbitration(Map, String, Collection) was
678   * performed} but could not resolve the dispute
679   *
680   * @see #getValue(Map, String, Converter, String)
681   */
682  public final <T> T getValue(final String name, final Converter<T> converter) {
683    return this.getValue(this.getConfigurationCoordinates(), name, converter, null);
684  }
685
686  /**
687   * Returns a configuration value corresponding to the configuration
688   * property suitable for the supplied {@code
689   * configurationCoordinates} and {@code name}, as {@linkplain
690   * Converter#convert(String) converted} by the supplied {@link
691   * Converter}.
692   *
693   * <p>This method may return {@code null}.</p>
694   *
695   * @param <T> the type to which a {@link String}-typed configuration
696   * value should be converted
697   *
698   * @param configurationCoordinates a {@link Map} representing the
699   * configuration coordinates in effect for this request; may be
700   * {@code null}
701   *
702   * @param name the name of the configuration property for which a
703   * value will be returned; must not be {@code null}
704   *
705   * @param converter a {@link Converter} instance that will convert
706   * any {@link String} configuration value into the type of object
707   * that this method will return; must not be {@code null}
708   *
709   * @return the configuration value, or {@code null}
710   *
711   * @exception NullPointerException if {@code name} or {@code
712   * converter} is {@code null}
713   *
714   * @exception ConversionException if type conversion could not occur
715   * for any reason
716   *
717   * @exception AmbiguousConfigurationValuesException if two or more
718   * values were found that could be suitable and arbitration
719   * {@linkplain #performArbitration(Map, String, Collection) was
720   * performed} but could not resolve the dispute
721   *
722   * @exception ConfigurationException if any other
723   * configuration-related error occurs
724   *
725   * @see #getValue(Map, String, Converter, String)
726   */
727  public final <T> T getValue(final Map<String, String> configurationCoordinates, final String name, final Converter<T> converter) {
728    return this.getValue(configurationCoordinates, name, converter, null);
729  }
730
731  /**
732   * Returns an object that is the value for the configuration request
733   * represented by the supplied {@code configurationCoordinates},
734   * {@code name} and {@code defaultValue} parameters, as converted by
735   * the supplied {@link Converter}.
736   *
737   * <p>This method may return {@code null}.</p>
738   *
739   * @param <T> the type of the object to be returned
740   *
741   * @param configurationCoordinates the configuration coordinates for which
742   * a value should be selected; may be {@code null}
743   *
744   * @param name the name of the configuration property within the
745   * world defined by the supplied {@code configurationCoordinates}
746   * whose value is to be selected; must not be {@code null}
747   *
748   * @param converter a {@link Converter} instance that will convert
749   * any {@link String} configuration value into the type of object
750   * that this method will return; must not be {@code null}
751   *
752   * @param defaultValue the fallback default value to use as an
753   * absolute last resort; may be {@code null}; will also be converted
754   * by the supplied {@link Converter}
755   *
756   * @return the value for the implied configuration property, or {@code null}
757   *
758   * @exception NullPointerException if either {@code name} or {@code
759   * converter} is {@code null}
760   *
761   * @exception ConversionException if type conversion could not occur
762   * for any reason
763   *
764   * @exception AmbiguousConfigurationValuesException if two or more
765   * values were found that could be suitable and arbitration
766   * {@linkplain #performArbitration(Map, String, Collection) was
767   * performed} but could not resolve the dispute
768   *
769   * @exception ConfigurationException if any other
770   * configuration-related error occurs
771   *
772   * @see Converter#convert(String)
773   *
774   * @see Configuration#getValue(Map, String)
775   *
776   * @see #performArbitration(Map, String, Collection)
777   *
778   * @see #handleMalformedConfigurationValues(Collection)
779   */
780  public <T> T getValue(Map<String, String> configurationCoordinates, final String name, final Converter<T> converter, final String defaultValue) {
781    final String cn = this.getClass().getName();
782    final String mn = "getValue";
783    if (this.logger.isLoggable(Level.FINER)) {
784      this.logger.entering(cn, mn, new Object[] { configurationCoordinates, name, converter, defaultValue });
785    }
786    Objects.requireNonNull(name);
787    Objects.requireNonNull(converter);
788    this.checkState();
789    if (configurationCoordinates == null) {
790      configurationCoordinates = Collections.emptyMap();
791    }
792
793    // The selected value is the best candidate at any given moment
794    // for using to compute the return value of this method.  When it
795    // is null, it means we haven't found a suitable value yet.
796    ConfigurationValue selectedValue = null;    
797
798    // We use a PriorityQueue of ConfigurationValues sorted by their
799    // specificity (most specific first) to keep track of the most
800    // specific ConfigurationValue found so far.  We create it only
801    // when necessary.
802    PriorityQueue<ConfigurationValue> values = null;
803
804    // Subclasses are given a chance to deal with bad values that
805    // might be encountered by badly-behaved Configuration instances;
806    // see #handleMalformedConfigurationValues(Collection) for
807    // details.
808    Collection<ConfigurationValue> badValues = null;
809
810    for (final Configuration configuration : this.configurations) {
811      assert configuration != null;
812
813      final ConfigurationValue value;
814      try {
815        if (isActive(configuration)) {
816          value = null;
817        } else {
818          this.activate(configuration);
819          value = configuration.getValue(configurationCoordinates, name);
820        }
821      } finally {
822        this.deactivate(configuration);
823      }
824      
825      if (value != null) {        
826
827        if (name.equals(value.getName())) {
828          Map<String, String> valueCoordinates = value.getCoordinates();
829          if (valueCoordinates == null) {
830            valueCoordinates = Collections.emptyMap();
831          }
832          
833          final int configurationCoordinatesSize = configurationCoordinates.size();
834          final int valueCoordinatesSize = valueCoordinates.size();
835          
836          if (configurationCoordinatesSize < valueCoordinatesSize) {
837            // Bad value!
838            if (badValues == null) {
839              badValues = new LinkedList<>();
840            }
841            badValues.add(value);
842            
843          } else if (configurationCoordinates.equals(valueCoordinates)) {
844            // We have an exact match.  We hope it's going to be the
845            // only one.
846            
847            if (selectedValue == null) {
848              
849              if (values == null || values.isEmpty()) {
850                // There aren't any conflicts yet; this is good.  This
851                // value will be our candidate.
852                selectedValue = value;
853                
854              } else {
855                // We got a match, but we already *had* a match, so we
856                // don't have a candidate--instead, add it to the
857                // bucket of values that will be arbitrated later.
858                values.add(value);
859                
860              }
861              
862            } else {
863              assert selectedValue != null;
864              // We have an exact match, but we already identified a
865              // candidate, so oops, we have to treat our prior match
866              // and this one as non-candidates.
867              
868              if (values == null) {
869                values = new PriorityQueue<>(configurationValueComparator);
870              }
871              values.add(selectedValue);
872              selectedValue = null;
873              values.add(value);
874            }
875            
876          } else if (configurationCoordinatesSize == valueCoordinatesSize) {
877            // Bad value!  The configuration subsystem handed back a
878            // value containing coordinates not drawn from the
879            // configurationCoordinatesSet.  We know this because we
880            // already tested for Set equality, which failed, so this
881            // test means disparate entries.
882            if (badValues == null) {
883              badValues = new LinkedList<>();
884            }
885            badValues.add(value);
886            
887          } else if (selectedValue != null) {
888            // Nothing to do; we've already got our candidate.  We
889            // don't break here because we're going to ensure there
890            // aren't any duplicates.
891            
892          } else if (configurationCoordinates.entrySet().containsAll(valueCoordinates.entrySet())) {
893            // We specified, e.g., {a=b, c=d, e=f} and they have, say,
894            // {c=d, e=f} or {a=b, c=d} etc. but not, say, {q=r}.
895            if (values == null) {
896              values = new PriorityQueue<>(configurationValueComparator);
897            }
898            values.add(value);
899            
900          } else {
901            // Bad value!
902            if (badValues == null) {
903              badValues = new LinkedList<>();
904            }
905            badValues.add(value);
906            
907          }
908        } else {
909          // We asked for "frobnicationInterval"; they responded with
910          // "hostname".  Bad value.
911          if (badValues == null) {
912            badValues = new LinkedList<>();
913          }
914          badValues.add(value);
915        }
916      }
917    }
918    assert this.allConfigurationsInactive();
919
920    // Give a subclass a chance to deal with bad values.  Dealing with
921    // them might very well involve throwing an exception which will
922    // obviously preclude arbitration and conversion.  That's fine.
923    if (badValues != null && !badValues.isEmpty()) {
924      this.handleMalformedConfigurationValues(badValues);
925    }
926
927    // Perform arbitration if necessary, or otherwise ensure that we
928    // end up with the most suitable value possible.
929    if (selectedValue == null) {
930      final Collection<ConfigurationValue> valuesToArbitrate = new LinkedList<>();
931      int highestSpecificitySoFarEncountered = -1;
932      if (values != null) {
933        VALUES_LOOP:
934        while (!values.isEmpty()) {
935          
936          final ConfigurationValue value = values.poll();
937          assert value != null;
938          
939          // The values are sorted by their specificity, most specific
940          // first.
941          final int valueSpecificity = Math.max(0, value.specificity());
942          assert highestSpecificitySoFarEncountered < 0 || valueSpecificity <= highestSpecificitySoFarEncountered;
943          
944          if (highestSpecificitySoFarEncountered < 0 || valueSpecificity < highestSpecificitySoFarEncountered) {
945            if (selectedValue == null) {
946              assert valuesToArbitrate.isEmpty();
947              selectedValue = value;
948              highestSpecificitySoFarEncountered = valueSpecificity;
949            } else if (valuesToArbitrate.isEmpty()) {
950              // We have a selected value that is non-null, and no
951              // further values to arbitrate, so we're done.  We know
952              // we picked the most specific value so we effectively
953              // discard the others.
954              break VALUES_LOOP;
955            } else {
956              valuesToArbitrate.add(value);
957            }
958          } else if (valueSpecificity == highestSpecificitySoFarEncountered) {
959            assert selectedValue != null;
960            if (value.isAuthoritative()) {
961              if (selectedValue.isAuthoritative()) {
962                // Both say they're authoritative; arbitration required
963                valuesToArbitrate.add(selectedValue);
964                selectedValue = null;
965                valuesToArbitrate.add(value);
966              } else {
967                // value is authoritative; selectedValue is not; so swap
968                // them
969                selectedValue = value;
970              }
971            } else if (selectedValue.isAuthoritative()) {
972              // value is not authoritative; selected value is; so just
973              // drop value on the floor; it's not authoritative.
974            } else {
975              // Neither is authoritative; arbitration required.
976              valuesToArbitrate.add(selectedValue);
977              selectedValue = null;
978              valuesToArbitrate.add(value);
979            }
980          } else {
981            assert false : "valueSpecificity > highestSpecificitySoFarEncountered: " + valueSpecificity + " > " + highestSpecificitySoFarEncountered;
982          }
983          
984        }
985      }
986      if (selectedValue == null) {
987        selectedValue = this.performArbitration(configurationCoordinates, name, Collections.unmodifiableCollection(valuesToArbitrate));
988      }
989    }
990
991    // Perform conversion, including of null values.
992    final T returnValue;
993    if (selectedValue == null) {
994      if (defaultValue == null) {
995        returnValue = converter.convert(null);
996      } else {
997        returnValue = converter.convert(this.interpolate(defaultValue));
998      }
999    } else {
1000      final String valueToConvert = selectedValue.getValue();
1001      if (valueToConvert == null) {
1002        returnValue = converter.convert(null);
1003      } else {
1004        returnValue = converter.convert(this.interpolate(valueToConvert));
1005      }
1006    }
1007
1008    if (this.logger.isLoggable(Level.FINER)) {
1009      this.logger.exiting(cn, mn, returnValue);
1010    }
1011    return returnValue;
1012  }
1013
1014  /**
1015   * Interpolates any expressions occurring within the supplied {@code
1016   * value} and returns the result of interpolation.
1017   *
1018   * <p>This method may return {@code null}.</p>
1019   *
1020   * <p>Overrides of this method may return {@code null}.</p>
1021   *
1022   * <p>The default implementation of this method performs
1023   * interpolation by using a {@link ValueExpression} as {@linkplain
1024   * ExpressionFactory#createValueExpression(ELContext, String, Class)
1025   * produced by an <code>ExpressionFactory</code>}.</p>
1026   *
1027   * <p>A {@code configurations} object is made available to any
1028   * Expression Language expressions, which exposes this {@link
1029   * Configurations} object.  This means, among other things, that you
1030   * can retrieve configuration values using the following syntax:</p>
1031   * <pre>${configurations["java.home"]}</pre>
1032   *
1033   * @param value a configuration value {@link String}, before any
1034   * type conversion has taken place, with (possibly) expression
1035   * language expressions in it; may be {@code null} in which case
1036   * {@code null} is returned
1037   *
1038   * @return the result of interpolating the supplied {@code value},
1039   * or {@code null}
1040   *
1041   * @exception PropertyNotFoundException if the supplied {@code
1042   * value} contained a valid expression language expression that
1043   * identifies an unknown property
1044   *
1045   * @see <a
1046   * href="https://docs.oracle.com/javaee/7/tutorial/jsf-el.htm#GJDDD">the
1047   * section in the Java EE Tutorial on the Unified Expression
1048   * Language</a>
1049   */
1050  public String interpolate(final String value) {
1051    final String cn = this.getClass().getName();
1052    final String mn = "interpolate";
1053    if (this.logger.isLoggable(Level.FINER)) {
1054      this.logger.entering(cn, mn, value);
1055    }
1056    final String returnValue;
1057    if (value == null) {
1058      returnValue = null;
1059    } else {
1060      final ValueExpression valueExpression = this.expressionFactory.createValueExpression(this.elContext, value, String.class);
1061      assert valueExpression != null;
1062      returnValue = String.class.cast(valueExpression.getValue(this.elContext));
1063    }
1064    if (this.logger.isLoggable(Level.FINER)) {
1065      this.logger.exiting(cn, mn, returnValue);
1066    }
1067    return returnValue;
1068  }
1069
1070  /**
1071   * Returns a {@link Set} of names of {@link ConfigurationValue}s
1072   * that might be returned by this {@link Configurations} instance.
1073   *
1074   * <p>This method does not return {@code null}.</p>
1075   *
1076   * <p>Overrides of this method must not return {@code null}.</p>
1077   *
1078   * <p>This implementation does not cache results.</p>
1079   *
1080   * <p>Just because a name appears in the returned {@link Set} does
1081   * <em>not</em> mean that a {@link ConfigurationValue} <em>will</em>
1082   * be returned for it in a location in configuration space
1083   * identified by any arbitrary set of configuration coordinates.</p>
1084   *
1085   * @return a non-{@code null} {@link Set} of names of {@link
1086   * ConfigurationValue}s
1087   */
1088  @Override
1089  public Set<String> getNames() {
1090    final String cn = this.getClass().getName();
1091    final String mn = "getNames";
1092    if (this.logger.isLoggable(Level.FINER)) {
1093      this.logger.entering(cn, mn);
1094    }
1095    
1096    final Set<String> returnValue;
1097    if (this.configurations == null || this.configurations.isEmpty()) {
1098      returnValue = Collections.emptySet();
1099    } else {
1100      final Set<String> names = new TreeSet<>();
1101      for (final Configuration configuration : this.configurations) {
1102        if (configuration != null) {
1103          final Set<String> configurationNames = configuration.getNames();
1104          if (configurationNames != null && !configurationNames.isEmpty()) {
1105            names.addAll(configurationNames);
1106          }
1107        }
1108      }
1109      if (names.isEmpty()) {
1110        returnValue = Collections.emptySet();
1111      } else {
1112        returnValue = Collections.unmodifiableSet(names);
1113      }
1114    }
1115    
1116    if (this.logger.isLoggable(Level.FINER)) {
1117      this.logger.exiting(cn, mn, returnValue);
1118    }
1119    return returnValue;
1120  }
1121  
1122  /**
1123   * Handles any badly formed {@link ConfigurationValue} instances
1124   * received from {@link Configuration} instances during the
1125   * execution of a configuration value request.
1126   *
1127   * <p>The default implementation of this method does nothing.
1128   * Malformed values are thus effectively discarded.</p>
1129   *
1130   * <p>This method is called from the {@link #getValue(Map, String,
1131   * Converter, String)} method.</p>
1132   *
1133   * @param badValues a {@link Collection} of {@link
1134   * ConfigurationValue} instances that were deemed to be malformed in
1135   * some way; may be {@code null}
1136   *
1137   * @exception ConfigurationException if the {@link #getValue(Map,
1138   * String, Converter, String)} method should abort processing
1139   *
1140   * @see #getValue(Map, String, Converter, String)
1141   */
1142  protected void handleMalformedConfigurationValues(final Collection<ConfigurationValue> badValues) {
1143    if (this.logger.isLoggable(Level.FINER)) {
1144      final String cn = this.getClass().getName();
1145      final String mn = "handleMalformedConfigurationValues";
1146      this.logger.entering(cn, mn, badValues);
1147      this.logger.logp(Level.FINER, cn, mn, "Discarding {0}", badValues);
1148      this.logger.exiting(cn, mn);
1149    }
1150  }
1151
1152  /**
1153   * Given a logical request for a configuration value, represented by
1154   * the {@code configurationCoordinates} and {@code name} parameter
1155   * values, and a {@link Collection} of {@link ConfigurationValue}
1156   * instances that represents the ambiguous response from several
1157   * {@link Configuration} instances, attempts to resolve the
1158   * ambiguity by returning a single {@link ConfigurationValue}
1159   * instead.
1160   *
1161   * <p>This method may return {@code null}.</p>
1162   *
1163   * <p>Overrides of this method may return {@code null}.</p>
1164   *
1165   * <p>The default implementation of this method asks all registered
1166   * {@link Arbiter}s in turn to perform the arbitration and returns
1167   * the first non-{@code null} response received.</p>
1168   *
1169   * @param configurationCoordinates the ({@linkplain
1170   * Collections#unmodifiableMap(Map) immutable}) configuration
1171   * coordinates in effect for the request; may be {@code null}
1172   *
1173   * @param name the name of the configuration value; may be {@code
1174   * null}
1175   *
1176   * @param values an {@linkplain
1177   * Collections#unmodifiableCollection(Collection) immutable} {@link
1178   * Collection} of definitionally ambiguous {@link
1179   * ConfigurationValue}s that resulted from the request; may be
1180   * {@code null}
1181   *
1182   * @return the result of arbitration, or {@code null}
1183   *
1184   * @exception AmbiguousConfigurationValuesException if successful
1185   * arbitration did not happen for any reason
1186   *
1187   * @see Arbiter
1188   */
1189  protected ConfigurationValue performArbitration(final Map<? extends String, ? extends String> configurationCoordinates,
1190                                                  final String name,
1191                                                  final Collection<? extends ConfigurationValue> values) {
1192    final String cn = this.getClass().getName();
1193    final String mn = "performArbitration";
1194    if (this.logger.isLoggable(Level.FINER)) {
1195      this.logger.entering(cn, mn, new Object[] { configurationCoordinates, name, values });
1196    }
1197
1198    ConfigurationValue returnValue = null;
1199    if (this.arbiters != null && !this.arbiters.isEmpty()) {
1200      for (final Arbiter arbiter : arbiters) {
1201        if (arbiter != null) {
1202          final ConfigurationValue arbitrationResult = arbiter.arbitrate(configurationCoordinates, name, values);
1203          if (arbitrationResult != null) {
1204            returnValue = arbitrationResult;
1205            break;
1206          }
1207        }
1208      }
1209    }
1210    if (returnValue == null && values != null && !values.isEmpty()) {
1211      throw new AmbiguousConfigurationValuesException(null, null, configurationCoordinates, name, values);
1212    }
1213    
1214    if (this.logger.isLoggable(Level.FINER)) {
1215      this.logger.exiting(cn, mn, returnValue);
1216    }
1217    return returnValue;
1218  }
1219
1220  /**
1221   * If this {@link Configurations} has not yet finished {@linkplain
1222   * #Configurations() constructing}, then this method will throw an
1223   * {@link IllegalStateException}.
1224   *
1225   * @exception IllegalStateException if this {@link Configurations}
1226   * has not yet finished {@linkplain #Configurations() constructing}
1227   *
1228   * @see #Configurations()
1229   */
1230  private final void checkState() {
1231    if (!this.initialized) {
1232      throw new IllegalStateException();
1233    }
1234  }
1235
1236  /**
1237   * Returns {@code true} if the supplied {@link Configuration} is
1238   * currently in the middle of executing its {@link
1239   * Configuration#getValue(Map, String)} method on the current {@link
1240   * Thread}.
1241   *
1242   * @param configuration the {@link Configuration} to test; may be
1243   * {@code null} in which case {@code false} will be returned
1244   *
1245   * @return {@code true} if the supplied {@link Configuration} is
1246   * active; {@code false} otherwise
1247   *
1248   * @see #activate(Configuration)
1249   *
1250   * @see #deactivate(Configuration)
1251   */
1252  private final boolean isActive(final Configuration configuration) {
1253    final boolean returnValue;
1254    if (configuration == null) {
1255      returnValue = false;
1256    } else {
1257      final Set<Configuration> configurations = staticCurrentlyActiveConfigurations.get().get(this);
1258      returnValue = configurations != null && configurations.contains(configuration);
1259    }
1260    return returnValue;
1261  }
1262
1263  /**
1264   * Records that the supplied {@link Configuration} is in the middle
1265   * of executing its {@link Configuration#getValue(Map, String)}
1266   * method on the current {@link Thread}.
1267   *
1268   * <p>This method is idempotent.</p>
1269   *
1270   * @param configuration the {@link Configuration} in question; may
1271   * be {@code null} in which case no action is taken
1272   *
1273   * @see #isActive(Configuration)
1274   *
1275   * @see #deactivate(Configuration)
1276   */
1277  private final void activate(final Configuration configuration) {
1278    if (configuration != null) {
1279      final Map<Configurations, Set<Configuration>> map = staticCurrentlyActiveConfigurations.get();
1280      assert map != null;
1281      Set<Configuration> configurations = map.get(this);
1282      if (configurations == null) {
1283        configurations = new HashSet<>();
1284        map.put(this, configurations);
1285      }
1286      configurations.add(configuration);
1287      assert this.isActive(configuration);
1288    }
1289  }
1290
1291  /**
1292   * Records that the supplied {@link Configuration} is no longer in
1293   * the middle of executing its {@link Configuration#getValue(Map,
1294   * String)} method on the current {@link Thread}.
1295   *
1296   * <p>This method is idempotent.</p>
1297   *
1298   * @param configuration the {@link Configuration} in question; may
1299   * be {@code null} in which case no action is taken
1300   *
1301   * @see #isActive(Configuration)
1302   *
1303   * @see #activate(Configuration)
1304   */
1305  private final void deactivate(final Configuration configuration) {
1306    if (configuration != null) {
1307      final Set<Configuration> configurations = staticCurrentlyActiveConfigurations.get().get(this);
1308      if (configurations != null) {
1309        configurations.remove(configuration);
1310      }
1311    }
1312    assert !this.isActive(configuration);
1313  }
1314
1315  /**
1316   * Returns {@code true} if all {@link Configuration} instances have
1317   * been {@linkplain #deactivate(Configuration) deactivated}.
1318   *
1319   * @return {@code true} if all {@link Configuration} instances have
1320   * been {@linkplain #deactivate(Configuration) deactivated}; {@code
1321   * false} otherwise
1322   *
1323   * @see #isActive(Configuration)
1324   *
1325   * @see #deactivate(Configuration)
1326   */
1327  private final boolean allConfigurationsInactive() {
1328    final Set<Configuration> configurations = staticCurrentlyActiveConfigurations.get().get(this);
1329    return configurations == null || configurations.isEmpty();
1330  }
1331
1332
1333  /*
1334   * Inner and nested classes.
1335   */
1336
1337
1338  /**
1339   * An {@link ELResolver} that resolves a {@code configurations}
1340   * top-level object in the Expression Language and resolves its
1341   * properties by treating them as the names of configuration
1342   * properties.
1343   *
1344   * @author <a href="https://about.me/lairdnelson"
1345   * target="_parent">Laird Nelson</a>
1346   *
1347   * @see ELResolver
1348   *
1349   * @see StandardELContext#addELResolver(ELResolver)
1350   */
1351  private final class ConfigurationELResolver extends ELResolver {
1352
1353
1354    /*
1355     * Constructors.
1356     */
1357
1358    
1359    /**
1360     * Creates a new {@link ConfigurationELResolver}.
1361     */
1362    private ConfigurationELResolver() {
1363      super();
1364    }
1365
1366
1367    /*
1368     * Instance methods.
1369     */
1370
1371    
1372    /**
1373     * Returns {@link Class Object.class} when invoked.
1374     *
1375     * @param elContext the {@link ELContext} in effect; ignored
1376     *
1377     * @param base the base {@link Object} in effect; ignored
1378     *
1379     * @return {@link Class Object.class} when invoked
1380     *
1381     * @see ELResolver#getCommonPropertyType(ELContext, Object)
1382     */
1383    @Override
1384    public final Class<?> getCommonPropertyType(final ELContext elContext, final Object base) {
1385      return Object.class;
1386    }
1387
1388    /**
1389     * Returns {@code null} when invoked.
1390     *
1391     * @param elContext the {@link ELContext} in effect; ignored
1392     *
1393     * @param base the base {@link Object} in effect; ignored
1394     *
1395     * @return {@code null} when invoked
1396     *
1397     * @see ELResolver#getFeatureDescriptors(ELContext, Object)
1398     */
1399    @Override
1400    public final Iterator<FeatureDescriptor> getFeatureDescriptors(final ELContext elContext, final Object base) {
1401      return null;
1402    }
1403
1404    /**
1405     * Calls {@link ELContext#setPropertyResolved(boolean)
1406     * elContext.setPropertyResolved(true)} and returns {@code true}
1407     * when invoked.
1408     *
1409     * @param elContext the {@link ELContext} in effect; ignored
1410     *
1411     * @param base the base {@link Object} in effect; ignored
1412     *
1413     * @param property the property in effect; ignored
1414     *
1415     * @return {@code true} when invoked
1416     *
1417     * @see ELContext#setPropertyResolved(boolean)
1418     *
1419     * @see ELResolver#isReadOnly(ELContext, Object, Object)
1420     */
1421    @Override
1422    public final boolean isReadOnly(final ELContext elContext, final Object base, final Object property) {
1423      if (elContext != null && (property instanceof String || property instanceof Configurations)) {
1424        elContext.setPropertyResolved(true);
1425      }
1426      return true;
1427    }
1428
1429    /**
1430     * Returns a {@link Class} representing the type of object that
1431     * the supplied {@code base}/{@code property} pair represents.
1432     *
1433     * <p>This method may return {@code null}.</p>
1434     *
1435     * <p>If the supplied {@code base} is {@code null} and the
1436     * supplied {@code property} is a {@link String} equal to "{@code
1437     * configurations}", then this method returns {@link Class
1438     * Configurations.class}.</p>
1439     *
1440     * <p>If the supplied {@code base} is an instance of {@link
1441     * Configurations} and the supplied {@code property} is a {@link
1442     * String}, then the {@code property} is treated as the name of a
1443     * configuration property, and the {@link
1444     * Configurations#getValue(String)} method is invoked on the
1445     * {@code base} object with it.  If the return value of that
1446     * method invocation is {@code null}, then a {@link
1447     * PropertyNotFoundException} is thrown; otherwise {@link Class
1448     * String.class} is returned.</p>
1449     *
1450     * <p>This method returns {@code null} in all other cases.</p>
1451     *
1452     * @param elContext the {@link ELContext} in effect; must not be
1453     * {@code null}
1454     *
1455     * @param base the base; may be {@code null}
1456     *
1457     * @param property the property; may be {@code null}
1458     *
1459     * @return {@link Class String.class} or {@code null}
1460     *
1461     * @exception NullPointerException if {@code elContext} is {@code
1462     * null}
1463     *
1464     * @exception PropertyNotFoundException if {@code property} is a
1465     * {@link String} but does not identify a configuration property
1466     * that has a value
1467     *
1468     * @see ELResolver#getType(ELContext, Object, Object)
1469     *
1470     * @see Configurations#getValue(String)
1471     */
1472    @Override
1473    public final Class<?> getType(final ELContext elContext, final Object base, final Object property) {
1474      Objects.requireNonNull(elContext);
1475      Class<?> returnValue = null;
1476      if (base == null) {
1477        if ("configurations".equals(property)) {
1478          elContext.setPropertyResolved(true);
1479          returnValue = Configurations.class;
1480        }
1481      } else if (base instanceof Configurations) {
1482        if (property instanceof String) {
1483          final String value = ((Configurations)base).getValue(property.toString());
1484          elContext.setPropertyResolved(true);
1485          if (value == null) {
1486            throw new PropertyNotFoundException(property.toString());
1487          }
1488          returnValue = String.class;
1489        }
1490      }
1491      return returnValue;      
1492    }
1493
1494     /**
1495     * Returns the proper value for the supplied {@code base}/{@code
1496     * property} pair.
1497     *
1498     * <p>This method may return {@code null}.</p>
1499     *
1500     * <p>If the supplied {@code base} is {@code null} and the
1501     * supplied {@code property} is a {@link String} equal to "{@code
1502     * configurations}", then this method returns the {@link
1503     * Configurations} object housing this {@link ELResolver}
1504     * implementation.</p>
1505     *
1506     * <p>If the supplied {@code base} is an instance of {@link
1507     * Configurations} and the supplied {@code property} is a {@link
1508     * String}, then the {@code property} is treated as the name of a
1509     * configuration property, and the {@link
1510     * Configurations#getValue(String)} method is invoked on the
1511     * {@code base} object with it.  If the return value of that
1512     * method invocation is {@code null}, then a {@link
1513     * PropertyNotFoundException} is thrown; otherwise the return
1514     * value of its {@link ConfigurationValue#getValue()} method is
1515     * returned.</p>
1516     *
1517     * <p>This method returns {@code null} in all other cases.</p>
1518     *
1519     * @param elContext the {@link ELContext} in effect; must not be
1520     * {@code null}
1521     *
1522     * @param base the base; may be {@code null}
1523     *
1524     * @param property the property; may be {@code null}
1525     *
1526     * @return a {@link Configurations} object, a {@link String} or
1527     * {@code null}
1528     *
1529     * @exception NullPointerException if {@code elContext} is {@code
1530     * null}
1531     *
1532     * @exception PropertyNotFoundException if {@code property} is a
1533     * {@link String} but does not identify a configuration property
1534     * that has a value
1535     *
1536     * @see ELResolver#getValue(ELContext, Object, Object)
1537     *
1538     * @see Configurations#getValue(String)
1539     */
1540    @Override
1541    public final Object getValue(final ELContext elContext, final Object base, final Object property) {
1542      Objects.requireNonNull(elContext);
1543      Object returnValue = null;
1544      if (base == null) {
1545        if ("configurations".equals(property)) {
1546          elContext.setPropertyResolved(true);
1547          returnValue = Configurations.this;
1548        }
1549      } else if (base instanceof Configurations) {
1550        if (property instanceof String) {
1551          returnValue = ((Configurations)base).getValue(property.toString());
1552          elContext.setPropertyResolved(true);
1553          if (returnValue == null) {
1554            throw new PropertyNotFoundException(property.toString());
1555          }          
1556        }
1557      }
1558      return returnValue;
1559    }
1560
1561    /**
1562     * Effectively does nothing by {@linkplain
1563     * ELContext#setPropertyResolved(boolean) marking the supplied
1564     * <code>property</code> as not resolved}.
1565     *
1566     * @param elContext the {@link ELContext} in effect; may be {@code null}
1567     *
1568     * @param base the base; ignored; may be {@code null}
1569     *
1570     * @param property the property; ignored; may be {@code null}
1571     *
1572     * @param value the value; ignored; may be {@code null}
1573     *
1574     * @see ELResolver#setValue(ELContext, Object, Object, Object)
1575     *
1576     * @see #isReadOnly(ELContext, Object, Object)
1577     */
1578    @Override
1579    public final void setValue(final ELContext elContext, final Object base, final Object property, final Object value) {
1580      if (elContext != null) {
1581        elContext.setPropertyResolved(false);
1582      }
1583    }
1584    
1585  }
1586  
1587}