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