/******************************************************************************* * Copyright (c) 2000, 2015 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.jdt.internal.debug.ui; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.debug.core.DebugEvent; import org.eclipse.debug.core.DebugException; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.IDebugEventSetListener; import org.eclipse.debug.core.ILaunch; import org.eclipse.debug.core.ILaunchesListener; import org.eclipse.debug.core.model.IDebugTarget; import org.eclipse.debug.core.model.IVariable; import org.eclipse.debug.ui.DebugUITools; import org.eclipse.debug.ui.IDebugUIConstants; import org.eclipse.debug.ui.IValueDetailListener; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.Signature; import org.eclipse.jdt.debug.core.IEvaluationRunnable; import org.eclipse.jdt.debug.core.IJavaArray; import org.eclipse.jdt.debug.core.IJavaArrayType; import org.eclipse.jdt.debug.core.IJavaClassType; import org.eclipse.jdt.debug.core.IJavaDebugTarget; import org.eclipse.jdt.debug.core.IJavaInterfaceType; import org.eclipse.jdt.debug.core.IJavaObject; import org.eclipse.jdt.debug.core.IJavaPrimitiveValue; import org.eclipse.jdt.debug.core.IJavaReferenceType; import org.eclipse.jdt.debug.core.IJavaStackFrame; import org.eclipse.jdt.debug.core.IJavaThread; import org.eclipse.jdt.debug.core.IJavaType; import org.eclipse.jdt.debug.core.IJavaValue; import org.eclipse.jdt.debug.eval.IAstEvaluationEngine; import org.eclipse.jdt.debug.eval.ICompiledExpression; import org.eclipse.jdt.debug.eval.IEvaluationListener; import org.eclipse.jdt.debug.eval.IEvaluationResult; import org.eclipse.jdt.internal.debug.core.JDIDebugPlugin; import org.eclipse.jdt.internal.debug.core.JavaDebugUtils; import org.eclipse.jdt.internal.debug.core.logicalstructures.JDIAllInstancesValue; import org.eclipse.jdt.internal.debug.core.model.JDINullValue; import org.eclipse.jdt.internal.debug.core.model.JDIReferenceListValue; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.osgi.util.NLS; import com.sun.jdi.InvocationException; /** * Generates strings for the detail pane of views displaying java elements. */ public class JavaDetailFormattersManager implements IPropertyChangeListener, IDebugEventSetListener, ILaunchesListener { /** * The default detail formatters manager. */ static private JavaDetailFormattersManager fgDefault; /** * Return the default detail formatters manager. * * @return default detail formatters manager. */ static public JavaDetailFormattersManager getDefault() { if (fgDefault == null) { fgDefault= new JavaDetailFormattersManager(); } return fgDefault; } /** * Map of types to the associated formatter (code snippet). * (<code>String</code> -> <code>String</code>) */ private HashMap<String, DetailFormatter> fDetailFormattersMap; /** * Cache of compiled expressions. * Associate a pair type name/debug target to a compiled expression. */ private HashMap<Key, Expression> fCacheMap; /** * JavaDetailFormattersManager constructor. */ private JavaDetailFormattersManager() { populateDetailFormattersMap(); JDIDebugUIPlugin.getDefault().getPreferenceStore().addPropertyChangeListener(this); DebugPlugin.getDefault().getLaunchManager().addLaunchListener(this); DebugPlugin.getDefault().addDebugEventListener(this); DebugUITools.getPreferenceStore().addPropertyChangeListener(this); fCacheMap= new HashMap<>(); } /** * Populate the detail formatters map with data from preferences. */ private void populateDetailFormattersMap() { String[] detailFormattersList= JavaDebugOptionsManager.parseList(JDIDebugUIPlugin.getDefault().getPreferenceStore().getString(IJDIPreferencesConstants.PREF_DETAIL_FORMATTERS_LIST)); fDetailFormattersMap= new HashMap<>(detailFormattersList.length / 3); for (int i= 0, length= detailFormattersList.length; i < length;) { String typeName= detailFormattersList[i++]; String snippet= detailFormattersList[i++].replace('\u0000', ','); boolean enabled= ! JavaDetailFormattersPreferencePage.DETAIL_FORMATTER_IS_DISABLED.equals(detailFormattersList[i++]); fDetailFormattersMap.put(typeName, new DetailFormatter(typeName, snippet, enabled)); } } /** * Compute asynchronously the 'toString' of the given value. If a formatter is associated to * the type of the given value, this formatter is used instead of the <code>toString()</code> * method. * The result is return through the listener. * * @param objectValue the value to 'format' * @param thread the thread to use to performed the evaluation * @param listener the listener */ public void computeValueDetail(final IJavaValue objectValue, final IJavaThread thread, final IValueDetailListener listener) { thread.queueRunnable(new Runnable() { @Override public void run() { resolveFormatter(objectValue, thread, listener); } }); } private void resolveFormatter(final IJavaValue value, final IJavaThread thread, final IValueDetailListener listener) { EvaluationListener evaluationListener= new EvaluationListener(value, thread, listener); if (value instanceof IJavaObject) { IJavaObject objectValue= (IJavaObject) value; try { if(value instanceof JDIAllInstancesValue) { listener.detailComputed(value, ((JDIAllInstancesValue)value).getDetailString()); return; } if(value instanceof JDIReferenceListValue) { listener.detailComputed(value, ((JDIReferenceListValue)value).getDetailString()); return; } IJavaDebugTarget debugTarget= (IJavaDebugTarget) thread.getDebugTarget(); // get the compiled expression to use Expression expression= getCompiledExpression(objectValue, debugTarget, thread); if (expression != null) { expression.getEngine().evaluateExpression(expression.getExpression(), objectValue, thread, evaluationListener, DebugEvent.EVALUATION_IMPLICIT, false); return; } } catch (CoreException e) { listener.detailComputed(value, e.toString()); return; } } try { evaluationListener.valueToString(value); } catch (DebugException e) { String detail = e.getStatus().getMessage(); if (e.getStatus().getException() instanceof UnsupportedOperationException) { detail = DebugUIMessages.JavaDetailFormattersManager_7; } else if (e.getStatus().getCode() == IJavaThread.ERR_INCOMPATIBLE_THREAD_STATE) { detail = DebugUIMessages.JavaDetailFormattersManager_6; } listener.detailComputed(value, detail); } } private IJavaProject getJavaProject(IJavaObject javaValue, IJavaThread thread) throws CoreException { IType type = null; if (javaValue instanceof IJavaArray) { IJavaArrayType arrType = (IJavaArrayType) javaValue.getJavaType(); IJavaType compType = arrType.getComponentType(); while (compType instanceof IJavaArrayType) { compType = ((IJavaArrayType)compType).getComponentType(); } type = JavaDebugUtils.resolveType(compType); } else { type = JavaDebugUtils.resolveType(javaValue); } if (type != null) { return type.getJavaProject(); } IJavaStackFrame stackFrame= null; IJavaDebugTarget target = javaValue.getDebugTarget().getAdapter(IJavaDebugTarget.class); if (target != null) { stackFrame= (IJavaStackFrame) thread.getTopStackFrame(); if (stackFrame != null && !stackFrame.getDebugTarget().equals(target)) { stackFrame= null; } } if (stackFrame == null) { return null; } return JavaDebugUtils.resolveJavaProject(stackFrame); } /** * Searches the listing of implemented interfaces to see if one of them has a detail formatter * @param type the type whose interfaces you want to inspect * @return an associated details formatter of <code>null</code> if none is found * @since 3.2 */ public DetailFormatter getDetailFormatterFromInterface(IJavaClassType type) { try { IJavaInterfaceType[] inter = type.getAllInterfaces(); Object formatter = null; for (int i = 0; i < inter.length; i++) { formatter = fDetailFormattersMap.get(inter[i].getName()); if(formatter != null) { return (DetailFormatter) formatter; } } return null; } catch(DebugException e) {return null;} } /** * Returns if the specified <code>IJavaType</code> has a detail formatter on one of its interfaces * @param type the type to inspect * @return true if there is an existing detail formatter on one of the types' interfaces, false otherwise * @since 3.2 */ public boolean hasInterfaceDetailFormatter(IJavaType type) { if(type instanceof IJavaClassType) { return getDetailFormatterFromInterface((IJavaClassType) type) != null; } return false; } /** * Searches the superclass hierarchy to see if any of the specified classes parents have a detail formatter * @param type the current type. Ideally this should be the first parent class. * @return the first detail formatter located walking up the superclass hierarchy or <code>null</code> if none are found * @since 3.2 */ public DetailFormatter getDetailFormatterFromSuperclass(IJavaClassType type) { try { if(type == null) { return null; } DetailFormatter formatter = fDetailFormattersMap.get(type.getName()); if(formatter != null && formatter.isEnabled()) { return formatter; } return getDetailFormatterFromSuperclass(type.getSuperclass()); } catch(DebugException e) {return null;} } /** * Returns if one of the parent classes of the specified type has a detail formatter * @param type the type to inspect * @return true if one of the parent classes of the type has a detail formatter, false otherwise * @since 3.2 */ public boolean hasSuperclassDetailFormatter(IJavaType type) { if(type instanceof IJavaClassType) { return getDetailFormatterFromSuperclass((IJavaClassType) type) != null; } return false; } public boolean hasAssociatedDetailFormatter(IJavaType type) { return getAssociatedDetailFormatter(type) != null; } public DetailFormatter getAssociatedDetailFormatter(IJavaType type) { String typeName = ""; //$NON-NLS-1$ try { while (type instanceof IJavaArrayType) { type = ((IJavaArrayType)type).getComponentType(); } if (type instanceof IJavaClassType) { typeName = type.getName(); } else { return null; } } catch (DebugException e) {return null;} return fDetailFormattersMap.get(typeName); } public void setAssociatedDetailFormatter(DetailFormatter detailFormatter) { fDetailFormattersMap.put(detailFormatter.getTypeName(), detailFormatter); savePreference(); } private void savePreference() { Collection<DetailFormatter> valuesList= fDetailFormattersMap.values(); String[] values= new String[valuesList.size() * 3]; int i= 0; for (Iterator<DetailFormatter> iter= valuesList.iterator(); iter.hasNext();) { DetailFormatter detailFormatter= iter.next(); values[i++]= detailFormatter.getTypeName(); values[i++]= detailFormatter.getSnippet().replace(',','\u0000'); values[i++]= detailFormatter.isEnabled() ? JavaDetailFormattersPreferencePage.DETAIL_FORMATTER_IS_ENABLED : JavaDetailFormattersPreferencePage.DETAIL_FORMATTER_IS_DISABLED; } String pref = JavaDebugOptionsManager.serializeList(values); JDIDebugUIPlugin.getDefault().getPreferenceStore().setValue(IJDIPreferencesConstants.PREF_DETAIL_FORMATTERS_LIST, pref); } /** * Return the detail formatter (code snippet) associate with * the given type or one of its super types, super interfaces. * @param type the class type * @return the code snippet for the given type / super type / super interface * @throws DebugException if there is problem computing the snippet */ private String getDetailFormatter(IJavaClassType type) throws DebugException { String snippet= getDetailFormatterSuperClass(type); if (snippet != null) { return snippet; } IJavaInterfaceType[] allInterfaces= type.getAllInterfaces(); for (int i= 0; i < allInterfaces.length; i++) { DetailFormatter detailFormatter= fDetailFormattersMap.get(allInterfaces[i].getName()); if (detailFormatter != null && detailFormatter.isEnabled()) { return detailFormatter.getSnippet(); } } return null; } /** * Return the detail formatter (code snippet) associate with * the given type or one of its super types. * @param type the class type * @return the snippet for the given class / super class * @throws DebugException if there is a problem computing the snippet */ private String getDetailFormatterSuperClass(IJavaClassType type) throws DebugException { if (type == null) { return null; } DetailFormatter detailFormatter= fDetailFormattersMap.get(type.getName()); if (detailFormatter != null && detailFormatter.isEnabled()) { return detailFormatter.getSnippet(); } return getDetailFormatterSuperClass(type.getSuperclass()); } /** * Return the expression which corresponds to the code formatter associated with the type of * the given object or <code>null</code> if none. * * The code snippet is compiled in the context of the given object. * @param javaObject the Java object * @param debugTarget the target * @param thread the thread context * @return the compiled expression to be evaluated * @throws CoreException is a problem occurs compiling the expression */ private Expression getCompiledExpression(IJavaObject javaObject, IJavaDebugTarget debugTarget, IJavaThread thread) throws CoreException { IJavaType type = javaObject.getJavaType(); if (type == null) { return null; } String typeName = type.getName(); Key key = new Key(typeName, debugTarget); if (fCacheMap.containsKey(key)) { return fCacheMap.get(key); } String snippet = null; if (type instanceof IJavaClassType) { snippet = getDetailFormatter((IJavaClassType) type); } else if (type instanceof IJavaArrayType) { snippet = getArraySnippet((IJavaArray) javaObject); } if (snippet != null) { IJavaProject project = getJavaProject(javaObject, thread); if (project != null) { IAstEvaluationEngine evaluationEngine = JDIDebugPlugin .getDefault().getEvaluationEngine(project, debugTarget); ICompiledExpression res = evaluationEngine .getCompiledExpression(snippet, javaObject); if (res != null) { Expression exp = new Expression(res, evaluationEngine); fCacheMap.put(key, exp); return exp; } } } return null; } protected String getArraySnippet(IJavaArray value) throws DebugException { String signature = value.getSignature(); int nesting = Signature.getArrayCount(signature); if (nesting > 1) { // for nested primitive arrays, print everything String sig = Signature.getElementType(signature); if (sig.length() == 1 || "Ljava/lang/String;".equals(sig)) { //$NON-NLS-1$ // return null so we get to "valueToString(IJavaValue)" for primitive and string types return null; } } if (((IJavaArrayType)value.getJavaType()).getComponentType() instanceof IJavaReferenceType) { int length = value.getLength(); // guestimate at max entries to print based on char/space/comma per entry int maxLength = getMaxDetailLength(); if (maxLength > 0){ int maxEntries = (maxLength / 3) + 1; if (length > maxEntries) { StringBuffer snippet = new StringBuffer(); snippet.append("Object[] shorter = new Object["); //$NON-NLS-1$ snippet.append(maxEntries); snippet.append("]; System.arraycopy(this, 0, shorter, 0, "); //$NON-NLS-1$ snippet.append(maxEntries); snippet.append("); "); //$NON-NLS-1$ snippet.append("return java.util.Arrays.asList(shorter).toString();"); //$NON-NLS-1$ return snippet.toString(); } } return "java.util.Arrays.asList(this).toString()"; //$NON-NLS-1$ } return null; } /** * @see org.eclipse.jface.util.IPropertyChangeListener#propertyChange(PropertyChangeEvent) */ @Override public void propertyChange(PropertyChangeEvent event) { String property = event.getProperty(); if (property.equals(IJDIPreferencesConstants.PREF_DETAIL_FORMATTERS_LIST) || property.equals(IJDIPreferencesConstants.PREF_SHOW_DETAILS) || property.equals(IDebugUIConstants.PREF_MAX_DETAIL_LENGTH)) { populateDetailFormattersMap(); fCacheMap.clear(); // If a Java stack frame is selected in the Debug view, fire a change event on // it so the variables view will update for any formatter changes. IAdaptable selected = DebugUITools.getDebugContext(); if (selected != null) { IJavaStackFrame frame= selected.getAdapter(IJavaStackFrame.class); if (frame != null) { DebugPlugin.getDefault().fireDebugEventSet(new DebugEvent[] { new DebugEvent(frame, DebugEvent.CHANGE) }); } } } } /** * @see org.eclipse.debug.core.IDebugEventSetListener#handleDebugEvents(DebugEvent[]) */ @Override public void handleDebugEvents(DebugEvent[] events) { for (int i = 0; i < events.length; i++) { DebugEvent event = events[i]; if (event.getSource() instanceof IJavaDebugTarget && event.getKind() == DebugEvent.TERMINATE) { deleteCacheForTarget((IJavaDebugTarget) event.getSource()); } } } /** * @see org.eclipse.debug.core.ILaunchesListener#launchesAdded(ILaunch[]) */ @Override public void launchesAdded(ILaunch[] launches) { } /** * @see org.eclipse.debug.core.ILaunchesListener#launchesChanged(ILaunch[]) */ @Override public void launchesChanged(ILaunch[] launches) { } /** * @see org.eclipse.debug.core.ILaunchesListener#launchesRemoved(ILaunch[]) */ @Override public void launchesRemoved(ILaunch[] launches) { for (int i = 0; i < launches.length; i++) { ILaunch launch = launches[i]; IDebugTarget[] debugTargets= launch.getDebugTargets(); for (int j = 0; j < debugTargets.length; j++) { if (debugTargets[j] instanceof IJavaDebugTarget) { deleteCacheForTarget((IJavaDebugTarget)debugTargets[j]); } } } } /** * Remove from the cache compiled expression associated with * the given debug target. * * @param debugTarget the target */ private synchronized void deleteCacheForTarget(IJavaDebugTarget debugTarget) { for (Iterator<Key> iter= fCacheMap.keySet().iterator(); iter.hasNext();) { Key key= iter.next(); if ((key).fDebugTarget == debugTarget) { iter.remove(); } } } /** * Object used as the key in the cache map for associate a compiled * expression with a pair type name/debug target */ static private class Key { private String fTypeName; private IJavaDebugTarget fDebugTarget; Key(String typeName, IJavaDebugTarget debugTarget) { fTypeName= typeName; fDebugTarget= debugTarget; } @Override public boolean equals(Object obj) { if (obj instanceof Key) { Key key= (Key) obj; return fTypeName != null && fDebugTarget != null && fTypeName.equals(key.fTypeName) && fDebugTarget.equals(key.fDebugTarget); } return false; } @Override public int hashCode() { return fTypeName.hashCode() / 2 + fDebugTarget.hashCode() / 2; } } /** * Stores a compiled expression and evaluation engine used to evaluate the expression. */ static private class Expression { private ICompiledExpression fExpression; private IAstEvaluationEngine fEngine; Expression(ICompiledExpression expression, IAstEvaluationEngine engine) { fExpression = expression; fEngine = engine; } public ICompiledExpression getExpression() { return fExpression; } public IAstEvaluationEngine getEngine() { return fEngine; } } /** * Listener use to manage the result of the formatter. * Utilizes the 'standard' pretty printer methods to return the result. */ static private class EvaluationListener implements IEvaluationListener { /** * The selector of <code>java.lang.Object#toString()</code>, * used to evaluate 'toString()' for displaying details of values. */ private static final String fgToString= "toString"; //$NON-NLS-1$ /** * The signature of <code>java.lang.Object#toString()</code>, * used to evaluate 'toString()' for displaying details of values. */ private static final String fgToStringSignature= "()Ljava/lang/String;"; //$NON-NLS-1$ /** * Signature of a string object */ private static final String STRING_SIGNATURE = "Ljava/lang/String;"; //$NON-NLS-1$ private IJavaValue fValue; private IValueDetailListener fListener; private IJavaThread fThread; public EvaluationListener(IJavaValue value, IJavaThread thread, IValueDetailListener listener) { fValue= value; fThread= thread; fListener= listener; } @Override public void evaluationComplete(IEvaluationResult result) { if (result.hasErrors()) { StringBuffer error= new StringBuffer(DebugUIMessages.JavaDetailFormattersManager_Detail_formatter_error___1); DebugException exception= result.getException(); if (exception != null) { Throwable throwable= exception.getStatus().getException(); error.append("\n\t\t"); //$NON-NLS-1$ if (throwable instanceof InvocationException) { error.append(NLS.bind(DebugUIMessages.JavaDetailFormattersManager_An_exception_occurred___0__3, new String[] {((InvocationException) throwable).exception().referenceType().name()})); } else if (throwable instanceof UnsupportedOperationException) { error = new StringBuffer(); error.append(DebugUIMessages.JavaDetailFormattersManager_7); } else { error.append(exception.getStatus().getMessage()); } } else { String[] errors= result.getErrorMessages(); for (int i= 0, length= errors.length; i < length; i++) { error.append("\n\t\t").append(errors[i]); //$NON-NLS-1$ } } fListener.detailComputed(fValue, error.toString()); } else { try { valueToString(result.getValue()); } catch (DebugException e) { fListener.detailComputed(fValue, e.getStatus().getMessage()); } } } public void valueToString(final IJavaValue objectValue) throws DebugException { String nonEvalResult = null; StringBuffer result= null; if (objectValue.getSignature() == null) { // no need to spawn evaluate for a null fValue nonEvalResult = DebugUIMessages.JavaDetailFormattersManager_null; } else if (objectValue instanceof IJavaPrimitiveValue) { // no need to spawn evaluate for a primitive value result = new StringBuffer(); appendJDIPrimitiveValueString(result, objectValue); } else if (fThread == null || !fThread.isSuspended()) { // no thread available result = new StringBuffer(); result.append(DebugUIMessages.JavaDetailFormattersManager_no_suspended_threads); appendJDIValueString(result, objectValue); } else if (objectValue instanceof IJavaObject && STRING_SIGNATURE.equals(objectValue.getSignature())) { // no need to spawn evaluate for a java.lang.String result = new StringBuffer(); appendJDIValueString(result, objectValue); } if (result != null) { nonEvalResult = result.toString(); } if (nonEvalResult != null) { fListener.detailComputed(fValue, nonEvalResult); return; } IEvaluationRunnable eval = new IEvaluationRunnable() { @Override public void run(IJavaThread thread, IProgressMonitor monitor) throws DebugException { StringBuffer buf= new StringBuffer(); if (objectValue instanceof IJavaArray) { appendArrayDetail(buf, (IJavaArray) objectValue); } else if (objectValue instanceof IJavaObject) { appendObjectDetail(buf, (IJavaObject) objectValue); } else { appendJDIValueString(buf, objectValue); } fListener.detailComputed(fValue, buf.toString()); } }; fThread.runEvaluation(eval, null, DebugEvent.EVALUATION_IMPLICIT, false); } /* * Gets all values in array and appends the toString() if it is an array of Objects or the value if primitive. * NB - this method is only called if there is no compiled expression for an array to perform an * Arrays.asList().toString() to minimize toString() calls on remote target (i.e. one call to * List.toString() instead of one call per item in the array). */ protected void appendArrayDetail(StringBuffer result, IJavaArray arrayValue) throws DebugException { result.append('['); boolean partial = false; IJavaValue[] arrayValues = null; int maxLength = getMaxDetailLength(); int maxEntries = (maxLength / 3) + 1; // guess at char/comma/space per entry int length = -1; try { length = arrayValue.getLength(); if (maxLength > 0 && length > maxEntries) { partial = true; IVariable[] variables = arrayValue.getVariables(0, maxEntries); arrayValues = new IJavaValue[variables.length]; for (int i = 0; i < variables.length; i++) { arrayValues[i] = (IJavaValue) variables[i].getValue(); } } else { arrayValues= arrayValue.getValues(); } } catch (DebugException de) { JDIDebugUIPlugin.log(de); result.append(de.getStatus().getMessage()); return; } for (int i= 0; i < arrayValues.length; i++) { IJavaValue value= arrayValues[i]; if (value instanceof IJavaArray) { appendArrayDetail(result, (IJavaArray) value); } else if (value instanceof IJavaObject) { appendObjectDetail(result, (IJavaObject) value); } else { appendJDIValueString(result, value); } if (i < arrayValues.length - 1) { result.append(','); result.append(' '); } if (partial && result.length() > maxLength) { break; } } if (!partial) { result.append(']'); } } protected void appendJDIPrimitiveValueString(StringBuffer result, IJavaValue value) throws DebugException { result.append(value.getValueString()); } protected void appendJDIValueString(StringBuffer result, IJavaValue value) throws DebugException { result.append(value.getValueString()); } protected void appendObjectDetail(StringBuffer result, IJavaObject objectValue) throws DebugException { if(objectValue instanceof JDINullValue) { appendJDIValueString(result, objectValue); return; } // optimize if the result is a string - no need to send toString to a string if (STRING_SIGNATURE.equals(objectValue.getSignature())) { appendJDIValueString(result, objectValue); } else { IJavaValue toStringValue= objectValue.sendMessage(EvaluationListener.fgToString, EvaluationListener.fgToStringSignature, null, fThread, false); if (toStringValue == null) { result.append(DebugUIMessages.JavaDetailFormattersManager__unknown_); } else { appendJDIValueString(result, toStringValue); } } } } /** * (non java-doc) * Remove the provided <code>detailFormatter</code> from the map * @param detailFormatter the detail formatter */ public void removeAssociatedDetailFormatter(DetailFormatter detailFormatter) { fDetailFormattersMap.remove(detailFormatter.getTypeName()); savePreference(); } /** * Returns the maximum number of chars to display in the details area or 0 if * there is no maximum. * * @return maximum number of chars to display or 0 for no max */ private static int getMaxDetailLength() { return DebugUITools.getPreferenceStore().getInt(IDebugUIConstants.PREF_MAX_DETAIL_LENGTH); } }