org.eclipse.jpt.common.ui.internal.prefs.JptProblemSeveritiesPage.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.jpt.common.ui.internal.prefs.JptProblemSeveritiesPage.java

Source

/*******************************************************************************
 * Copyright (c) 2009, 2013 Oracle. 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:
 *     Oracle - initial API and implementation
 ******************************************************************************/
package org.eclipse.jpt.common.ui.internal.prefs;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.internal.ui.preferences.PropertyAndPreferencePage;
import org.eclipse.jdt.internal.ui.preferences.ScrolledPageContent;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.layout.PixelConverter;
import org.eclipse.jface.operation.IRunnableContext;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jpt.common.core.internal.utility.WorkspaceRunnableAdapter;
import org.eclipse.jpt.common.core.utility.ValidationMessage;
import org.eclipse.jpt.common.ui.JptCommonUiMessages;
import org.eclipse.jpt.common.ui.internal.JptUIPlugin;
import org.eclipse.jpt.common.ui.internal.jface.RunnableWithProgressAdapter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.ui.forms.events.ExpansionAdapter;
import org.eclipse.ui.forms.events.ExpansionEvent;
import org.eclipse.ui.forms.widgets.ExpandableComposite;
import org.eclipse.wst.validation.internal.provisional.core.IMessage;

/**
 * Abstract problem severities page that supports
 * workspace- and project-level severities.
 */
@SuppressWarnings("restriction")
public abstract class JptProblemSeveritiesPage extends PropertyAndPreferencePage {
    /**
     * Changed severities are stored in this map and either committed
     * (e.g. when the user presses the 'OK' button) or discarded
     * (e.g. when the user presses the 'Cancel' button).<ul>
     * <li> key = preference key (which is the
     * {@link ValidationMessage#getID() validation message ID})
     * <li> value = preference severity level (which is the
     *   {@link IMessage#getSeverity() validation message severity}):<ul>
     *   <li>{@link IMessage#HIGH_SEVERITY}
     *   <li>{@link IMessage#NORMAL_SEVERITY}
     *   <li>{@link IMessage#LOW_SEVERITY}
     *   <li>{@link ValidationMessage#IGNORE_SEVERITY}
     *   </ul>
     * </ul>
     */
    private final HashMap<String, Integer> changedSeverities = new HashMap<String, Integer>();

    /**
     * Cache the {@link Combo}s so we can revert the settings.
     */
    private final ArrayList<Combo> combos = new ArrayList<Combo>();

    /**
     * Cache the {@link ExpandableComposite}s so we can save
     * and restore the page's expansion state.
     */
    private final ArrayList<ExpandableComposite> expandablePanes = new ArrayList<ExpandableComposite>();

    /**
     * The key used to store and retrieve a combo's validation message.
     * @see org.eclipse.swt.widgets.Widget#getData(String)
     */
    /* CU private */ static final String VALIDATION_MESSAGE = ValidationMessage.class.getName();

    /**
     * The scrollable pane used to show the content of this page.
     */
    private ScrolledPageContent mainComposite;

    /**
     * The possible choices which describes the severity of a single problem.
     */
    private final PreferenceSeverity[] preferenceSeverities;
    private final String[] severityDisplayStrings;

    private Boolean hasProjectSpecificPreferences = null;

    /**
     * Constant used to store the expansion state of each expandable pane.
     */
    public static final String SETTINGS_EXPANDED = "expanded"; //$NON-NLS-1$

    /**
     * The preference key used to retrieve the dialog settings where the expansion
     * states have been stored.
     */
    public static final String SETTINGS_SECTION_NAME = "JpaProblemSeveritiesPage"; //$NON-NLS-1$

    public JptProblemSeveritiesPage() {
        super();
        this.preferenceSeverities = this.buildPreferenceSeverities();
        this.severityDisplayStrings = this.buildSeverityDisplayStrings();
    }

    @Override
    protected IPreferenceStore doGetPreferenceStore() {
        return this.getUIPlugin().getPreferenceStore();
    }

    protected abstract JptUIPlugin getUIPlugin();

    protected PreferenceSeverity[] buildPreferenceSeverities() {
        return DEFAULT_PREFERENCE_SEVERITIES;
    }

    protected static final PreferenceSeverity[] DEFAULT_PREFERENCE_SEVERITIES = buildDefaultPreferenceSeverities();

    protected static PreferenceSeverity[] buildDefaultPreferenceSeverities() {
        ArrayList<PreferenceSeverity> severities = new ArrayList<PreferenceSeverity>();
        severities.add(
                new PreferenceSeverity(IMessage.HIGH_SEVERITY, JptCommonUiMessages.PROBLEM_SEVERITIES_PAGE__ERROR));
        severities.add(new PreferenceSeverity(IMessage.NORMAL_SEVERITY,
                JptCommonUiMessages.PROBLEM_SEVERITIES_PAGE__WARNING));
        severities.add(
                new PreferenceSeverity(IMessage.LOW_SEVERITY, JptCommonUiMessages.PROBLEM_SEVERITIES_PAGE__INFO));
        severities.add(new PreferenceSeverity(ValidationMessage.IGNORE_SEVERITY,
                JptCommonUiMessages.PROBLEM_SEVERITIES_PAGE__IGNORE));
        return severities.toArray(new PreferenceSeverity[severities.size()]);
    }

    /**
     * Pair a preference value with its localized display string
     * (e.g. <code>"error"</code> and <code>"Error"</code>).
     */
    public static class PreferenceSeverity {
        public final int preferenceValue;
        public final String displayString;

        public PreferenceSeverity(int preferenceValue, String displayString) {
            super();
            this.preferenceValue = preferenceValue;
            this.displayString = displayString;
        }
    }

    /**
     * Pre-condition: {@link #preferenceSeverities} is already built.
     */
    protected String[] buildSeverityDisplayStrings() {
        int len = this.preferenceSeverities.length;
        String[] displayStrings = new String[len];
        for (int i = 0; i < len; i++) {
            displayStrings[i] = this.preferenceSeverities[i].displayString;
        }
        return displayStrings;
    }

    @Override
    protected Control createPreferenceContent(Composite parent) {
        PixelConverter pixelConverter = new PixelConverter(parent);
        // create a container because the caller will set the GridData and we need
        // to change the heightHint of the first child and we also need to set the
        // font otherwise the layout won't be calculated correctly
        Composite container = new Composite(parent, SWT.NONE);
        container.setFont(parent.getFont());
        GridLayout layout = new GridLayout(1, false);
        layout.marginHeight = 0;
        layout.marginWidth = 0;
        container.setLayout(layout);

        // the page's main composite
        this.mainComposite = new ScrolledPageContent(container);

        GridData gridData = new GridData(GridData.FILL, GridData.FILL, true, true);
        gridData.heightHint = pixelConverter.convertHeightInCharsToPixels(20);
        this.mainComposite.setLayoutData(gridData);

        parent = this.mainComposite.getBody();
        parent.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, true));
        layout = new GridLayout(1, false);
        layout.marginHeight = 0;
        layout.marginWidth = 0;
        parent.setLayout(layout);

        this.addCombos(parent);

        this.restoreSectionExpansionStates();

        return container;
    }

    protected abstract void addCombos(Composite parent);

    protected void restoreSectionExpansionStates() {
        IDialogSettings settings = this.getDialogPreferences();
        for (int index = this.expandablePanes.size(); index-- > 0;) {
            ExpandableComposite expandablePane = this.expandablePanes.get(index);
            if (settings == null) {
                expandablePane.setExpanded(index == 0); // only expand the first node by default
            } else {
                expandablePane.setExpanded(settings.getBoolean(SETTINGS_EXPANDED + index));
            }
        }
    }

    @Override
    public Point computeSize() {
        return this.doComputeSize();
    }

    protected Composite addExpandableSection(Composite parent, String text) {
        return this.addExpandableSection(parent, text, new GridData(GridData.FILL, GridData.FILL, true, false));
    }

    protected Composite addSubExpandableSection(Composite parent, String text) {
        return this.addExpandableSection(parent, text,
                new GridData(GridData.FILL, GridData.FILL, true, false, 2, 1));
    }

    /**
     * Creates and adds to the given <code>Composite</code> an expandable pane
     * where its content can be shown or hidden depending on the expansion state.
     *
     * @param parent The parent container
     * @param text The title of the expandable section
     * @return The container to which widgets can be added, which is a child of
     * the expandable pane
     */
    private Composite addExpandableSection(Composite parent, String text, GridData gridData) {
        int expansionStype = ExpandableComposite.TWISTIE | ExpandableComposite.CLIENT_INDENT;
        ExpandableComposite expandablePane = new ExpandableComposite(parent, SWT.NONE, expansionStype);

        expandablePane.setText(text);
        expandablePane.setFont(JFaceResources.getFontRegistry().getBold(JFaceResources.DIALOG_FONT));
        expandablePane.setLayoutData(gridData);
        expandablePane.addExpansionListener(this.buildExpansionListener());

        this.mainComposite.adaptChild(expandablePane);
        this.expandablePanes.add(expandablePane);

        parent = new Composite(expandablePane, SWT.NONE);
        parent.setLayout(new GridLayout(2, false));
        expandablePane.setClient(parent);

        return parent;
    }

    private ExpansionAdapter buildExpansionListener() {
        return new ExpansionListener();
    }

    /* CU private */ class ExpansionListener extends ExpansionAdapter {
        @Override
        public void expansionStateChanged(ExpansionEvent e) {
            JptProblemSeveritiesPage.this.expansionStateChanged();
        }
    }

    /**
     * Revalidates the layout in order to show or hide the vertical scroll bar
     * after a section was either expanded or collapsed. This unfortunately does
     * not happen automatically.
     */
    protected void expansionStateChanged() {
        this.mainComposite.reflow(true);
    }

    /**
     * Creates and adds to the given parent a labeled combo where the possible
     * choices are "Ignore", "Error" and "Warning".
     *
     * @param parent The parent to which the widgets are added
     * @param validationMessage The corresponding validation message
     */
    protected void addLabeledCombo(Composite parent, ValidationMessage validationMessage) {
        Label label = new Label(parent, SWT.NONE);
        label.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        label.setText(validationMessage.getDescription() + ':');

        Combo combo = new Combo(parent, SWT.READ_ONLY);
        combo.setItems(this.severityDisplayStrings);
        combo.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL));
        combo.setData(VALIDATION_MESSAGE, validationMessage);
        combo.select(this.getInitialComboIndex(validationMessage));
        combo.addSelectionListener(this.buildComboSelectionListener());

        this.mainComposite.adaptChild(combo);
        this.combos.add(combo);
    }

    /**
     * Return the combo index corresponding to the specified validation message's
     * current preference value.
     * <p>
     * Only called during initialization.
     */
    protected int getInitialComboIndex(ValidationMessage validationMessage) {
        return this.convertPreferenceValueToComboIndex(this.getPreferenceValue(validationMessage));
    }

    /**
     * Return the current preference value for the specified validation message.
     * <p>
     * Only called during initialization.
     */
    protected int getPreferenceValue(ValidationMessage validationMessage) {
        int prefValue = this.getPreferenceValue_(validationMessage);
        return (prefValue != ValidationMessage.UNSET_SEVERITY_PREFERENCE) ? prefValue
                : validationMessage.getDefaultSeverity();
    }

    protected int getPreferenceValue_(ValidationMessage validationMessage) {
        String prefKey = validationMessage.getID();
        // useProjectSettings() won't work here since the page is still being initialized
        return (this.isProjectPreferencePage() && this.hasProjectSpecificOptions(this.getProject()))
                ? this.getValidationMessageSeverityPreference(this.getProject(), prefKey)
                : this.getValidationMessageSeverityPreference(prefKey);
    }

    protected int convertPreferenceValueToComboIndex(int prefValue) {
        for (int i = 0; i < this.preferenceSeverities.length; i++) {
            if (prefValue == this.preferenceSeverities[i].preferenceValue) {
                return i;
            }
        }
        throw new IllegalArgumentException("unknown preference value: " + prefValue); //$NON-NLS-1$
    }

    private SelectionListener buildComboSelectionListener() {
        return new ComboSelectionListener();
    }

    /* CU private */ class ComboSelectionListener extends SelectionAdapter {
        @Override
        public void widgetSelected(SelectionEvent e) {
            JptProblemSeveritiesPage.this.comboSelected((Combo) e.widget);
        }
    }

    protected void comboSelected(Combo combo) {
        ValidationMessage msg = (ValidationMessage) combo.getData(VALIDATION_MESSAGE);
        String prefKey = msg.getID();
        int prefValue = this.preferenceSeverities[combo.getSelectionIndex()].preferenceValue;
        this.changedSeverities.put(prefKey, Integer.valueOf(prefValue));
    }

    @Override
    protected boolean hasProjectSpecificOptions(IProject project) {
        return this.getWorkspaceValidationPreferencesOverridden(project);
    }

    /**
     * For a project properties page, the superclass implementation calls
     * {@link #enableProjectSpecificSettings(boolean)}, with an argument of
     * <code>false</code>; so we need handle only the workspace preferences
     * page case here.
     */
    @Override
    protected void performDefaults() {
        if (!this.isProjectPreferencePage()) {
            this.revertWorkspaceToDefaultPreferences();
        }
        super.performDefaults();
    }

    /**
     * This is called only when the page is a workspace preferences page.
     */
    protected void revertWorkspaceToDefaultPreferences() {
        for (Combo combo : this.combos) {
            this.revertWorkspaceToDefaultPreference(combo);
        }
    }

    /**
     * This is called only when the page is a workspace preferences page.
     */
    protected void revertWorkspaceToDefaultPreference(Combo combo) {
        ValidationMessage validationMessage = (ValidationMessage) combo.getData(VALIDATION_MESSAGE);
        String prefKey = validationMessage.getID();
        int prefValue = validationMessage.getDefaultSeverity();
        combo.select(this.convertPreferenceValueToComboIndex(prefValue));
        // force the workspace-level preference to be removed
        this.changedSeverities.put(prefKey, Integer.valueOf(ValidationMessage.UNSET_SEVERITY_PREFERENCE));
    }

    /**
     * This is called only when the page is a project properties page.
     */
    @Override
    protected void enableProjectSpecificSettings(boolean useProjectSpecificSettings) {
        super.enableProjectSpecificSettings(useProjectSpecificSettings);
        if (this.getDefaultsButton() == null) {
            //@Hack("If the defaults button is null the control is currently being built," +
            //      "otherwise the 'enable project specific settings' checkbox is being pressed")
            return;
        }

        this.hasProjectSpecificPreferences = Boolean.valueOf(useProjectSpecificSettings);

        if (useProjectSpecificSettings) {
            this.copyCurrentPreferencesToProject();
        } else {
            this.revertProjectPreferences();
        }
    }

    /**
     * Copy <em>all</em> the current settings to the project-level settings.
     * This locks down all the settings; otherwise future changes to the
     * workspace-level settings would affect any project-level settings
     * that were not copied over.
     * <p>
     * This is called only when the page is a project properties page.
     */
    protected void copyCurrentPreferencesToProject() {
        for (Combo combo : this.combos) {
            this.copyCurrentPreferenceToProject(combo);
        }
    }

    /**
     * This is called only when the page is a project properties page.
     */
    protected void copyCurrentPreferenceToProject(Combo combo) {
        ValidationMessage validationMessage = (ValidationMessage) combo.getData(VALIDATION_MESSAGE);
        String prefKey = validationMessage.getID();
        int prefValue = this.getValidationMessageSeverityPreference(prefKey);
        if (prefValue == ValidationMessage.UNSET_SEVERITY_PREFERENCE) {
            prefValue = validationMessage.getDefaultSeverity();
        }
        combo.select(this.convertPreferenceValueToComboIndex(prefValue));
        // combo does not fire a selection event when set programmatically...
        this.changedSeverities.put(prefKey, Integer.valueOf(prefValue));
    }

    /**
     * This is called only when the page is a project properties page.
     */
    protected void revertProjectPreferences() {
        for (Combo combo : this.combos) {
            this.revertProjectPreference(combo);
        }
    }

    /**
     * This is called only when the page is a project properties page.
     */
    protected void revertProjectPreference(Combo combo) {
        ValidationMessage validationMessage = (ValidationMessage) combo.getData(VALIDATION_MESSAGE);
        String prefKey = validationMessage.getID();
        int prefValue = this.getValidationMessageSeverityPreference(prefKey);
        if (prefValue == ValidationMessage.UNSET_SEVERITY_PREFERENCE) {
            prefValue = validationMessage.getDefaultSeverity();
        }
        combo.select(this.convertPreferenceValueToComboIndex(prefValue));
        // force the project-level preference to be removed
        this.changedSeverities.put(prefKey, Integer.valueOf(ValidationMessage.UNSET_SEVERITY_PREFERENCE));
    }

    @Override
    protected void noDefaultAndApplyButton() {
        throw new IllegalStateException(
                "Don't call this, see enableProjectSpecificSettings for the hack that looks for the defaultsButton being null"); //$NON-NLS-1$
    }

    // ********** plug-in preferences **********

    protected abstract int getValidationMessageSeverityPreference(IProject project, String prefKey);

    protected abstract int getValidationMessageSeverityPreference(String prefKey);

    protected abstract boolean getWorkspaceValidationPreferencesOverridden(IProject project);

    protected abstract void setWorkspaceValidationPreferencesOverridden(IProject project, boolean value);

    protected abstract void setValidationMessageSeverityPreference(String prefKey, int value);

    protected abstract void setValidationMessageSeverityPreference(IProject project, String prefKey, int value);

    // ********** OK/Revert/Apply behavior **********

    @Override
    public boolean performOk() {
        super.performOk();
        if (this.hasProjectSpecificPreferences != null) {
            this.setWorkspaceValidationPreferencesOverridden(this.getProject(),
                    this.hasProjectSpecificPreferences.booleanValue());
        }
        for (Map.Entry<String, Integer> entry : this.changedSeverities.entrySet()) {
            this.setProblemSeverityPreference(entry.getKey(), entry.getValue().intValue());
        }
        try {
            // true=fork; false=uncancellable
            this.buildOKDialog().run(true, false, this.buildOKRunnable());
        } catch (InterruptedException ex) {
            // should *not* happen...
            Thread.currentThread().interrupt();
            return false;
        } catch (InvocationTargetException ex) {
            throw new RuntimeException(ex.getTargetException());
        }

        return true;
    }

    protected void setProblemSeverityPreference(String prefKey, int value) {
        if (this.isProjectPreferencePage()) {
            this.setValidationMessageSeverityPreference(this.getProject(), prefKey, value);
        } else {
            this.setValidationMessageSeverityPreference(prefKey, value);
        }
    }

    private IRunnableContext buildOKDialog() {
        return new ProgressMonitorDialog(this.getShell());
    }

    private IRunnableWithProgress buildOKRunnable() {
        return new OKRunnable();
    }

    /* CU private */ class OKRunnable extends RunnableWithProgressAdapter {
        @Override
        public void run(IProgressMonitor monitor) throws InvocationTargetException {
            try {
                this.run_(monitor);
            } catch (CoreException ex) {
                throw new InvocationTargetException(ex);
            }
        }

        /**
         * {@link #performOk_(IProgressMonitor)} triggers a build that locks the
         * workspace root, so we need to use the workspace root as our
         * scheduling rule here
         */
        private void run_(IProgressMonitor monitor) throws CoreException {
            IWorkspace ws = ResourcesPlugin.getWorkspace();
            ws.run(JptProblemSeveritiesPage.this.buildOkWorkspaceRunnable(), ws.getRoot(), IWorkspace.AVOID_UPDATE,
                    monitor);
        }
    }

    IWorkspaceRunnable buildOkWorkspaceRunnable() {
        return new OKWorkspaceRunnable();
    }

    /* CU private */ class OKWorkspaceRunnable extends WorkspaceRunnableAdapter {
        @Override
        public void run(IProgressMonitor monitor) throws CoreException {
            JptProblemSeveritiesPage.this.performOk_(monitor);
        }
    }

    void performOk_(IProgressMonitor monitor) throws CoreException {
        int buildKind = IncrementalProjectBuilder.FULL_BUILD;
        IProject project = this.getProject();
        if (project != null) {
            // project preference page
            project.build(buildKind, monitor);
        } else {
            // workspace preference page
            ResourcesPlugin.getWorkspace().build(buildKind, monitor);
        }
    }

    @Override
    public void dispose() {
        this.storeSectionExpansionStates(this.getDialogPreferences());
        super.dispose();
    }

    protected IDialogSettings getDialogPreferences() {
        return this.getUIPlugin().getDialogSettings(SETTINGS_SECTION_NAME);
    }

    protected void storeSectionExpansionStates(IDialogSettings settings) {
        for (int index = this.expandablePanes.size(); index-- > 0;) {
            ExpandableComposite expandablePane = this.expandablePanes.get(index);
            settings.put(SETTINGS_EXPANDED + index, expandablePane.isExpanded());
        }
    }

}