001/*
002 * JGrapes Event Driven Framework
003 * Copyright (C) 2016-2018 Michael N. Lipp
004 * 
005 * This program is free software; you can redistribute it and/or modify it 
006 * under the terms of the GNU Affero General Public License as published by 
007 * the Free Software Foundation; either version 3 of the License, or 
008 * (at your option) any later version.
009 * 
010 * This program is distributed in the hope that it will be useful, but 
011 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
012 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License 
013 * for more details.
014 * 
015 * You should have received a copy of the GNU Affero General Public License along 
016 * with this program; if not, see <http://www.gnu.org/licenses/>.
017 */
018
019package org.jgrapes.core;
020
021import java.lang.ref.ReferenceQueue;
022import java.lang.ref.WeakReference;
023import java.time.Duration;
024import java.time.Instant;
025import java.util.Collections;
026import java.util.Comparator;
027import java.util.HashMap;
028import java.util.HashSet;
029import java.util.Iterator;
030import java.util.Map;
031import java.util.PriorityQueue;
032import java.util.Set;
033import java.util.WeakHashMap;
034import java.util.concurrent.ExecutorService;
035import java.util.concurrent.Executors;
036import java.util.concurrent.ThreadFactory;
037import java.util.concurrent.atomic.AtomicLong;
038import java.util.logging.Level;
039
040import org.jgrapes.core.annotation.ComponentManager;
041import org.jgrapes.core.events.Start;
042import org.jgrapes.core.events.Started;
043import org.jgrapes.core.internal.ComponentVertex;
044import org.jgrapes.core.internal.CoreUtils;
045import org.jgrapes.core.internal.GeneratorRegistry;
046
047/**
048 * This class provides some utility functions.
049 */
050@SuppressWarnings({ "PMD.TooManyMethods", "PMD.ClassNamingConventions",
051    "PMD.ExcessivePublicCount" })
052public class Components {
053
054    private static ExecutorService defaultExecutorService
055        = Executors.newCachedThreadPool(
056            new ThreadFactory() {
057                @SuppressWarnings("PMD.CommentRequired")
058                public Thread newThread(Runnable runnable) {
059                    Thread thread
060                        = Executors.defaultThreadFactory().newThread(runnable);
061                    thread.setDaemon(true);
062                    return thread;
063                }
064            });
065
066    private static ExecutorService timerExecutorService
067        = defaultExecutorService;
068
069    private Components() {
070    }
071
072    /**
073     * Return the default executor service for the framework.
074     * 
075     * @return the defaultExecutorService
076     */
077    public static ExecutorService defaultExecutorService() {
078        return defaultExecutorService;
079    }
080
081    /**
082     * Set the default executor service for the framework. The default 
083     * value is a cached thread pool (see @link 
084     * {@link Executors#newCachedThreadPool()}) with daemon threads.
085     * 
086     * @param defaultExecutorService the executor service to set
087     */
088    public static void setDefaultExecutorService(
089            ExecutorService defaultExecutorService) {
090        // If the timer executor service is set to the default
091        // executor service, adjust it to the new value as well.
092        if (timerExecutorService == Components.defaultExecutorService) {
093            timerExecutorService = defaultExecutorService;
094        }
095        Components.defaultExecutorService = defaultExecutorService;
096    }
097
098    /**
099     * Returns a component's manager. For a component that inherits
100     * from {@link org.jgrapes.core.Component} this method simply returns
101     * the component as it is its own manager.
102     * 
103     * For components that implement {@link ComponentType} but don't inherit from 
104     * {@link org.jgrapes.core.Component} the method returns the value of 
105     * the attribute annotated as manager slot. If this attribute is still
106     * empty, this method makes the component the root
107     * of a new tree and returns its manager.
108     * 
109     * @param component the component
110     * @return the component (with its manager attribute set)
111     */
112    public static Manager manager(ComponentType component) {
113        return ComponentVertex.componentVertex(component, null);
114    }
115
116    /**
117     * Returns a component's manager like {@link #manager(ComponentType)}.
118     * If the manager slot attribute is empty, the component is initialized
119     * with its component channel set to the given parameter. Invoking
120     * this method overrides any channel set in the
121     * {@link ComponentManager} annotation.
122     * 
123     * This method is usually invoked by the constructor of a class
124     * that implements {@link ComponentType}.
125     * 
126     * @param component the component
127     * @param componentChannel the channel that the component's 
128     * handlers listen on by default and that 
129     * {@link Manager#fire(Event, Channel...)} sends the event to 
130     * @return the component (with its manager attribute set)
131     * @see Component#Component(Channel)
132     */
133    public static Manager manager(
134            ComponentType component, Channel componentChannel) {
135        return ComponentVertex.componentVertex(component, componentChannel);
136    }
137
138    /**
139     * Fires a {@link Start} event with an associated
140     * {@link Started} completion event on the broadcast channel
141     * of the given application and wait for the completion of the
142     * <code>Start</code> event.
143     * 
144     * @param application the application to start
145     * @throws InterruptedException if the execution was interrupted
146     */
147    public static void start(ComponentType application)
148            throws InterruptedException {
149        manager(application).fire(new Start(), Channel.BROADCAST).get();
150    }
151
152    /**
153     * Wait until all generators and event queues are exhausted. When this
154     * stage is reached, nothing can happen anymore unless a new event is
155     * sent from an external thread.
156     * 
157     * @throws InterruptedException if the current thread was interrupted
158     * while waiting
159     */
160    public static void awaitExhaustion() throws InterruptedException {
161        GeneratorRegistry.instance().awaitExhaustion();
162    }
163
164    /**
165     * Wait until all generators and event queues are exhausted or
166     * the maximum wait time has expired.
167     * 
168     * @param timeout the wait time in milliseconds
169     * @return {@code true} if exhaustion state was reached
170     * @throws InterruptedException if the execution was interrupted 
171     * @see #awaitExhaustion()
172     */
173    public static boolean awaitExhaustion(long timeout)
174            throws InterruptedException {
175        return GeneratorRegistry.instance().awaitExhaustion(timeout);
176    }
177
178    /**
179     * Utility method that checks if an assertion error has occurred
180     * while executing handlers. If so, the error is thrown and
181     * the assertion error store is reset.
182     * <P>
183     * This method is intended for junit tests. It enables easy propagation
184     * of assertion failures to the main thread.
185     * 
186     * @throws AssertionError if an assertion error occurred while
187     * executing the application
188     */
189    public static void checkAssertions() {
190        CoreUtils.checkAssertions();
191    }
192
193    /**
194     * Returns the full name of the object's class together with an id (see 
195     * {@link #objectId(Object)}). The result can be used as a unique
196     * human readable identifier for arbitrary objects.
197     * 
198     * @param object
199     *            the object
200     * @return the object's name
201     */
202    public static String fullObjectName(Object object) {
203        if (object == null) {
204            return "<null>";
205        }
206        StringBuilder builder = new StringBuilder();
207        builder.append(object.getClass().getName())
208            .append('#')
209            .append(objectId(object));
210        return builder.toString();
211    }
212
213    /**
214     * Returns the simple name of the object's class together with an id 
215     * (see {@link #objectId(Object)}). Can be used to create a human
216     * readable, though not necessarily unique, label for an object.
217     * 
218     * @param object
219     *            the object
220     * @return the object's name
221     */
222    public static String simpleObjectName(Object object) {
223        if (object == null) {
224            return "<null>";
225        }
226        StringBuilder builder = new StringBuilder();
227        builder.append(simpleClassName(object.getClass()))
228            .append('#')
229            .append(objectId(object));
230        return builder.toString();
231    }
232
233    /**
234     * Returns the name of the object's class together with an id (see 
235     * {@link #objectId(Object)}). May be used to implement {@code toString()}
236     * with identifiable objects. If the log level is "finer", the full
237     * class name will be used for the returned value, else the simple name.
238     * 
239     * @param object
240     *            the object
241     * @return the object's name
242     */
243    public static String objectName(Object object) {
244        if (object == null) {
245            return "<null>";
246        }
247        StringBuilder builder = new StringBuilder();
248        builder.append(Components.className(object.getClass()))
249            .append('#')
250            .append(objectId(object));
251        return builder.toString();
252    }
253
254    private static Map<Object, String> objectIds // NOPMD
255        = new WeakHashMap<>();
256    private static Map<Class<?>, AtomicLong> idCounters // NOPMD
257        = new WeakHashMap<>();
258
259    private static String getId(Class<?> scope, Object object) {
260        if (object == null) {
261            return "?";
262        }
263        synchronized (objectIds) {
264            return objectIds.computeIfAbsent(object,
265                key -> Long.toString(idCounters
266                    .computeIfAbsent(scope, newKey -> new AtomicLong())
267                    .incrementAndGet()));
268
269        }
270    }
271
272    /**
273     * Returns the full name or simple name of the class depending
274     * on the log level.
275     * 
276     * @param clazz the class
277     * @return the name
278     */
279    public static String className(Class<?> clazz) {
280        if (CoreUtils.classNames.isLoggable(Level.FINER)) {
281            return clazz.getName();
282        } else {
283            return simpleClassName(clazz);
284        }
285    }
286
287    /**
288     * Returns the simple name of a class. Contrary to 
289     * {@link Class#getSimpleName()}, this method returns
290     * the last segement of the full name for anonymous
291     * classes (instead of an empty string).
292     * 
293     * @param clazz the class
294     * @return the name
295     */
296    public static String simpleClassName(Class<?> clazz) {
297        if (!clazz.isAnonymousClass()) {
298            return clazz.getSimpleName();
299        }
300        // Simple name of anonymous class is empty
301        String name = clazz.getName();
302        int lastDot = name.lastIndexOf('.');
303        if (lastDot <= 0) {
304            return name;
305        }
306        return name.substring(lastDot + 1);
307    }
308
309    /**
310     * Returns an id of the object that is unique within a specific scope. Ids
311     * are generated and looked up in the scope of the object's class unless the
312     * class implements {@link IdInfoProvider}.
313     * 
314     * @param object
315     *            the object
316     * @return the object's name
317     */
318    public static String objectId(Object object) {
319        if (object == null) {
320            return "?";
321        }
322        if (object instanceof IdInfoProvider) {
323            return getId(((IdInfoProvider) object).idScope(),
324                ((IdInfoProvider) object).idObject());
325        } else {
326            return getId(object.getClass(), object);
327        }
328    }
329
330    /**
331     * Implemented by classes that want a special class (scope) to be used
332     * for looking up their id or want to map to another object for getting the
333     * id (see {@link Components#objectId(Object)}).
334     */
335    public interface IdInfoProvider {
336
337        /**
338         * Returns the scope.
339         * 
340         * @return the scope
341         */
342        Class<?> idScope();
343
344        /**
345         * Returns the object to be used for generating the id.
346         * 
347         * @return the object (defaults to {@code this})
348         */
349        default Object idObject() {
350            return this;
351        }
352    }
353
354    /**
355     * Instances are added to the scheduler in order to be invoked
356     * at a given time.
357     */
358    @FunctionalInterface
359    public interface TimeoutHandler {
360
361        /**
362         * Invoked when the timeout occurs.
363         * 
364         * @param timer the timer that has timed out and needs handling
365         */
366        void timeout(Timer timer);
367    }
368
369    /**
370     * Represents a timer as created by 
371     * {@link Components#schedule(TimeoutHandler, Instant)}.
372     */
373    public static class Timer {
374        private final Scheduler scheduler;
375        private final TimeoutHandler timeoutHandler;
376        private Instant scheduledFor;
377
378        private Timer(Scheduler scheduler,
379                TimeoutHandler timeoutHandler, Instant scheduledFor) {
380            this.scheduler = scheduler;
381            this.timeoutHandler = timeoutHandler;
382            this.scheduledFor = scheduledFor;
383        }
384
385        /**
386         * Reschedules the timer for the given instant.
387         * 
388         * @param scheduledFor the instant
389         */
390        public void reschedule(Instant scheduledFor) {
391            scheduler.reschedule(this, scheduledFor);
392        }
393
394        /**
395         * Reschedules the timer for the given duration after now.
396         * 
397         * @param scheduledFor the timeout
398         */
399        public void reschedule(Duration scheduledFor) {
400            reschedule(Instant.now().plus(scheduledFor));
401        }
402
403        /**
404         * Returns the timeout handler of this timer.
405         * 
406         * @return the handler
407         */
408        public TimeoutHandler timeoutHandler() {
409            return timeoutHandler;
410        }
411
412        /**
413         * Returns the instant that this handler is scheduled for.
414         * 
415         * @return the instant or `null` if the timer has been cancelled.
416         */
417        public Instant scheduledFor() {
418            return scheduledFor;
419        }
420
421        /**
422         * Cancels this timer.
423         */
424        public void cancel() {
425            scheduler.cancel(this);
426        }
427    }
428
429    /**
430     * Returns the executor service used for executing timers.
431     * 
432     * @return the timer executor service
433     */
434    public static ExecutorService timerExecutorService() {
435        return timerExecutorService;
436    }
437
438    /**
439     * Sets the executor service used for executing timers.
440     * Defaults to the {@link #defaultExecutorService()}.
441     * 
442     * @param timerExecutorService the timerExecutorService to set
443     */
444    public static void setTimerExecutorService(
445            ExecutorService timerExecutorService) {
446        Components.timerExecutorService = timerExecutorService;
447    }
448
449    /**
450     * A general purpose scheduler.
451     */
452    private static class Scheduler extends Thread {
453
454        private final PriorityQueue<Timer> timers
455            = new PriorityQueue<>(10,
456                Comparator.comparing(Timer::scheduledFor));
457
458        /**
459         * Instantiates a new scheduler.
460         */
461        public Scheduler() {
462            setName("Components.Scheduler");
463            setDaemon(true);
464            start();
465        }
466
467        /**
468         * Schedule the handler and return the resulting timer.
469         *
470         * @param timeoutHandler the timeout handler
471         * @param scheduledFor the scheduled for
472         * @return the timer
473         */
474        public Timer schedule(
475                TimeoutHandler timeoutHandler, Instant scheduledFor) {
476            @SuppressWarnings("PMD.AccessorClassGeneration")
477            Timer timer = new Timer(this, timeoutHandler, scheduledFor);
478            synchronized (timers) {
479                timers.add(timer);
480                timers.notifyAll();
481            }
482            return timer;
483        }
484
485        private void reschedule(Timer timer, Instant scheduledFor) {
486            synchronized (timers) {
487                timers.remove(timer);
488                timer.scheduledFor = scheduledFor;
489                timers.add(timer);
490                timers.notifyAll();
491            }
492        }
493
494        private void cancel(Timer timer) {
495            synchronized (timers) {
496                timers.remove(timer);
497                timers.notifyAll();
498                timer.scheduledFor = null;
499            }
500        }
501
502        @Override
503        public void run() {
504            while (true) {
505                while (true) {
506                    @SuppressWarnings("PMD.AvoidFinalLocalVariable")
507                    final Timer first;
508                    synchronized (timers) {
509                        first = timers.peek();
510                        if (first == null
511                            || first.scheduledFor().isAfter(Instant.now())) {
512                            break;
513                        }
514                        timers.poll();
515                    }
516                    timerExecutorService.submit(
517                        () -> first.timeoutHandler().timeout(first));
518                }
519                try {
520                    synchronized (timers) {
521                        if (timers.size() == 0) {
522                            timers.wait();
523                        } else {
524                            timers
525                                .wait(Math.max(1,
526                                    Duration.between(Instant.now(),
527                                        timers.peek().scheduledFor())
528                                        .toMillis()));
529                        }
530                    }
531                } catch (Exception e) { // NOPMD
532                    // Keep running.
533                }
534            }
535        }
536    }
537
538    @SuppressWarnings("PMD.FieldDeclarationsShouldBeAtStartOfClass")
539    private static Scheduler scheduler = new Scheduler();
540
541    /**
542     * Schedules the given timeout handler for the given instance. 
543     * 
544     * @param timeoutHandler the handler
545     * @param scheduledFor the instance in time
546     * @return the timer
547     */
548    public static Timer schedule(
549            TimeoutHandler timeoutHandler, Instant scheduledFor) {
550        return scheduler.schedule(timeoutHandler, scheduledFor);
551    }
552
553    /**
554     * Schedules the given timeout handler for the given 
555     * offset from now. 
556     * 
557     * @param timeoutHandler the handler
558     * @param scheduledFor the time to wait
559     * @return the timer
560     */
561    public static Timer schedule(
562            TimeoutHandler timeoutHandler, Duration scheduledFor) {
563        return scheduler.schedule(
564            timeoutHandler, Instant.now().plus(scheduledFor));
565    }
566
567    /**
568     * Puts the given key and value in the given {@link Map} and
569     * returns the map. Looks ugly when nested, but comes in handy 
570     * sometimes.
571     *
572     * @param <K> the key type
573     * @param <V> the value type
574     * @param map the map
575     * @param key the key
576     * @param value the value
577     * @return the map
578     */
579    public static <K, V> Map<K, V> put(Map<K, V> map, K key, V value) {
580        map.put(key, value);
581        return map;
582    }
583
584    /**
585     * Provisional replacement for method available in Java 9. 
586     * 
587     * @return an empty map
588     */
589    public static <K, V> Map<K, V> mapOf() {
590        return new HashMap<>();
591    }
592
593    /**
594     * Provisional replacement for method available in Java 9. 
595     * 
596     * @return an immutable map filled with the given values
597     */
598    @SuppressWarnings({ "PMD.ShortVariable", "PMD.AvoidDuplicateLiterals" })
599    public static <K, V> Map<K, V> mapOf(K k1, V v1) {
600        @SuppressWarnings("PMD.UseConcurrentHashMap")
601        Map<K, V> result = new HashMap<>();
602        result.put(k1, v1);
603        return Collections.unmodifiableMap(result);
604    }
605
606    /**
607     * Provisional replacement for method available in Java 9. 
608     * 
609     * @return an immutable map filled with the given values
610     */
611    @SuppressWarnings("PMD.ShortVariable")
612    public static <K, V> Map<K, V> mapOf(K k1, V v1, K k2, V v2) {
613        @SuppressWarnings("PMD.UseConcurrentHashMap")
614        Map<K, V> result = new HashMap<>();
615        result.put(k1, v1);
616        result.put(k2, v2);
617        return Collections.unmodifiableMap(result);
618    }
619
620    /**
621     * Provisional replacement for method available in Java 9. 
622     * 
623     * @return an immutable map filled with the given values
624     */
625    @SuppressWarnings("PMD.ShortVariable")
626    public static <K, V> Map<K, V> mapOf(K k1, V v1, K k2, V v2, K k3, V v3) {
627        @SuppressWarnings("PMD.UseConcurrentHashMap")
628        Map<K, V> result = new HashMap<>();
629        result.put(k1, v1);
630        result.put(k2, v2);
631        result.put(k3, v3);
632        return Collections.unmodifiableMap(result);
633    }
634
635    /**
636     * Provisional replacement for method available in Java 9. 
637     * 
638     * @return an immutable map filled with the given values
639     */
640    @SuppressWarnings("PMD.ShortVariable")
641    public static <K, V> Map<K, V> mapOf(K k1, V v1, K k2, V v2, K k3, V v3,
642            K k4, V v4) {
643        @SuppressWarnings("PMD.UseConcurrentHashMap")
644        Map<K, V> result = new HashMap<>();
645        result.put(k1, v1);
646        result.put(k2, v2);
647        result.put(k3, v3);
648        result.put(k4, v4);
649        return Collections.unmodifiableMap(result);
650    }
651
652    /**
653     * Provisional replacement for method available in Java 9. 
654     * 
655     * @return an immutable map filled with the given values
656     */
657    @SuppressWarnings({ "PMD.ExcessiveParameterList", "PMD.ShortVariable",
658        "PMD.AvoidDuplicateLiterals" })
659    public static <K, V> Map<K, V> mapOf(K k1, V v1, K k2, V v2, K k3, V v3,
660            K k4, V v4, K k5, V v5) {
661        @SuppressWarnings("PMD.UseConcurrentHashMap")
662        Map<K, V> result = new HashMap<>();
663        result.put(k1, v1);
664        result.put(k2, v2);
665        result.put(k3, v3);
666        result.put(k4, v4);
667        result.put(k5, v5);
668        return Collections.unmodifiableMap(result);
669    }
670
671    /**
672     * Provisional replacement for method available in Java 9. 
673     * 
674     * @return an immutable map filled with the given values
675     */
676    @SuppressWarnings({ "PMD.ExcessiveParameterList", "PMD.ShortVariable" })
677    public static <K, V> Map<K, V> mapOf(K k1, V v1, K k2, V v2, K k3, V v3,
678            K k4, V v4, K k5, V v5, K k6, V v6) {
679        @SuppressWarnings("PMD.UseConcurrentHashMap")
680        Map<K, V> result = new HashMap<>();
681        result.put(k1, v1);
682        result.put(k2, v2);
683        result.put(k3, v3);
684        result.put(k4, v4);
685        result.put(k5, v5);
686        result.put(k6, v6);
687        return Collections.unmodifiableMap(result);
688    }
689
690    /**
691     * Provisional replacement for method available in Java 9. 
692     * 
693     * @return an immutable map filled with the given values
694     */
695    @SuppressWarnings({ "PMD.ExcessiveParameterList", "PMD.ShortVariable" })
696    public static <K, V> Map<K, V> mapOf(K k1, V v1, K k2, V v2, K k3, V v3,
697            K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7) {
698        @SuppressWarnings("PMD.UseConcurrentHashMap")
699        Map<K, V> result = new HashMap<>();
700        result.put(k1, v1);
701        result.put(k2, v2);
702        result.put(k3, v3);
703        result.put(k4, v4);
704        result.put(k5, v5);
705        result.put(k6, v6);
706        result.put(k7, v7);
707        return Collections.unmodifiableMap(result);
708    }
709
710    /**
711     * Provisional replacement for method available in Java 9. 
712     * 
713     * @return an immutable map filled with the given values
714     */
715    @SuppressWarnings({ "PMD.ExcessiveParameterList", "PMD.ShortVariable" })
716    public static <K, V> Map<K, V> mapOf(K k1, V v1, K k2, V v2, K k3, V v3,
717            K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7, K k8, V v8) {
718        @SuppressWarnings("PMD.UseConcurrentHashMap")
719        Map<K, V> result = new HashMap<>();
720        result.put(k1, v1);
721        result.put(k2, v2);
722        result.put(k3, v3);
723        result.put(k4, v4);
724        result.put(k5, v5);
725        result.put(k6, v6);
726        result.put(k7, v7);
727        result.put(k8, v8);
728        return Collections.unmodifiableMap(result);
729    }
730
731    /**
732     * Provisional replacement for method available in Java 9. 
733     * 
734     * @return an immutable map filled with the given values
735     */
736    @SuppressWarnings({ "PMD.ExcessiveParameterList", "PMD.ShortVariable" })
737    public static <K, V> Map<K, V> mapOf(K k1, V v1, K k2, V v2, K k3, V v3,
738            K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7, K k8, V v8,
739            K k9, V v9) {
740        @SuppressWarnings("PMD.UseConcurrentHashMap")
741        Map<K, V> result = new HashMap<>();
742        result.put(k1, v1);
743        result.put(k2, v2);
744        result.put(k3, v3);
745        result.put(k4, v4);
746        result.put(k5, v5);
747        result.put(k6, v6);
748        result.put(k7, v7);
749        result.put(k8, v8);
750        result.put(k9, v9);
751        return Collections.unmodifiableMap(result);
752    }
753
754    /**
755     * Provisional replacement for method available in Java 9. 
756     * 
757     * @return an immutable map filled with the given values
758     */
759    @SuppressWarnings({ "PMD.ExcessiveParameterList", "PMD.ShortVariable" })
760    public static <K, V> Map<K, V> mapOf(K k1, V v1, K k2, V v2, K k3, V v3,
761            K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7, K k8, V v8,
762            K k9, V v9, K k10, V v10) {
763        @SuppressWarnings("PMD.UseConcurrentHashMap")
764        Map<K, V> result = new HashMap<>();
765        result.put(k1, v1);
766        result.put(k2, v2);
767        result.put(k3, v3);
768        result.put(k4, v4);
769        result.put(k5, v5);
770        result.put(k6, v6);
771        result.put(k7, v7);
772        result.put(k8, v8);
773        result.put(k9, v9);
774        result.put(k10, v10);
775        return Collections.unmodifiableMap(result);
776    }
777
778    /**
779     * An index of pooled items. Each key is associated with a set
780     * of values. Values can be added or retrieved from the set.
781     *
782     * @param <K> the key type
783     * @param <V> the value type
784     */
785    public static class PoolingIndex<K, V> {
786
787        @SuppressWarnings("PMD.UseConcurrentHashMap")
788        private final Map<K, Set<ValueReference>> backing = new HashMap<>();
789        private final ReferenceQueue<V> orphanedEntries
790            = new ReferenceQueue<>();
791
792        @SuppressWarnings("PMD.CommentRequired")
793        private class ValueReference extends WeakReference<V> {
794
795            private final K key;
796
797            public ValueReference(K key, V referent) {
798                super(referent, orphanedEntries);
799                this.key = key;
800            }
801
802            /*
803             * (non-Javadoc)
804             * 
805             * @see java.lang.Object#hashCode()
806             */
807            @Override
808            public int hashCode() {
809                V value = get();
810                if (value == null) {
811                    return 0;
812                }
813                return value.hashCode();
814            }
815
816            /*
817             * (non-Javadoc)
818             * 
819             * @see java.lang.Object#equals(java.lang.Object)
820             */
821            @Override
822            public boolean equals(Object obj) {
823                if (obj == null) {
824                    return false;
825                }
826                if (!obj.getClass().equals(ValueReference.class)) {
827                    return false;
828                }
829                V value1 = get();
830                @SuppressWarnings("unchecked")
831                V value2 = ((ValueReference) obj).get();
832                if (value1 == null || value2 == null) {
833                    return false;
834                }
835                return value1.equals(value2);
836            }
837        }
838
839        @SuppressWarnings("PMD.CompareObjectsWithEquals")
840        private void cleanOrphaned() {
841            while (true) {
842                @SuppressWarnings("unchecked")
843                ValueReference orphaned
844                    = (ValueReference) orphanedEntries.poll();
845                if (orphaned == null) {
846                    return;
847                }
848                synchronized (this) {
849                    Set<ValueReference> set = backing.get(orphaned.key);
850                    if (set == null) {
851                        continue;
852                    }
853                    Iterator<ValueReference> iter = set.iterator();
854                    while (iter.hasNext()) {
855                        ValueReference ref = iter.next();
856                        if (ref == orphaned) {
857                            iter.remove();
858                            if (set.isEmpty()) {
859                                backing.remove(orphaned.key);
860                            }
861                            break;
862                        }
863                    }
864                }
865            }
866        }
867
868        /**
869         * Remove all entries.
870         */
871        public void clear() {
872            backing.clear();
873            cleanOrphaned();
874        }
875
876        /**
877         * Checks if the key is in the index.
878         *
879         * @param key the key
880         * @return true, if successful
881         */
882        public boolean containsKey(Object key) {
883            return backing.containsKey(key);
884        }
885
886        /**
887         * Checks if the index is empty.
888         *
889         * @return true, if is empty
890         */
891        public boolean isEmpty() {
892            return backing.isEmpty();
893        }
894
895        /**
896         * Returns all keys.
897         *
898         * @return the sets the
899         */
900        public Set<K> keySet() {
901            synchronized (this) {
902                return backing.keySet();
903            }
904        }
905
906        /**
907         * Adds the value to the pool of values associated with the key.
908         *
909         * @param key the key
910         * @param value the value
911         * @return the v
912         */
913        public V add(K key, V value) {
914            synchronized (this) {
915                cleanOrphaned();
916                backing.computeIfAbsent(key, k -> new HashSet<>())
917                    .add(new ValueReference(key, value));
918                return value;
919            }
920        }
921
922        /**
923         * Retrives and removes an item from the pool associated with the key.
924         *
925         * @param key the key
926         * @return the removed item or `null` if the pool is empty
927         */
928        @SuppressWarnings("PMD.AvoidBranchingStatementAsLastInLoop")
929        public V poll(K key) {
930            synchronized (this) {
931                cleanOrphaned();
932                Set<ValueReference> set = backing.get(key);
933                if (set == null) {
934                    return null;
935                }
936                Iterator<ValueReference> iter = set.iterator();
937                while (iter.hasNext()) {
938                    ValueReference ref = iter.next();
939                    V value = ref.get();
940                    iter.remove();
941                    if (value == null) {
942                        continue;
943                    }
944                    if (set.isEmpty()) {
945                        backing.remove(key);
946                    }
947                    return value;
948                }
949            }
950            return null;
951        }
952
953        /**
954         * Removes all values associated with the key.
955         *
956         * @param key the key
957         */
958        public void removeAll(K key) {
959            synchronized (this) {
960                cleanOrphaned();
961                backing.remove(key);
962            }
963        }
964
965        /**
966         * Removes the given value from the pool associated with the given key.
967         *
968         * @param key the key
969         * @param value the value
970         * @return the v
971         */
972        @SuppressWarnings("PMD.DataflowAnomalyAnalysis")
973        public V remove(K key, V value) {
974            synchronized (this) {
975                cleanOrphaned();
976                Set<ValueReference> set = backing.get(key);
977                if (set == null) {
978                    return null;
979                }
980                Iterator<ValueReference> iter = set.iterator();
981                while (iter.hasNext()) {
982                    ValueReference ref = iter.next();
983                    V stored = ref.get();
984                    boolean found = false;
985                    if (stored == null) {
986                        iter.remove();
987                    }
988                    if (stored.equals(value)) {
989                        iter.remove();
990                        found = true;
991                    }
992                    if (set.isEmpty()) {
993                        backing.remove(key);
994                    }
995                    if (found) {
996                        return value;
997                    }
998                }
999            }
1000            return null;
1001        }
1002
1003        /**
1004         * Removes the value from the first pool in which it is found.
1005         *
1006         * @param value the value
1007         * @return the value or `null` if the value is not found
1008         */
1009        public V remove(V value) {
1010            synchronized (this) {
1011                for (Set<ValueReference> set : backing.values()) {
1012                    Iterator<ValueReference> iter = set.iterator();
1013                    while (iter.hasNext()) {
1014                        ValueReference ref = iter.next();
1015                        V stored = ref.get();
1016                        if (stored == null) {
1017                            iter.remove();
1018                        }
1019                        if (stored.equals(value)) {
1020                            iter.remove();
1021                            return value;
1022                        }
1023                    }
1024                }
1025            }
1026            return null;
1027        }
1028
1029        /**
1030         * Returns the number of keys in the index.
1031         *
1032         * @return the numer of keys
1033         */
1034        public int keysSize() {
1035            return backing.size();
1036        }
1037
1038    }
1039}