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