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}