/** * This is the exception handler that need to be registered with * Thread.uncaughtExceptionHandler(..) * the main variable **/ public void handleException(Thread t, Throwable e) { log.error("Unhandled Exception", e); if (e instanceof ArrayIndexOutOfBoundsException) { boolean isUpdateCachedBoundsBug = Arrays.stream(e.getStackTrace()) .anyMatch(s -> s.getClassName().startsWith("javafx.scene.Parent") && "updateCachedBounds".equals(s.getMethodName())); if (isUpdateCachedBoundsBug) { log.error("Cleaning dirty nodes on the whole scene graph"); Platform.runLater(() -> { // make sure it s the UI thread FXUtils.traverseChildren(main, r -> { // walk the scene graph if (r instanceof Parent) recomputeBounds((Parent) r); return true; }); }); } else { log.error("Detected an AIOBE that is not an updateCachedBounds bug", e); } } else { log.error("Unhandled Exception", e); } } /** * empties the dirtyChildren list and set the dirtyChildrenCount to ZERO */ public static void recomputeBounds(Parent p) { try { log.error("Checking {} id: {}", p.getClass().getName(), p.getId()); Field dirtyChildren = Parent.class.getDeclaredField("dirtyChildren"); dirtyChildren.setAccessible(true); dirtyChildren.set(p, null); Field dirtyChildrenCount = Parent.class.getDeclaredField("dirtyChildrenCount"); dirtyChildrenCount.setAccessible(true); dirtyChildrenCount.set(p, 0); } catch (Exception e) { log.error("While recalculating bounds",e); } } /** * Walk the scene graph and apply p to each node. */ private static void traverseChildren(Parent root, Function p) { Boolean recurse = p.apply(root); if (recurse == null || recurse == false) { return; } if (root instanceof TitledPane) { // is a special case as the content is not in the list of children. Node content = ((TitledPane) root).getContent(); if (content instanceof Parent) { traverseChildren((Parent) content, p, depth + 1); } } ObservableList children = root.getChildrenUnmodifiable(); for (Node node : children) { if (node instanceof Parent) { traverseChildren((Parent) node, p, depth + 1); } } }