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