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