AsyncIcon implements Icon { AsyncIcon(@NotNull CompletableFuture<? extends Icon> futureIcon, @NotNull Icon placeholderIcon, @Nullable Runnable onIconLoad) { myIcon = placeholderIcon; myW = placeholderIcon.getIconWidth(); myH = placeholderIcon.getIconHeight(); futureIcon.whenComplete((result, e) -> { if (e != null) { Logger.getInstance(AsyncIcon.class).warn("Unable to load AsyncIcon", e); return; } if (result != null) { myIcon = result; } if (onIconLoad != null) { onIconLoad.run(); } }); } AsyncIcon(@NotNull CompletableFuture<? extends Icon> futureIcon,
                   @NotNull Icon placeholderIcon,
                   @Nullable Runnable onIconLoad); @Override void paintIcon(Component c, Graphics g, int x, int y); @Override int getIconWidth(); @Override int getIconHeight();  }
@Test public void testAsyncIcon() { CompletableFuture<Icon> futureIcon = new CompletableFuture(); Icon placeholder = EmptyIcon.create(50, 40); AtomicInteger loaded = new AtomicInteger(0); AsyncIcon asyncIcon = new AsyncIcon(futureIcon, placeholder, loaded::incrementAndGet); assertEquals(0, loaded.get()); assertEquals(50, asyncIcon.getIconWidth()); assertEquals(40, asyncIcon.getIconHeight()); AtomicInteger painted = new AtomicInteger(0); Icon mockIcon = new Icon() { @Override public void paintIcon(Component c, Graphics g, int x, int y) { painted.incrementAndGet(); } @Override public int getIconWidth() { return 50; } @Override public int getIconHeight() { return 40; } }; asyncIcon.paintIcon(null, null, 1, 2); assertEquals(0, painted.get()); futureIcon.complete(mockIcon); assertEquals(1, loaded.get()); asyncIcon.paintIcon(null, null, 1, 2); assertEquals(1, painted.get()); asyncIcon = new AsyncIcon( CompletableFuture.completedFuture(mockIcon), placeholder, loaded::incrementAndGet); assertEquals(2, loaded.get()); asyncIcon.paintIcon(null, null, 1, 2); assertEquals(2, painted.get()); }