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