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.kubernetes.controller;
018
019import java.beans.PropertyChangeEvent;
020import java.beans.PropertyChangeListener;
021import java.beans.PropertyChangeSupport;
022
023import java.io.Serializable;
024
025import java.util.Collection;
026import java.util.Collections;
027import java.util.HashSet;
028import java.util.Iterator;
029import java.util.LinkedHashMap;
030import java.util.Map;
031import java.util.Map.Entry;
032import java.util.NoSuchElementException;
033import java.util.Objects;
034import java.util.Set;
035import java.util.Spliterator;
036
037import java.util.concurrent.Executor;
038import java.util.concurrent.Executors;
039import java.util.concurrent.ExecutorService;
040import java.util.concurrent.Future;
041import java.util.concurrent.ScheduledExecutorService;
042import java.util.concurrent.ScheduledFuture;
043import java.util.concurrent.ScheduledThreadPoolExecutor;
044import java.util.concurrent.TimeUnit;
045
046import java.util.function.Consumer;
047import java.util.function.Supplier;
048
049import java.util.logging.Level;
050import java.util.logging.Logger;
051
052import io.fabric8.kubernetes.api.model.HasMetadata;
053import io.fabric8.kubernetes.api.model.ObjectMeta;
054
055import net.jcip.annotations.GuardedBy;
056import net.jcip.annotations.ThreadSafe;
057
058import org.microbean.development.annotation.Blocking;
059import org.microbean.development.annotation.NonBlocking;
060
061/**
062 * An {@link EventCache} that temporarily stores {@link Event}s in
063 * {@link EventQueue}s, one per named Kubernetes resource, and
064 * {@linkplain #start(Consumer) provides a means for processing those
065 * queues}.
066 *
067 * <h2>Thread Safety</h2>
068 *
069 * <p>This class is safe for concurrent use by multiple {@link
070 * Thread}s.</p>
071 *
072 * @param <T> a type of Kubernetes resource
073 *
074 * @author <a href="https://about.me/lairdnelson"
075 * target="_parent">Laird Nelson</a>
076 *
077 * @see #add(Object, AbstractEvent.Type, HasMetadata)
078 *
079 * @see #replace(Collection, Object)
080 *
081 * @see #synchronize()
082 *
083 * @see #start(Consumer)
084 *
085 * @see EventQueue
086 */
087@ThreadSafe
088public class EventQueueCollection<T extends HasMetadata> implements EventCache<T>, Supplier<EventQueue<T>>, AutoCloseable {
089
090
091  /*
092   * Instance fields.
093   */
094
095
096  /**
097   * A {@link PropertyChangeSupport} object that manages {@link
098   * PropertyChangeEvent}s on behalf of this {@link
099   * EventQueueCollection}.
100   *
101   * <p>This field is never {@code null}.</p>
102   *
103   * @see #addPropertyChangeListener(String, PropertyChangeListener)
104   */
105  private final PropertyChangeSupport propertyChangeSupport;
106  
107  /**
108   * Whether this {@link EventQueueCollection} is in the process of
109   * {@linkplain #close() closing}.
110   *
111   * @see #close()
112   */
113  private volatile boolean closing;
114
115  /**
116   * Whether or not this {@link EventQueueCollection} has been
117   * populated via an invocation of the {@link #replace(Collection,
118   * Object)} method.
119   *
120   * <p>Mutations of this field must be synchronized on {@code
121   * this}.</p>
122   *
123   * @see #replace(Collection, Object)
124   */
125  @GuardedBy("this")
126  private boolean populated;
127
128  /**
129   * The number of {@link EventQueue}s that this {@link
130   * EventQueueCollection} was initially {@linkplain
131   * #replace(Collection, Object) seeded with}.
132   *
133   * <p>Mutations of this field must be synchronized on {@code
134   * this}.</p>
135   *
136   * @see #replace(Collection, Object)
137   */
138  @GuardedBy("this")
139  private int initialPopulationCount;
140
141  /**
142   * A {@link LinkedHashMap} of {@link EventQueue} instances, indexed
143   * by {@linkplain EventQueue#getKey() their keys}.
144   *
145   * <p>This field is never {@code null}.</p>
146   *
147   * <p>Mutations of the contents of this {@link LinkedHashMap} must
148   * be synchronized on {@code this}.</p>
149   *
150   * @see #add(Object, AbstractEvent.Type, HasMetadata)
151   */
152  @GuardedBy("this")
153  private final LinkedHashMap<Object, EventQueue<T>> map;
154
155  /**
156   * A {@link Map} containing the last known state of Kubernetes
157   * resources this {@link EventQueueCollection} is caching events
158   * for.  This field is used chiefly by the {@link #synchronize()}
159   * method, but by others as well.
160   *
161   * <p>This field may be {@code null}.</p>
162   *
163   * <p>Mutations of this field must be synchronized on this field's
164   * value.</p>
165   *
166   * @see #getKnownObjects()
167   *
168   * @see #synchronize()
169   */
170  @GuardedBy("itself")
171  private final Map<?, ? extends T> knownObjects;
172
173  @GuardedBy("this")
174  private ScheduledExecutorService consumerExecutor;
175  
176  /**
177   * A {@link Logger} used by this {@link EventQueueCollection}.
178   *
179   * <p>This field is never {@code null}.</p>
180   *
181   * @see #createLogger()
182   */
183  protected final Logger logger;
184
185  
186  /*
187   * Constructors.
188   */
189
190
191  /**
192   * Creates a new {@link EventQueueCollection} with an initial
193   * capacity of {@code 16} and a load factor of {@code 0.75} that is
194   * not interested in tracking Kubernetes resource deletions.
195   *
196   * @see #EventQueueCollection(Map, int, float)
197   */
198  public EventQueueCollection() {
199    this(null, 16, 0.75f);
200  }
201
202  /**
203   * Creates a new {@link EventQueueCollection} with an initial
204   * capacity of {@code 16} and a load factor of {@code 0.75}.
205   *
206   * @param knownObjects a {@link Map} containing the last known state
207   * of Kubernetes resources this {@link EventQueueCollection} is
208   * caching events for; may be {@code null} if this {@link
209   * EventQueueCollection} is not interested in tracking deletions of
210   * objects; if non-{@code null} <strong>will be synchronized on by
211   * this class</strong> during retrieval and traversal operations
212   *
213   * @see #EventQueueCollection(Map, int, float)
214   */
215  public EventQueueCollection(final Map<?, ? extends T> knownObjects) {
216    this(knownObjects, 16, 0.75f);
217  }
218
219  /**
220   * Creates a new {@link EventQueueCollection}.
221   *
222   * @param knownObjects a {@link Map} containing the last known state
223   * of Kubernetes resources this {@link EventQueueCollection} is
224   * caching events for; may be {@code null} if this {@link
225   * EventQueueCollection} is not interested in tracking deletions of
226   * objects; if non-{@code null} <strong>will be synchronized on by
227   * this class</strong> during retrieval and traversal operations
228   *
229   * @param initialCapacity the initial capacity of the internal data
230   * structure used to house this {@link EventQueueCollection}'s
231   * {@link EventQueue}s; must be an integer greater than {@code 0}
232   *
233   * @param loadFactor the load factor of the internal data structure
234   * used to house this {@link EventQueueCollection}'s {@link
235   * EventQueue}s; must be a positive number between {@code 0} and
236   * {@code 1}
237   */
238  public EventQueueCollection(final Map<?, ? extends T> knownObjects, final int initialCapacity, final float loadFactor) {
239    super();
240    final String cn = this.getClass().getName();
241    final String mn = "<init>";
242    this.logger = this.createLogger();
243    if (logger == null) {
244      throw new IllegalStateException();
245    }
246    if (this.logger.isLoggable(Level.FINER)) {
247      final String knownObjectsString;
248      if (knownObjects == null) {
249        knownObjectsString = null;
250      } else {
251        synchronized (knownObjects) {
252          knownObjectsString = knownObjects.toString();
253        }
254      }
255      this.logger.entering(cn, mn, new Object[] { knownObjectsString, Integer.valueOf(initialCapacity), Float.valueOf(loadFactor) });
256    }
257    this.propertyChangeSupport = new PropertyChangeSupport(this);
258    this.map = new LinkedHashMap<>(initialCapacity, loadFactor);
259    this.knownObjects = knownObjects;
260    if (this.logger.isLoggable(Level.FINER)) {
261      this.logger.exiting(cn, mn);
262    }
263  }
264
265
266  /*
267   * Instance methods.
268   */
269
270  /**
271   * Returns a {@link Logger} for use by this {@link
272   * EventQueueCollection}.
273   *
274   * <p>This method never returns {@code null}.</p>
275   *
276   * <p>Overrides of this method must not return {@code null}.</p>
277   *
278   * @return a non-{@code null} {@link Logger} for use by this {@link
279   * EventQueueCollection}
280   */
281  protected Logger createLogger() {
282    return Logger.getLogger(this.getClass().getName());
283  }
284
285  private final Map<?, ? extends T> getKnownObjects() {
286    return this.knownObjects;
287  }
288
289  /**
290   * Returns {@code true} if this {@link EventQueueCollection} is empty.
291   *
292   * @return {@code true} if this {@link EventQueueCollection} is
293   * empty; {@code false} otherwise
294   */
295  private synchronized final boolean isEmpty() {
296    return this.map.isEmpty();
297  }
298
299  /**
300   * Returns {@code true} if this {@link EventQueueCollection} has
301   * been populated via a call to {@link #add(Object, AbstractEvent.Type,
302   * HasMetadata)} at some point, and if there are no {@link
303   * EventQueue}s remaining to be {@linkplain #start(Consumer)
304   * removed}.
305   *
306   * <p>This is a <a
307   * href="https://docs.oracle.com/javase/tutorial/javabeans/writing/properties.html#bound">bound
308   * property</a>.</p>
309   *
310   * @return {@code true} if this {@link EventQueueCollection} has
311   * been populated via a call to {@link #add(Object, AbstractEvent.Type,
312   * HasMetadata)} at some point, and if there are no {@link
313   * EventQueue}s remaining to be {@linkplain #start(Consumer)
314   * removed}; {@code false} otherwise
315   *
316   * @see #replace(Collection, Object)
317   *
318   * @see #add(Object, AbstractEvent.Type, HasMetadata)
319   *
320   * @see #synchronize()
321   */
322  public synchronized final boolean isSynchronized() {
323    return this.populated && this.initialPopulationCount == 0;
324  }
325
326  /**
327   * <strong>Synchronizes on the {@code knownObjects} object</strong>
328   * {@linkplain #EventQueueCollection(Map, int, float) supplied at
329   * construction time}, if there is one, and, for every Kubernetes
330   * resource found within at the time of this call, adds a {@link
331   * SynchronizationEvent} for it with an {@link AbstractEvent.Type}
332   * of {@link AbstractEvent.Type#MODIFICATION}.
333   */
334  @Override
335  public final void synchronize() {
336    final String cn = this.getClass().getName();
337    final String mn = "synchronize";
338    if (this.logger.isLoggable(Level.FINER)) {
339      this.logger.entering(cn, mn);
340    }
341    synchronized (this) {
342      final Map<?, ? extends T> knownObjects = this.getKnownObjects();
343      if (knownObjects != null) {
344        synchronized (knownObjects) {
345          if (!knownObjects.isEmpty()) {
346            final Collection<? extends T> values = knownObjects.values();
347            if (values != null && !values.isEmpty()) {
348              for (final T knownObject : values) {
349                if (knownObject != null) {
350                  // We follow the Go code in that we use *our* key
351                  // extraction logic, rather than relying on the
352                  // known key in the knownObjects map.
353                  final Object key = this.getKey(knownObject);
354                  if (key != null) {
355                    final EventQueue<T> eventQueue = this.map.get(key);
356                    if (eventQueue == null || eventQueue.isEmpty()) {
357                      // We make a SynchronizationEvent of type
358                      // MODIFICATION.  shared_informer.go checks in
359                      // its HandleDeltas function to see if oldObj
360                      // exists; if so, it's a modification.  Here we
361                      // take action *only if* the equivalent of
362                      // oldObj exists, therefore this is a
363                      // SynchronizationEvent of type MODIFICATION,
364                      // not ADDITION.
365                      this.synchronize(this, AbstractEvent.Type.MODIFICATION, knownObject, true /* yes, populate */);
366                    }
367                  }
368                }
369              }
370            }
371          }
372        }
373      }
374    }
375    if (this.logger.isLoggable(Level.FINER)) {
376      this.logger.exiting(cn, mn);
377    }
378  }
379
380  /**
381   * At a high level, fully replaces the internal state of this {@link
382   * EventQueueCollection} to reflect only the Kubernetes resources
383   * contained in the supplied {@link Collection}, notionally firing
384   * {@link SynchronizationEvent}s and {@link Event}s of type {@link
385   * AbstractEvent.Type#DELETION} as appropriate.
386   *
387   * <p>{@link EventQueue}s managed by this {@link
388   * EventQueueCollection} that have not yet {@linkplain
389   * #start(Consumer) been processed} are not removed by this
390   * operation.</p>
391   *
392   * <p><strong>This method synchronizes on the supplied {@code
393   * incomingResources} {@link Collection}</strong> while iterating
394   * over it.</p>
395   *
396   * @param incomingResources the {@link Collection} of Kubernetes
397   * resources with which to replace this {@link
398   * EventQueueCollection}'s internal state; may be {@code null} or
399   * {@linkplain Collection#isEmpty() empty}, which will be taken as
400   * an indication that this {@link EventQueueCollection} should
401   * effectively be emptied
402   *
403   * @param resourceVersion the version of the Kubernetes list
404   * resource that contained the incoming resources; currently
405   * ignored; may be {@code null}
406   *
407   * @exception IllegalStateException if the {@link
408   * #createEvent(Object, AbstractEvent.Type, HasMetadata)} method returns
409   * {@code null} for any reason
410   *
411   * @see SynchronizationEvent
412   *
413   * @see #createEvent(Object, AbstractEvent.Type, HasMetadata)
414   */
415  @Override
416  public synchronized final void replace(final Collection<? extends T> incomingResources, final Object resourceVersion) {
417    final String cn = this.getClass().getName();
418    final String mn = "replace";
419    if (this.logger.isLoggable(Level.FINER)) {
420      final String incomingResourcesString;
421      if (incomingResources == null) {
422        incomingResourcesString = null;
423      } else {
424        synchronized (incomingResources) {
425          incomingResourcesString = incomingResources.toString();
426        }
427      }
428      this.logger.entering(cn, mn, new Object[] { incomingResourcesString, resourceVersion });
429    }
430
431    final boolean oldSynchronized = this.isSynchronized();
432    final int size;
433    final Set<Object> replacementKeys;
434
435    if (incomingResources == null) {
436      size = 0;
437      replacementKeys = Collections.emptySet();
438    } else {
439      synchronized (incomingResources) {
440        if (incomingResources.isEmpty()) {
441          size = 0;
442          replacementKeys = Collections.emptySet();
443        } else {
444          size = incomingResources.size();
445          assert size > 0;
446          replacementKeys = new HashSet<>();
447          for (final T resource : incomingResources) {
448            if (resource != null) {
449              replacementKeys.add(this.getKey(resource));
450              this.synchronize(this, AbstractEvent.Type.ADDITION, resource, false);
451            }
452          }
453        }
454      }
455    }
456
457    int queuedDeletions = 0;
458    
459    final Map<?, ? extends T> knownObjects = this.getKnownObjects();
460    if (knownObjects == null) {
461
462      for (final EventQueue<T> eventQueue : this.map.values()) {
463        assert eventQueue != null;
464        final Object key;
465        final AbstractEvent<? extends T> newestEvent;        
466        synchronized (eventQueue) {
467          if (eventQueue.isEmpty()) {
468            newestEvent = null;
469            key = null;
470            assert false : "eventQueue.isEmpty(): " + eventQueue;
471          } else {
472            key = eventQueue.getKey();
473            if (key == null) {
474              throw new IllegalStateException();
475            }
476            if (replacementKeys.contains(key)) {
477              newestEvent = null;
478            } else {
479              // We have an EventQueue indexed under a key that
480              // identifies a resource that no longer exists in
481              // Kubernetes.  Inform any consumers via a deletion
482              // event that this object was removed at some point from
483              // Kubernetes.  The state of the object in question is
484              // indeterminate.
485              newestEvent = eventQueue.getLast();
486              assert newestEvent != null;
487            }
488          }
489        }
490        if (newestEvent != null) {
491          assert key != null;
492          // We grab the last event in the queue in question and get
493          // its resource; this will serve as the state of the
494          // Kubernetes resource in question the last time we knew
495          // about it.  This state is not necessarily, but could be,
496          // the true actual last state of the resource in question.
497          // The point is, the true state of the object when it was
498          // deleted is unknown.  We build a new event to reflect all
499          // this.
500          //
501          // Astute readers will realize that this could result in two
502          // DELETION events enqueued, back to back, with identical
503          // payloads.  See the deduplicate() method in EventQueue,
504          // which takes care of this situation.
505          final T resourceToBeDeleted = newestEvent.getResource();
506          assert resourceToBeDeleted != null;
507          final Event<T> event = this.createEvent(this, AbstractEvent.Type.DELETION, resourceToBeDeleted);
508          if (event == null) {
509            throw new IllegalStateException("createEvent() == null");
510          }
511          event.setKey(key);
512          this.add(event, false /* don't treat this as a population event */);
513        }
514      }
515      
516    } else {
517
518      synchronized (knownObjects) {
519        if (!knownObjects.isEmpty()) {
520          final Collection<? extends Entry<?, ? extends T>> entrySet = knownObjects.entrySet();
521          if (entrySet != null && !entrySet.isEmpty()) {
522            for (final Entry<?, ? extends T> entry : entrySet) {
523              if (entry != null) {
524                final Object knownKey = entry.getKey();
525                if (!replacementKeys.contains(knownKey)) {
526                  final Event<T> event = this.createEvent(this, AbstractEvent.Type.DELETION, entry.getValue());
527                  if (event == null) {
528                    throw new IllegalStateException("createEvent() == null");
529                  }
530                  event.setKey(knownKey);
531                  this.add(event, false /* don't treat this as a population event */);
532                  queuedDeletions++;
533                }
534              }
535            }
536          }
537        }
538      }
539      
540    }
541      
542    if (!this.populated) {
543      this.populated = true;
544      this.firePropertyChange("populated", false, true);
545      assert size >= 0;
546      assert queuedDeletions >= 0;
547      final int oldInitialPopulationCount = this.initialPopulationCount;
548      this.initialPopulationCount = size + queuedDeletions;
549      this.firePropertyChange("initialPopulationCount", oldInitialPopulationCount, this.initialPopulationCount);
550      if (this.initialPopulationCount == 0) {
551        this.firePropertyChange("synchronized", oldSynchronized, true);
552      }
553    }
554
555    if (this.logger.isLoggable(Level.FINER)) {
556      this.logger.exiting(cn, mn);
557    }
558  }
559
560  /**
561   * Returns an {@link Object} which will be used as the key that will
562   * uniquely identify the supplied {@code resource} to this {@link
563   * EventQueueCollection}.
564   *
565   * <p>This method may return {@code null}, but only if {@code
566   * resource} is {@code null} or is constructed in such a way that
567   * its {@link HasMetadata#getMetadata()} method returns {@code
568   * null}.</p>
569   *
570   * <p>Overrides of this method may return {@code null}, but only if
571   * {@code resource} is {@code null}.
572   *
573   * <p>The default implementation of this method returns the return
574   * value of the {@link HasMetadatas#getKey(HasMetadata)} method.</p>
575   *
576   * @param resource a {@link HasMetadata} for which a key should be
577   * returned; may be {@code null} in which case {@code null} may be
578   * returned
579   *
580   * @return a non-{@code null} key for the supplied {@code resource};
581   * or {@code null} if {@code resource} is {@code null}
582   *
583   * @see HasMetadatas#getKey(HasMetadata)
584   */
585  protected Object getKey(final T resource) {
586    final String cn = this.getClass().getName();
587    final String mn = "getKey";
588    if (this.logger.isLoggable(Level.FINER)) {
589      this.logger.entering(cn, mn, resource);
590    }
591    final Object returnValue = HasMetadatas.getKey(resource);
592    if (this.logger.isLoggable(Level.FINER)) {
593      this.logger.exiting(cn, mn, returnValue);
594    }
595    return returnValue;
596  }
597
598  /**
599   * Creates a new {@link EventQueue} suitable for holding {@link
600   * Event}s {@linkplain Event#getKey() matching} the supplied {@code
601   * key}.
602   *
603   * <p>This method never returns {@code null}.</p>
604   *
605   * <p>Overrides of this method must not return {@code null}.</p>
606   *
607   * @param key the key {@linkplain EventQueue#getKey() for the new
608   * <code>EventQueue</code>}; must not be {@code null}
609   *
610   * @return the new {@link EventQueue}; never {@code null}
611   *
612   * @exception NullPointerException if {@code key} is {@code null}
613   *
614   * @see EventQueue#EventQueue(Object)
615   */
616  protected EventQueue<T> createEventQueue(final Object key) {
617    final String cn = this.getClass().getName();
618    final String mn = "createEventQueue";
619    if (this.logger.isLoggable(Level.FINER)) {
620      this.logger.entering(cn, mn, key);
621    }
622    final EventQueue<T> returnValue = new EventQueue<T>(key);
623    if (this.logger.isLoggable(Level.FINER)) {
624      this.logger.exiting(cn, mn, returnValue);
625    }
626    return returnValue;
627  }
628
629  /**
630   * Starts a new {@link Thread} that, until {@link #close()} is
631   * called, removes {@link EventQueue}s from this {@link
632   * EventQueueCollection} and supplies them to the supplied {@link
633   * Consumer}, and returns a {@link Future} representing this task.
634   *
635   * <p>This method may return {@code null}.</p>
636   *
637   * <p>Invoking this method does not block the calling {@link
638   * Thread}.</p>
639   *
640   * @param siphon the {@link Consumer} that will process each {@link
641   * EventQueue} as it becomes ready; must not be {@code null}
642   *
643   * @return a {@link Future} representing the task that is feeding
644   * {@link EventQueue}s to the supplied {@link Consumer}, or {@code
645   * null} if no task was started
646   *
647   * @exception NullPointerException if {@code siphon} is {@code null}
648   */
649  @NonBlocking
650  public final Future<?> start(final Consumer<? super EventQueue<? extends T>> siphon) {
651    final String cn = this.getClass().getName();
652    final String mn = "start";
653    if (this.logger.isLoggable(Level.FINER)) {
654      this.logger.entering(cn, mn, siphon);
655    }
656    
657    Objects.requireNonNull(siphon);
658
659    final Future<?> returnValue;
660    synchronized (this) {
661      if (this.consumerExecutor == null) {
662        this.consumerExecutor = this.createScheduledThreadPoolExecutor();
663        if (this.consumerExecutor == null) {
664          throw new IllegalStateException();
665        }
666        returnValue = this.consumerExecutor.scheduleWithFixedDelay(this.createEventQueueConsumptionTask(siphon), 0L, 1L, TimeUnit.SECONDS);
667      } else {
668        returnValue = null;
669      }
670    }
671    
672    if (this.logger.isLoggable(Level.FINER)) {
673      this.logger.exiting(cn, mn, returnValue);
674    }
675    return returnValue;
676  }
677
678  private final ScheduledThreadPoolExecutor createScheduledThreadPoolExecutor() {
679    final ScheduledThreadPoolExecutor returnValue = new ScheduledThreadPoolExecutor(1);
680    returnValue.setRemoveOnCancelPolicy(true);
681    return returnValue;
682  }
683
684  /**
685   * Semantically closes this {@link EventQueueCollection} by
686   * detaching any {@link Consumer} previously attached via the {@link
687   * #start(Consumer)} method.  {@linkplain #add(Object, AbstractEvent.Type,
688   * HasMetadata) Additions}, {@linkplain #replace(Collection, Object)
689   * replacements} and {@linkplain #synchronize() synchronizations}
690   * are still possible, but there won't be anything consuming any
691   * events generated by or supplied to these operations.
692   *
693   * <p>A closed {@link EventQueueCollection} may be {@linkplain
694   * #start(Consumer) started} again.</p>
695   *
696   * @see #start(Consumer)
697   */
698  @Override
699  public final void close() {
700    final String cn = this.getClass().getName();
701    final String mn = "close";
702    if (this.logger.isLoggable(Level.FINER)) {
703      this.logger.entering(cn, mn);
704    }
705
706    final ExecutorService consumerExecutor;
707    synchronized (this) {
708      this.closing = true;
709      consumerExecutor = this.consumerExecutor;
710    }
711
712    if (consumerExecutor != null) {
713      consumerExecutor.shutdown();
714      try {
715        if (!consumerExecutor.awaitTermination(60, TimeUnit.SECONDS)) {
716          consumerExecutor.shutdownNow();
717          if (!consumerExecutor.awaitTermination(60, TimeUnit.SECONDS) && this.logger.isLoggable(Level.WARNING)) {
718            this.logger.logp(Level.WARNING, cn, mn, "consumerExecutor.awaitTermination() failed");
719          }
720        }
721      } catch (final InterruptedException interruptedException) {
722        consumerExecutor.shutdownNow();
723        Thread.currentThread().interrupt();
724      }
725    }
726
727    if (this.logger.isLoggable(Level.FINER)) {
728      this.logger.exiting(cn, mn);
729    }
730  }
731
732  private final Runnable createEventQueueConsumptionTask(final Consumer<? super EventQueue<? extends T>> siphon) {
733    Objects.requireNonNull(siphon);
734    final Runnable returnValue = () -> {
735      while (!Thread.currentThread().isInterrupted()) {
736        @Blocking
737        final EventQueue<T> eventQueue = this.get();
738        if (eventQueue != null) {
739          synchronized (eventQueue) {
740            try {
741              siphon.accept(eventQueue);
742            } catch (final TransientException transientException) {              
743              this.map.putIfAbsent(eventQueue.getKey(), eventQueue);
744            }
745          }
746        }
747      }
748    };
749    return returnValue;
750  }
751
752  /**
753   * Returns an {@link EventQueue} if one is available,
754   * <strong>blocking if one is not</strong> and returning {@code
755   * null} only if the {@linkplain Thread#interrupt() current thread
756   * is interrupted}.
757   *
758   * <p>This method may return {@code null} in which case the current
759   * {@link Thread} has been {@linkplain Thread#interrupt()
760   * interrupted}.</p>
761   *
762   * @return an {@link EventQueue}, or {@code null}
763   */
764  @Blocking
765  @Override
766  public final EventQueue<T> get() {
767    final String cn = this.getClass().getName();
768    final String mn = "get";
769    if (this.logger.isLoggable(Level.FINER)) {
770      this.logger.entering(cn, mn);
771    }
772
773    EventQueue<T> returnValue = null;
774    try {
775      returnValue = this.take();
776    } catch (final InterruptedException interruptedException) {
777      Thread.currentThread().interrupt();
778      returnValue = null;
779    }
780    
781    if (this.logger.isLoggable(Level.FINER)) {
782      this.logger.exiting(cn, mn, returnValue);
783    }
784    return returnValue;
785  }
786  
787  @Blocking
788  private final EventQueue<T> take() throws InterruptedException {
789    final String cn = this.getClass().getName();
790    final String mn = "take";
791    if (this.logger.isLoggable(Level.FINER)) {
792      this.logger.entering(cn, mn);
793    }
794
795    final EventQueue<T> returnValue;
796    synchronized (this) {
797      while (this.isEmpty() && !this.closing) {
798        this.wait(); // blocks
799      }
800      assert this.populated : "this.populated == false";
801      if (this.isEmpty()) {
802        assert this.closing : "this.isEmpty() && !this.closing";
803        returnValue = null;
804      } else {
805        final Iterator<EventQueue<T>> iterator = this.map.values().iterator();
806        assert iterator != null;
807        assert iterator.hasNext();
808        returnValue = iterator.next();
809        assert returnValue != null;
810        iterator.remove();
811        if (this.initialPopulationCount > 0) {
812          // We know we're not populated and our
813          // initialPopulationCount is not 0, so therefore we are not
814          // synchronized.
815          assert !this.isSynchronized();
816          final int oldInitialPopulationCount = this.initialPopulationCount;
817          this.initialPopulationCount--;
818          this.firePropertyChange("initialPopulationCount", oldInitialPopulationCount, this.initialPopulationCount);
819          this.firePropertyChange("synchronized", false, this.isSynchronized());
820        }
821        this.firePropertyChange("empty", false, this.isEmpty());
822      }
823    }
824    
825    if (this.logger.isLoggable(Level.FINER)) {
826      final String eventQueueString;
827      synchronized (returnValue) {
828        eventQueueString = returnValue.toString();
829      }
830      this.logger.exiting(cn, mn, eventQueueString);
831    }
832    return returnValue;
833  }
834
835  /**
836   * Creates an {@link Event} using the supplied raw materials and
837   * returns it.
838   *
839   * <p>This method never returns {@code null}.</p>
840   *
841   * <p>Implementations of this method must not return {@code
842   * null}.</p>
843   *
844   * <p>Implementations of this method must return a new {@link Event}
845   * with every invocation.</p>
846   *
847   * @param source the {@linkplain Event#getSource() source} of the
848   * {@link Event} that will be created; must not be null
849   *
850   * @param eventType the {@linkplain Event#getType() type} of {@link
851   * Event} that will be created; must not be {@code null}
852   *
853   * @param resource the {@linkplain Event#getResource() resource} of
854   * the {@link Event} that will be created; must not be
855   * {@code null}
856   *
857   * @return the created {@link Event}; never {@code null}
858   */
859  protected Event<T> createEvent(final Object source, final AbstractEvent.Type eventType, final T resource) {
860    final String cn = this.getClass().getName();
861    final String mn = "createEvent";
862    if (this.logger.isLoggable(Level.FINER)) {
863      this.logger.entering(cn, mn, new Object[] { source, eventType, resource });
864    }
865
866    Objects.requireNonNull(source);
867    Objects.requireNonNull(eventType);
868    Objects.requireNonNull(resource);
869    final Event<T> returnValue = new Event<>(source, eventType, null, resource);
870
871    if (this.logger.isLoggable(Level.FINER)) {
872      this.logger.exiting(cn, mn, returnValue);
873    }
874    return returnValue;
875  }
876
877  protected SynchronizationEvent<T> createSynchronizationEvent(final Object source, final AbstractEvent.Type eventType, final T resource) {
878    final String cn = this.getClass().getName();
879    final String mn = "createSynchronizationEvent";
880    if (this.logger.isLoggable(Level.FINER)) {
881      this.logger.entering(cn, mn, new Object[] { source, eventType, resource });
882    }
883
884    Objects.requireNonNull(source);
885    Objects.requireNonNull(eventType);
886    Objects.requireNonNull(resource);
887    final SynchronizationEvent<T> returnValue = new SynchronizationEvent<>(source, eventType, null, resource);
888
889    if (this.logger.isLoggable(Level.FINER)) {
890      this.logger.exiting(cn, mn, returnValue);
891    }
892    return returnValue;
893  }
894
895  private final SynchronizationEvent<T> synchronize(final Object source, final AbstractEvent.Type eventType, final T resource, final boolean populate) {
896    final String cn = this.getClass().getName();
897    final String mn = "synchronize";
898    if (this.logger.isLoggable(Level.FINER)) {
899      this.logger.entering(cn, mn, new Object[] { source, eventType, resource });
900    }
901
902    Objects.requireNonNull(source);
903    Objects.requireNonNull(eventType);
904    Objects.requireNonNull(resource);
905
906    if (!(eventType.equals(AbstractEvent.Type.ADDITION) || eventType.equals(AbstractEvent.Type.MODIFICATION))) {
907      throw new IllegalArgumentException("Illegal eventType: " + eventType);
908    }
909
910    final SynchronizationEvent<T> event = this.createSynchronizationEvent(source, eventType, resource);
911    if (event == null) {
912      throw new IllegalStateException("createSynchronizationEvent() == null");
913    }
914    final SynchronizationEvent<T> returnValue = this.add(event, populate);
915
916    if (this.logger.isLoggable(Level.FINER)) {
917      this.logger.exiting(cn, mn, returnValue);
918    }
919    return returnValue;
920  }
921  
922  /**
923   * Adds a new {@link Event} constructed out of the parameters
924   * supplied to this method to this {@link EventQueueCollection} and
925   * returns the {@link Event} that was added.
926   *
927   * <p>This method may return {@code null}.</p>
928   *
929   * <p>This implementation {@linkplain #createEventQueue(Object)
930   * creates an <code>EventQueue</code>} if necessary for the {@link
931   * Event} that will be added, and then adds the new {@link Event} to
932   * the queue.</p>
933   *
934   * @param source the {@linkplain Event#getSource() source} of the
935   * {@link Event} that will be created and added; must not be null
936   *
937   * @param eventType the {@linkplain Event#getType() type} of {@link
938   * Event} that will be created and added; must not be {@code null}
939   *
940   * @param resource the {@linkplain Event#getResource() resource} of
941   * the {@link Event} that will be created and added; must not be
942   * {@code null}
943   *
944   * @return the {@link Event} that was created and added, or {@code
945   * null} if no {@link Event} was actually added as a result of this
946   * method's invocation
947   *
948   * @exception NullPointerException if any of the parameters is
949   * {@code null}
950   *
951   * @see Event
952   */
953  @Override
954  public final Event<T> add(final Object source, final AbstractEvent.Type eventType, final T resource) {
955    return this.add(source, eventType, resource, true);
956  }
957
958  /**
959   * Adds a new {@link Event} constructed out of the parameters
960   * supplied to this method to this {@link EventQueueCollection} and
961   * returns the {@link Event} that was added.
962   *
963   * <p>This method may return {@code null}.</p>
964   *
965   * <p>This implementation {@linkplain #createEventQueue(Object)
966   * creates an <code>EventQueue</code>} if necessary for the {@link
967   * Event} that will be added, and then adds the new {@link Event} to
968   * the queue.</p>
969   *
970   * @param source the {@linkplain Event#getSource() source} of the
971   * {@link Event} that will be created and added; must not be null
972   *
973   * @param eventType the {@linkplain Event#getType() type} of {@link
974   * Event} that will be created and added; must not be {@code null}
975   *
976   * @param resource the {@linkplain Event#getResource() resource} of
977   * the {@link Event} that will be created and added; must not be
978   * {@code null}
979   *
980   * @param populate if {@code true} then this {@link
981   * EventQueueCollection} will be internally marked as <em>initially
982   * populated</em>
983   *
984   * @return the {@link Event} that was created and added, or {@code
985   * null} if no {@link Event} was actually added as a result of this
986   * method's invocation
987   *
988   * @exception NullPointerException if any of the parameters is
989   * {@code null}
990   *
991   * @see Event
992   */
993  private final Event<T> add(final Object source, final AbstractEvent.Type eventType, final T resource, final boolean populate) {
994    final String cn = this.getClass().getName();
995    final String mn = "add";
996    if (this.logger.isLoggable(Level.FINER)) {
997      this.logger.entering(cn, mn, new Object[] { source, eventType, resource, Boolean.valueOf(populate)});
998    }
999    
1000    final Event<T> event = this.createEvent(source, eventType, resource);
1001    if (event == null) {
1002      throw new IllegalStateException("createEvent() == null");
1003    }
1004    final Event<T> returnValue = this.add(event, populate);
1005
1006    if (this.logger.isLoggable(Level.FINER)) {
1007      this.logger.exiting(cn, mn, returnValue);
1008    }
1009    return returnValue;
1010  }
1011
1012  /**
1013   * Adds the supplied {@link Event} to this {@link
1014   * EventQueueCollection} and returns the {@link Event} that was
1015   * added.
1016   *
1017   * <p>This method may return {@code null}.</p>
1018   *
1019   * <p>This implementation {@linkplain #createEventQueue(Object)
1020   * creates an <code>EventQueue</code>} if necessary for the {@link
1021   * Event} that will be added, and then adds the new {@link Event} to
1022   * the queue.</p>
1023   *
1024   * @param <E> an {@link AbstractEvent} type that is both consumed
1025   * and returned
1026   *
1027   * @param event the {@link Event} to add; must not be {@code null}
1028   *
1029   * @param populate if {@code true} then this {@link
1030   * EventQueueCollection} will be internally marked as <em>initially
1031   * populated</em>
1032   *
1033   * @return the {@link Event} that was created and added, or {@code
1034   * null} if no {@link Event} was actually added as a result of this
1035   * method's invocation
1036   *
1037   * @exception NullPointerException if any of the parameters is
1038   * {@code null}
1039   *
1040   * @see Event
1041   */
1042  private final <E extends AbstractEvent<T>> E add(final E event, final boolean populate) {
1043    final String cn = this.getClass().getName();
1044    final String mn = "add";
1045    if (this.logger.isLoggable(Level.FINER)) {
1046      this.logger.entering(cn, mn, new Object[] { event, Boolean.valueOf(populate) });
1047    }
1048
1049    if (this.closing) {
1050      throw new IllegalStateException();
1051    }
1052    
1053    Objects.requireNonNull(event);
1054
1055    final Object key = event.getKey();
1056    if (key == null) {
1057      throw new IllegalArgumentException("event.getKey() == null");
1058    }
1059    
1060    E returnValue = null;
1061    
1062    synchronized (this) {
1063
1064      if (populate) {
1065        final boolean old = this.populated;
1066        this.populated = true;
1067        this.firePropertyChange("populated", old, true);
1068      }
1069      
1070      EventQueue<T> eventQueue = this.map.get(key);
1071      final boolean eventQueueExisted = eventQueue != null;
1072      if (!eventQueueExisted) {
1073        eventQueue = this.createEventQueue(key);
1074        if (eventQueue == null) {
1075          throw new IllegalStateException("createEventQueue(key) == null: " + key);
1076        }
1077      }
1078      assert eventQueue != null;
1079
1080      final boolean eventAdded;
1081      final boolean eventQueueIsEmpty;
1082      synchronized (eventQueue) {
1083        eventAdded = eventQueue.addEvent(event);
1084        // Adding an event to an EventQueue can result in compression,
1085        // which may result in the EventQueue becoming empty as a
1086        // result of the add operation.
1087        eventQueueIsEmpty = eventQueue.isEmpty();
1088      }
1089
1090      if (eventAdded) {
1091        returnValue = event;
1092      }
1093
1094      if (eventQueueIsEmpty) {
1095        // Compression might have emptied the queue, so an add could
1096        // result in an empty queue.  We don't permit empty queues.
1097        if (eventQueueExisted) {
1098          returnValue = null;
1099          final boolean old = this.isEmpty();
1100          this.map.remove(key);
1101          this.firePropertyChange("empty", old, this.isEmpty());
1102        } else {
1103          // Nothing to do; the queue we added the event to was
1104          // created here, and was never added to our internal map, so
1105          // we're done.
1106        }
1107      } else if (!eventQueueExisted) {
1108        // We created the EventQueue we just added to; now we need to
1109        // store it.
1110        final boolean old = this.isEmpty();
1111        this.map.put(key, eventQueue);
1112        this.firePropertyChange("empty", old, this.isEmpty());        
1113        // Notify anyone blocked on our empty state that we're no
1114        // longer empty
1115        this.notifyAll();
1116      }
1117      
1118    }
1119
1120    if (this.logger.isLoggable(Level.FINER)) {
1121      this.logger.exiting(cn, mn, returnValue);
1122    }
1123    return returnValue;
1124  }
1125
1126  
1127  /*
1128   * PropertyChangeListener support.
1129   */
1130
1131
1132  /**
1133   * Adds the supplied {@link PropertyChangeListener} to this {@link
1134   * EventQueueCollection}'s collection of such listeners so that it
1135   * will be notified only when the bound property bearing the
1136   * supplied {@code name} changes.
1137   *
1138   * @param name the name of the bound property whose changes are of
1139   * interest; may be {@code null} in which case all property change
1140   * notifications will be dispatched to the supplied {@link
1141   * PropertyChangeListener}
1142   *
1143   * @param listener the {@link PropertyChangeListener} to add; may be
1144   * {@code null} in which case no action will be taken
1145   *
1146   * @see #addPropertyChangeListener(PropertyChangeListener)
1147   */
1148  public final void addPropertyChangeListener(final String name, final PropertyChangeListener listener) {
1149    if (listener != null) {
1150      this.propertyChangeSupport.addPropertyChangeListener(name, listener);
1151    }
1152  }
1153
1154  /**
1155   * Adds the supplied {@link PropertyChangeListener} to this {@link
1156   * EventQueueCollection}'s collection of such listeners so that it
1157   * will be notified whenever any bound property of this {@link
1158   * EventQueueCollection} changes.
1159   *
1160   * @param listener the {@link PropertyChangeListener} to add; may be
1161   * {@code null} in which case no action will be taken
1162   *
1163   * @see #addPropertyChangeListener(String, PropertyChangeListener)
1164   */
1165  public final void addPropertyChangeListener(final PropertyChangeListener listener) {
1166    if (listener != null) {
1167      this.propertyChangeSupport.addPropertyChangeListener(listener);
1168    }
1169  }
1170
1171  /**
1172   * Removes the supplied {@link PropertyChangeListener} from this
1173   * {@link EventQueueCollection} so that it will no longer be
1174   * notified of changes to bound properties bearing the supplied
1175   * {@code name}.
1176   *
1177   * @param name a bound property name; may be {@code null}
1178   *
1179   * @param listener the {@link PropertyChangeListener} to remove; may
1180   * be {@code null} in which case no action will be taken
1181   *
1182   * @see #addPropertyChangeListener(String, PropertyChangeListener)
1183   *
1184   * @see #removePropertyChangeListener(PropertyChangeListener)
1185   */
1186  public final void removePropertyChangeListener(final String name, final PropertyChangeListener listener) {
1187    if (listener != null) {
1188      this.propertyChangeSupport.removePropertyChangeListener(name, listener);
1189    }
1190  }
1191
1192  /**
1193   * Removes the supplied {@link PropertyChangeListener} from this
1194   * {@link EventQueueCollection} so that it will no longer be
1195   * notified of any changes to bound properties.
1196   *
1197   * @param listener the {@link PropertyChangeListener} to remove; may
1198   * be {@code null} in which case no action will be taken
1199   *
1200   * @see #addPropertyChangeListener(PropertyChangeListener)
1201   *
1202   * @see #removePropertyChangeListener(String, PropertyChangeListener)
1203   */
1204  public final void removePropertyChangeListener(final PropertyChangeListener listener) {
1205    if (listener != null) {
1206      this.propertyChangeSupport.removePropertyChangeListener(listener);
1207    }
1208  }
1209
1210  /**
1211   * Returns an array of {@link PropertyChangeListener}s that were
1212   * {@linkplain #addPropertyChangeListener(String,
1213   * PropertyChangeListener) registered} to receive notifications for
1214   * changes to bound properties bearing the supplied {@code name}.
1215   *
1216   * <p>This method never returns {@code null}.</p>
1217   *
1218   * @param name the name of a bound property; may be {@code null} in
1219   * which case an empty array will be returned
1220   *
1221   * @return a non-{@code null} array of {@link
1222   * PropertyChangeListener}s
1223   *
1224   * @see #getPropertyChangeListeners()
1225   *
1226   * @see #addPropertyChangeListener(String, PropertyChangeListener)
1227   *
1228   * @see #removePropertyChangeListener(String,
1229   * PropertyChangeListener)
1230   */
1231  public final PropertyChangeListener[] getPropertyChangeListeners(final String name) {
1232    return this.propertyChangeSupport.getPropertyChangeListeners(name);
1233  }
1234
1235  /**
1236   * Returns an array of {@link PropertyChangeListener}s that were
1237   * {@linkplain #addPropertyChangeListener(String,
1238   * PropertyChangeListener) registered} to receive notifications for
1239   * changes to all bound properties.
1240   *
1241   * <p>This method never returns {@code null}.</p>
1242   *
1243   * @return a non-{@code null} array of {@link
1244   * PropertyChangeListener}s
1245   *
1246   * @see #getPropertyChangeListeners(String)
1247   *
1248   * @see #addPropertyChangeListener(PropertyChangeListener)
1249   *
1250   * @see #removePropertyChangeListener(PropertyChangeListener)
1251   */
1252  public final PropertyChangeListener[] getPropertyChangeListeners() {
1253    return this.propertyChangeSupport.getPropertyChangeListeners();
1254  }
1255
1256  /**
1257   * Fires a {@link PropertyChangeEvent} to {@linkplain
1258   * #addPropertyChangeListener(String, PropertyChangeListener)
1259   * registered <tt>PropertyChangeListener</tt>s} if the supplied
1260   * {@code old} and {@code newValue} objects are non-{@code null} and
1261   * not equal to each other.
1262   *
1263   * @param propertyName the name of the bound property that might
1264   * have changed; may be {@code null} (indicating that some unknown
1265   * set of bound properties has changed)
1266   *
1267   * @param old the old value of the bound property in question; may
1268   * be {@code null}
1269   *
1270   * @param newValue the new value of the bound property; may be
1271   * {@code null}
1272   */
1273  protected final void firePropertyChange(final String propertyName, final Object old, final Object newValue) {
1274    final String cn = this.getClass().getName();
1275    final String mn = "firePropertyChange";
1276    if (this.logger.isLoggable(Level.FINER)) {
1277      this.logger.entering(cn, mn, new Object[] { propertyName, old, newValue });
1278    }
1279    this.propertyChangeSupport.firePropertyChange(propertyName, old, newValue);
1280    if (this.logger.isLoggable(Level.FINER)) {
1281      this.logger.exiting(cn, mn);
1282    }
1283  }
1284
1285  /**
1286   * Fires a {@link PropertyChangeEvent} to {@linkplain
1287   * #addPropertyChangeListener(String, PropertyChangeListener)
1288   * registered <tt>PropertyChangeListener</tt>s} if the supplied
1289   * {@code old} and {@code newValue} objects are non-{@code null} and
1290   * not equal to each other.
1291   *
1292   * @param propertyName the name of the bound property that might
1293   * have changed; may be {@code null} (indicating that some unknown
1294   * set of bound properties has changed)
1295   *
1296   * @param old the old value of the bound property in question
1297   *
1298   * @param newValue the new value of the bound property
1299   */
1300  protected final void firePropertyChange(final String propertyName, final int old, final int newValue) {
1301    final String cn = this.getClass().getName();
1302    final String mn = "firePropertyChange";
1303    if (this.logger.isLoggable(Level.FINER)) {
1304      this.logger.entering(cn, mn, new Object[] { propertyName, Integer.valueOf(old), Integer.valueOf(newValue) });
1305    }
1306    this.propertyChangeSupport.firePropertyChange(propertyName, old, newValue);
1307    if (this.logger.isLoggable(Level.FINER)) {
1308      this.logger.exiting(cn, mn);
1309    }
1310  }
1311
1312  /**
1313   * Fires a {@link PropertyChangeEvent} to {@linkplain
1314   * #addPropertyChangeListener(String, PropertyChangeListener)
1315   * registered <tt>PropertyChangeListener</tt>s} if the supplied
1316   * {@code old} and {@code newValue} objects are non-{@code null} and
1317   * not equal to each other.
1318   *
1319   * @param name the name of the bound property that might
1320   * have changed; may be {@code null} (indicating that some unknown
1321   * set of bound properties has changed)
1322   *
1323   * @param old the old value of the bound property in question
1324   *
1325   * @param newValue the new value of the bound property
1326   */
1327  protected final void firePropertyChange(final String name, final boolean old, final boolean newValue) {
1328    final String cn = this.getClass().getName();
1329    final String mn = "firePropertyChange";
1330    if (this.logger.isLoggable(Level.FINER)) {
1331      this.logger.entering(cn, mn, new Object[] { name, Boolean.valueOf(old), Boolean.valueOf(newValue) });
1332    }
1333    this.propertyChangeSupport.firePropertyChange(name, old, newValue);
1334    if (this.logger.isLoggable(Level.FINER)) {
1335      this.logger.exiting(cn, mn);
1336    }
1337  }
1338  
1339
1340  /*
1341   * Inner and nested classes.
1342   */
1343  
1344
1345  /**
1346   * A {@link RuntimeException} indicating that a {@link Consumer}
1347   * {@linkplain EventQueueCollection#start(Consumer) started} by an
1348   * {@link EventQueueCollection} has encountered an error that might
1349   * not happen if the consumption operation is retried.
1350   *
1351   * @author <a href="https://about.me/lairdnelson"
1352   * target="_parent">Laird Nelson</a>
1353   *
1354   * @see EventQueueCollection
1355   */
1356  public static final class TransientException extends RuntimeException {
1357
1358
1359    /*
1360     * Static fields.
1361     */
1362
1363
1364    /**
1365     * The version of this class for {@linkplain Serializable
1366     * serialization purposes}.
1367     *
1368     * @see Serializable
1369     */
1370    private static final long serialVersionUID = 1L;
1371
1372
1373    /*
1374     * Constructors.
1375     */
1376
1377
1378    /**
1379     * Creates a new {@link TransientException}.
1380     */
1381    public TransientException() {
1382      super();
1383    }
1384
1385    /**
1386     * Creates a new {@link TransientException}.
1387     *
1388     * @param message a detail message describing the error; may be
1389     * {@code null}
1390     */
1391    public TransientException(final String message) {
1392      super(message);
1393    }
1394
1395    /**
1396     * Creates a new {@link TransientException}.
1397     *
1398     * @param cause the {@link Throwable} that caused this {@link
1399     * TransientException} to be created; may be {@code null}
1400     */
1401    public TransientException(final Throwable cause) {
1402      super(cause);
1403    }
1404
1405    /**
1406     * Creates a new {@link TransientException}.
1407     *
1408     * @param message a detail message describing the error; may be
1409     * {@code null}
1410     *
1411     * @param cause the {@link Throwable} that caused this {@link
1412     * TransientException} to be created; may be {@code null}
1413     */
1414    public TransientException(final String message, final Throwable cause) {
1415      super(message, cause);
1416    }
1417    
1418  }
1419
1420}