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