(Object e)
| 376 | } |
| 377 | |
| 378 | public String exceptionMessage(Object e) { |
| 379 | // Walk the cause chain — reflection wraps the real exception in |
| 380 | // InvocationTargetException → CompletionException → ..., so the |
| 381 | // surface message alone usually hides the real exchange/parser error. |
| 382 | // The 4-frame stack tail at the end pinpoints opaque JDK exceptions |
| 383 | // (IndexOutOfBoundsException, NullPointerException) that have no message. |
| 384 | if (!(e instanceof Throwable)) return String.valueOf(e); |
| 385 | Throwable cur = (Throwable) e; |
| 386 | StringBuilder sb = new StringBuilder(); |
| 387 | int depth = 0; |
| 388 | Throwable deepest = cur; |
| 389 | while (cur != null && depth < 5) { |
| 390 | if (depth > 0) sb.append(" → caused by: "); |
| 391 | sb.append("[").append(cur.getClass().getSimpleName()).append("] "); |
| 392 | sb.append(String.valueOf(cur.getMessage())); |
| 393 | deepest = cur; |
| 394 | cur = cur.getCause(); |
| 395 | depth++; |
| 396 | } |
| 397 | if (deepest != null && deepest.getStackTrace() != null) { |
| 398 | StackTraceElement[] st = deepest.getStackTrace(); |
| 399 | int shown = 0; |
| 400 | for (int i = 0; i < st.length && shown < 4; i++) { |
| 401 | String fr = st[i].toString(); |
| 402 | if (fr.startsWith("java.base/jdk.internal") || fr.startsWith("java.base/java.util.Objects")) continue; |
| 403 | sb.append(" @ ").append(fr); |
| 404 | shown++; |
| 405 | } |
| 406 | } |
| 407 | return sb.toString(); |
| 408 | } |
| 409 | |
| 410 | public static CompletableFuture<Void> close(Object exchange) { |
| 411 | // do nothing |
no test coverage detected