CompletableFuture<NodeRenderer> (@Nullable Type type) { DebuggerManagerThreadImpl.assertIsManagerThread(); // in case evaluation is not possible, force default renderer if (!isEvaluationPossible()) { return CompletableFuture.completedFuture(getDefaultRenderer(type)); } try { Object renderer = myNodeRenderersMap.get(type); if (renderer instanceof NodeRenderer) { return CompletableFuture.completedFuture((NodeRenderer)renderer); } else if (renderer instanceof CompletableFuture) { //noinspection unchecked CompletableFuture<NodeRenderer> future = (CompletableFuture<NodeRenderer>)renderer; LOG.assertTrue(!future.isDone(), "Completed future for " + type); return future; } List<NodeRenderer> enabledRenderers = ContainerUtil.filter(myRenderers, NodeRenderer::isEnabled); CompletableFuture<NodeRenderer> res = DebuggerUtilsImpl.getFirstApplicableRenderer(enabledRenderers, type) .handle((r, throwable) -> { DebuggerManagerThreadImpl.assertIsManagerThread(); if (r == null || throwable != null) { r = getDefaultRenderer(type); // do not cache the fallback renderer myNodeRenderersMap.remove(type); } else { // TODO: may add a new (possibly incorrect) value after the cleanup in reloadRenderers myNodeRenderersMap.put(type, r); } return r; }); // check if future is already done myNodeRenderersMap.putIfAbsent(type, res); return res; } catch (ClassNotPreparedException e) { LOG.info(e); // use default, but do not cache return CompletableFuture.completedFuture(getDefaultRenderer(type)); } }