Bug 577564 - add warning dialog before modifying final fields

Added preference and dialog to warn user before modifying final fields
values in debugger either by a context menu or via the embeded value
editor. The new preference is enabled by default.

To disable in a product, following value can be used:
org.eclipse.jdt.debug.ui/org.eclipse.jdt.debug.ui.prompt_before_modifying_final_fields=false

Change-Id: I52a88eb3f1239bf04472f3f5f651be90c2e583b9
Signed-off-by: Andrey Loskutov <loskutov@gmx.de>
Reviewed-on: https://git.eclipse.org/r/c/jdt/eclipse.jdt.debug/+/188471
Tested-by: JDT Bot <jdt-bot@eclipse.org>
diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/DebugUIMessages.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/DebugUIMessages.java
index 13966b0..c4392c0 100644
--- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/DebugUIMessages.java
+++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/DebugUIMessages.java
@@ -84,6 +84,7 @@
 	public static String JavaDebugPreferencePage_19;
 	public static String JavaDebugPreferencePage_0;
 	public static String JavaDebugPreferencePage_20;
+	public static String JavaDebugPreferencePage_28;
 
 	public static String JavaDebugPreferencePage_advancedSourcelookup;
 	public static String JavaDebugPreferencePage_listenToThreadNameChanges;
@@ -257,6 +258,10 @@
 	public static String NoLineNumberAttributesStatusHandler_Java_Breakpoint_1;
 	public static String NoLineNumberAttributesStatusHandler_2;
 
+	public static String JavaVariableValueEditor_prompt_before_final_value_change_title;
+	public static String JavaVariableValueEditor_prompt_before_final_value_change_message;
+	public static String JavaVariableValueEditor_prompt_before_final_value_change_toggle_message;
+
 	public static String JavaDetailFormattersPreferencePage_Add__Formatter____5;
 	public static String JavaDetailFormattersPreferencePage_Allow_you_to_create_a_new_detail_formatter_6;
 	public static String JavaDetailFormattersPreferencePage__Remove_7;
diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/DebugUIMessages.properties b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/DebugUIMessages.properties
index 997b505..e173e63 100644
--- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/DebugUIMessages.properties
+++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/DebugUIMessages.properties
@@ -205,6 +205,11 @@
 NoLineNumberAttributesStatusHandler_Java_Breakpoint_1=Java Breakpoint
 NoLineNumberAttributesStatusHandler_2=Unable to install breakpoint in {0} due to missing line number attributes. Modify compiler options to generate line number attributes.
 
+JavaVariableValueEditor_prompt_before_final_value_change_title=Change final value?
+JavaVariableValueEditor_prompt_before_final_value_change_message=Changing final values could break the application you are currently debugging.\
+\n\nContinue with change?
+JavaVariableValueEditor_prompt_before_final_value_change_toggle_message=Don't show this dialog again
+
 JavaDetailFormattersPreferencePage_Add__Formatter____5=Ad&d...
 JavaDetailFormattersPreferencePage_Allow_you_to_create_a_new_detail_formatter_6=Allow you to create a new detail formatter
 JavaDetailFormattersPreferencePage__Remove_7=&Remove
@@ -324,6 +329,7 @@
 JavaDebugPreferencePage_25=Access & Modification
 JavaDebugPreferencePage_26=Access
 JavaDebugPreferencePage_27=Modification
+JavaDebugPreferencePage_28=&Warn before modifying final fields
 JavaDebugPreferencePage_SuspendOnRecurrencePolicy=Suspend policy for rec&urring exception instances:
 JavaDebugPreferencePage_SuspendOnRecurrencePolicy_Always=Always
 JavaDebugPreferencePage_SuspendOnRecurrencePolicy_Unconfigured=Unconfigured
diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/IJDIPreferencesConstants.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/IJDIPreferencesConstants.java
index 3b525ff..6f0af1f 100644
--- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/IJDIPreferencesConstants.java
+++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/IJDIPreferencesConstants.java
@@ -213,6 +213,8 @@
 	 */
 	public static final String PREF_ALERT_UNABLE_TO_INSTALL_BREAKPOINT = IJavaDebugUIConstants.PLUGIN_ID + ".prompt_unable_to_install_breakpoint"; //$NON-NLS-1$
 
+	public static final String PREF_PROMPT_BEFORE_MODIFYING_FINAL_FIELDS = IJavaDebugUIConstants.PLUGIN_ID + ".prompt_before_modifying_final_fields"; //$NON-NLS-1$
+
 	public static final String PREF_THREAD_MONITOR_IN_DEADLOCK_COLOR= "org.eclipse.jdt.debug.ui.InDeadlockColor"; //$NON-NLS-1$
 
 	public static final String PREF_LABELED_OBJECT_COLOR = IJavaDebugUIConstants.PLUGIN_ID + ".LabeledObject"; //$NON-NLS-1$
diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JDIDebugUIPreferenceInitializer.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JDIDebugUIPreferenceInitializer.java
index 7f24377..b5963e4 100644
--- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JDIDebugUIPreferenceInitializer.java
+++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JDIDebugUIPreferenceInitializer.java
@@ -36,6 +36,7 @@
 		store.setDefault(IJDIPreferencesConstants.PREF_ALERT_HCR_NOT_SUPPORTED, true);
 		store.setDefault(IJDIPreferencesConstants.PREF_ALERT_OBSOLETE_METHODS, true);
 		store.setDefault(IJDIPreferencesConstants.PREF_ALERT_UNABLE_TO_INSTALL_BREAKPOINT, true);
+		store.setDefault(IJDIPreferencesConstants.PREF_PROMPT_BEFORE_MODIFYING_FINAL_FIELDS, true);
 		store.setDefault(IJDIPreferencesConstants.PREF_PROMPT_DELETE_CONDITIONAL_BREAKPOINT, true);
 
 		store.setDefault(IJDIPreferencesConstants.PREF_SHOW_QUALIFIED_NAMES, false);
diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JavaDebugPreferencePage.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JavaDebugPreferencePage.java
index a6e14ce..18f9a25 100644
--- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JavaDebugPreferencePage.java
+++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JavaDebugPreferencePage.java
@@ -84,6 +84,7 @@
 	private Button fSuspendDuringEvaluations;
 	private Button fOpenInspector;
 	private Button fPromptUnableToInstallBreakpoint;
+	private Button fPromptBeforeModifyingFinalFields;
 	private Button fPromptDeleteConditionalBreakpoint;
 	private Button fFilterUnrelatedBreakpoints;
 	private Button fOnlyIncludeExportedEntries;
@@ -181,6 +182,7 @@
 		fConnectionTimeoutText.load();
 
 		SWTFactory.createVerticalSpacer(composite, 1);
+		fPromptBeforeModifyingFinalFields = SWTFactory.createCheckButton(composite, DebugUIMessages.JavaDebugPreferencePage_28, null, false, 1);
 		fPromptUnableToInstallBreakpoint = SWTFactory.createCheckButton(composite, DebugUIMessages.JavaDebugPreferencePage_19, null, false, 1);
 		fPromptDeleteConditionalBreakpoint= SWTFactory.createCheckButton(composite, DebugUIMessages.JavaDebugPreferencePage_promptWhenDeletingCondidtionalBreakpoint, null, false, 1);
 		fFilterUnrelatedBreakpoints = SWTFactory.createCheckButton(composite, DebugUIMessages.JavaDebugPreferencePage_filterUnrelatedBreakpoints, null, false, 1);
@@ -225,6 +227,7 @@
 		store.setValue(IJDIPreferencesConstants.PREF_ALERT_OBSOLETE_METHODS, fAlertObsoleteButton.getSelection());
 		store.setValue(IJDIPreferencesConstants.PREF_SUSPEND_ON_UNCAUGHT_EXCEPTIONS, fSuspendButton.getSelection());
 		store.setValue(IJDIPreferencesConstants.PREF_SUSPEND_ON_COMPILATION_ERRORS, fSuspendOnCompilationErrors.getSelection());
+		store.setValue(IJDIPreferencesConstants.PREF_PROMPT_BEFORE_MODIFYING_FINAL_FIELDS, fPromptBeforeModifyingFinalFields.getSelection());
 		store.setValue(IJDIPreferencesConstants.PREF_ALERT_UNABLE_TO_INSTALL_BREAKPOINT, fPromptUnableToInstallBreakpoint.getSelection());
 		store.setValue(IJDIPreferencesConstants.PREF_PROMPT_DELETE_CONDITIONAL_BREAKPOINT, fPromptDeleteConditionalBreakpoint.getSelection());
 		store.setValue(IJDIPreferencesConstants.PREF_OPEN_INSPECT_POPUP_ON_EXCEPTION, fOpenInspector.getSelection());
@@ -277,6 +280,7 @@
 		fAlertHCRButton.setSelection(store.getDefaultBoolean(IJDIPreferencesConstants.PREF_ALERT_HCR_FAILED));
 		fAlertHCRNotSupportedButton.setSelection(store.getDefaultBoolean(IJDIPreferencesConstants.PREF_ALERT_HCR_NOT_SUPPORTED));
 		fAlertObsoleteButton.setSelection(store.getDefaultBoolean(IJDIPreferencesConstants.PREF_ALERT_OBSOLETE_METHODS));
+		fPromptBeforeModifyingFinalFields.setSelection(store.getDefaultBoolean(IJDIPreferencesConstants.PREF_PROMPT_BEFORE_MODIFYING_FINAL_FIELDS));
 		fPromptUnableToInstallBreakpoint.setSelection(store.getDefaultBoolean(IJDIPreferencesConstants.PREF_ALERT_UNABLE_TO_INSTALL_BREAKPOINT));
 		fPromptDeleteConditionalBreakpoint.setSelection(store.getDefaultBoolean(IJDIPreferencesConstants.PREF_PROMPT_DELETE_CONDITIONAL_BREAKPOINT));
 		fOpenInspector.setSelection(store.getDefaultBoolean(IJDIPreferencesConstants.PREF_OPEN_INSPECT_POPUP_ON_EXCEPTION));
@@ -317,6 +321,7 @@
 		fAlertHCRButton.setSelection(store.getBoolean(IJDIPreferencesConstants.PREF_ALERT_HCR_FAILED));
 		fAlertHCRNotSupportedButton.setSelection(store.getBoolean(IJDIPreferencesConstants.PREF_ALERT_HCR_NOT_SUPPORTED));
 		fAlertObsoleteButton.setSelection(store.getBoolean(IJDIPreferencesConstants.PREF_ALERT_OBSOLETE_METHODS));
+		fPromptBeforeModifyingFinalFields.setSelection(store.getBoolean(IJDIPreferencesConstants.PREF_PROMPT_BEFORE_MODIFYING_FINAL_FIELDS));
 		fPromptUnableToInstallBreakpoint.setSelection(store.getBoolean(IJDIPreferencesConstants.PREF_ALERT_UNABLE_TO_INSTALL_BREAKPOINT));
 		fPromptDeleteConditionalBreakpoint.setSelection(store.getBoolean(IJDIPreferencesConstants.PREF_PROMPT_DELETE_CONDITIONAL_BREAKPOINT));
 		fOpenInspector.setSelection(store.getBoolean(IJDIPreferencesConstants.PREF_OPEN_INSPECT_POPUP_ON_EXCEPTION));
diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/actions/JavaVariableValueEditor.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/actions/JavaVariableValueEditor.java
index 612ce57..c64958a 100644
--- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/actions/JavaVariableValueEditor.java
+++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/actions/JavaVariableValueEditor.java
@@ -17,9 +17,15 @@
 import org.eclipse.debug.core.model.IVariable;
 import org.eclipse.debug.ui.actions.IVariableValueEditor;
 import org.eclipse.jdt.core.Signature;
+import org.eclipse.jdt.debug.core.IJavaModifiers;
 import org.eclipse.jdt.debug.core.IJavaVariable;
+import org.eclipse.jdt.internal.debug.ui.DebugUIMessages;
+import org.eclipse.jdt.internal.debug.ui.IJDIPreferencesConstants;
 import org.eclipse.jdt.internal.debug.ui.JDIDebugUIPlugin;
 import org.eclipse.jdt.internal.debug.ui.JDIModelPresentation;
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.dialogs.MessageDialogWithToggle;
+import org.eclipse.jface.preference.IPreferenceStore;
 import org.eclipse.swt.widgets.Shell;
 
 /**
@@ -27,9 +33,6 @@
  */
 public class JavaVariableValueEditor implements IVariableValueEditor {
 
-    /* (non-Javadoc)
-     * @see org.eclipse.debug.ui.actions.IVariableValueEditor#editVariable(org.eclipse.debug.core.model.IVariable, org.eclipse.swt.widgets.Shell)
-     */
     @Override
 	public boolean editVariable(IVariable variable, Shell shell) {
         String signature= null;
@@ -41,6 +44,10 @@
 	    if (signature == null) {
 	        return false;
 	    }
+		if (!isAllowedToModifyValue(variable)) {
+			// return true to avoid further processing
+			return true;
+		}
 	    IVariableValueEditor editor;
         if (JDIModelPresentation.isObjectValue(signature)) {
             editor= new JavaObjectValueEditor();
@@ -51,11 +58,13 @@
         return editor.editVariable(variable, shell);
     }
 
-    /* (non-Javadoc)
-     * @see org.eclipse.debug.ui.actions.IVariableValueEditor#saveVariable(org.eclipse.debug.core.model.IVariable, java.lang.String, org.eclipse.swt.widgets.Shell)
-     */
     @Override
 	public boolean saveVariable(IVariable variable, String expression, Shell shell) {
+		if (!isAllowedToModifyValue(variable)) {
+			// return true to avoid further processing
+			return true;
+		}
+
         // set the value of chars directly if expression is a single character (not an expression to evaluate)
     	if (expression.length() == 1 && variable instanceof IJavaVariable){
     		IJavaVariable javaVariable = (IJavaVariable)variable;
@@ -74,6 +83,50 @@
         return editor.saveVariable(variable, expression, shell);
     }
 
+	/**
+	 * @return {@code false} to prohibit editing a variable
+	 */
+	protected boolean isAllowedToModifyValue(IVariable variable) {
+		if (variable instanceof IJavaModifiers) {
+			IJavaModifiers modifiers = (IJavaModifiers) variable;
+			boolean allowed = isAllowedToModifyFinalValue(modifiers);
+			if (!allowed) {
+				// prohibit editing a variable that is declared as final
+				return false;
+			}
+		}
+		return true;
+	}
+
+	protected boolean isAllowedToModifyFinalValue(IJavaModifiers variable) {
+		IPreferenceStore preferenceStore = JDIDebugUIPlugin.getDefault().getPreferenceStore();
+		String key = IJDIPreferencesConstants.PREF_PROMPT_BEFORE_MODIFYING_FINAL_FIELDS;
+		if (!preferenceStore.getBoolean(key)) {
+			return true;
+		}
+		try {
+			if (!variable.isFinal()) {
+				return true;
+			}
+		} catch (DebugException e) {
+			JDIDebugUIPlugin.log(e);
+		}
+		return promptIfAllowedToModifyFinalValue(preferenceStore, key);
+	}
+
+	protected boolean promptIfAllowedToModifyFinalValue(IPreferenceStore preferenceStore, String key) {
+		boolean dontShowAgain = false;
+		final MessageDialogWithToggle dialog = MessageDialogWithToggle.openYesNoQuestion(
+				JDIDebugUIPlugin.getActiveWorkbenchShell(),
+				DebugUIMessages.JavaVariableValueEditor_prompt_before_final_value_change_title,
+				DebugUIMessages.JavaVariableValueEditor_prompt_before_final_value_change_message,
+				DebugUIMessages.JavaVariableValueEditor_prompt_before_final_value_change_toggle_message,
+				dontShowAgain,
+				preferenceStore,
+				key);
+		return dialog.getReturnCode() == IDialogConstants.YES_ID;
+	}
+
     public static String getSignature(IVariable variable) throws DebugException {
         String signature= null;
 		IJavaVariable javaVariable = variable.getAdapter(IJavaVariable.class);