From bf55685204fb0ead7ea2f5ccb38205565580d621 Mon Sep 17 00:00:00 2001 From: Kamil Krzywanski Date: Thu, 23 Jul 2026 23:04:58 +0200 Subject: [PATCH 1/2] Fix "Save Value to File" for large debugger strings (#9452) Keep shortened-string metadata in a strong map and resolve quoted Variable.getValue() forms so Save can stream the full remote content instead of the truncated 100k+"..." preview. --- .../debugger/jpda/models/ShortenedStrings.java | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/models/ShortenedStrings.java b/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/models/ShortenedStrings.java index 85558c67168f..6835889f54dd 100644 --- a/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/models/ShortenedStrings.java +++ b/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/models/ShortenedStrings.java @@ -43,6 +43,7 @@ import java.lang.ref.WeakReference; import java.text.MessageFormat; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -73,7 +74,7 @@ */ public final class ShortenedStrings { - private static final Map infoStrings = new WeakHashMap(); + private static final Map infoStrings = new HashMap(); private static final Map stringsCache = new WeakHashMap(); private static final Set retrievingStrings = new HashSet(); private static final Map isLittleEndianCache = @@ -85,7 +86,6 @@ public final class ShortenedStrings { @Override public void sessionRemoved(Session session) { - // Clean up. WeakHashMap does not clean up if not touched. :-( int n = DebuggerManager.getDebuggerManager().getSessions().length; if (n == 0) { synchronized (infoStrings) { @@ -107,8 +107,19 @@ public void sessionRemoved(Session session) { private ShortenedStrings() {} public static StringInfo getShortenedInfo(String s) { + if (s == null) { + return null; + } synchronized (infoStrings) { - return infoStrings.get(s); + StringInfo info = infoStrings.get(s); + if (info != null) { + return info; + } + // Variable.getValue() wraps Strings in quotes: "content..." + if (s.length() >= 2 && s.charAt(0) == '"' && s.charAt(s.length() - 1) == '"') { + return infoStrings.get(s.substring(1, s.length() - 1)); + } + return null; } } From f2ac64a51175ce2ff2e652ac96d4807f0a5f8fee Mon Sep 17 00:00:00 2001 From: Kamil Krzywanski Date: Wed, 12 Aug 2026 20:17:09 +0200 Subject: [PATCH 2/2] Document why shortened-string metadata uses a strong HashMap Explain that WeakHashMap drops entries when the UI holds a different String instance (or quoted Variable.getValue form) than the map key. Signed-off-by: Kamil Krzywanski --- .../debugger/jpda/models/ShortenedStrings.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/models/ShortenedStrings.java b/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/models/ShortenedStrings.java index 6835889f54dd..9231be6678d5 100644 --- a/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/models/ShortenedStrings.java +++ b/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/models/ShortenedStrings.java @@ -74,6 +74,17 @@ */ public final class ShortenedStrings { + /** + * Shortened display text → full remote content helper. + *

+ * Must be a strong map for the duration of a debugger session. A + * {@link WeakHashMap} drops the entry as soon as nothing holds the exact + * key {@link String} instance. The UI / property editor often holds an + * equal but different instance (or a quoted form from + * {@code Variable.getValue()}), so lookup fails while the truncated + * preview is still shown and "Save Value to File" only writes that + * preview. Cleared when the last debugger session ends. + */ private static final Map infoStrings = new HashMap(); private static final Map stringsCache = new WeakHashMap(); private static final Set retrievingStrings = new HashSet(); @@ -86,6 +97,7 @@ public final class ShortenedStrings { @Override public void sessionRemoved(Session session) { + // Strong infoStrings map is session-scoped; clear when idle. int n = DebuggerManager.getDebuggerManager().getSessions().length; if (n == 0) { synchronized (infoStrings) {