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