org.eclipse.datatools.sqltools.sqleditor.internal.editor.SQLUpdater.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.datatools.sqltools.sqleditor.internal.editor.SQLUpdater.java

Source

/*******************************************************************************
 * Copyright (c) 2004, 2005 Sybase, Inc. 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:
 *     Sybase, Inc. - initial API and implementation
 *******************************************************************************/
package org.eclipse.datatools.sqltools.sqleditor.internal.editor;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceRuleFactory;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.datatools.sqltools.core.SQLDevToolsConfiguration;
import org.eclipse.datatools.sqltools.core.SQLToolsFacade;
import org.eclipse.datatools.sqltools.editor.core.connection.ISQLEditorConnectionInfo;
import org.eclipse.datatools.sqltools.sql.parser.ParseException;
import org.eclipse.datatools.sqltools.sql.parser.ParserParameters;
import org.eclipse.datatools.sqltools.sql.parser.ParsingResult;
import org.eclipse.datatools.sqltools.sql.parser.SQLParser;
import org.eclipse.datatools.sqltools.sql.parser.SQLParserConstants;
import org.eclipse.datatools.sqltools.sql.parser.Token;
import org.eclipse.datatools.sqltools.sqleditor.EditorConstants;
import org.eclipse.datatools.sqltools.sqleditor.ISQLEditorInput;
import org.eclipse.datatools.sqltools.sqleditor.SQLEditor;
import org.eclipse.datatools.sqltools.sqleditor.internal.PreferenceConstants;
import org.eclipse.datatools.sqltools.sqleditor.internal.SQLEditorPlugin;
import org.eclipse.datatools.sqltools.sqleditor.internal.SQLEditorResources;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialogWithToggle;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.texteditor.MarkerAnnotation;

/**
 * This class will listen to the documentChanged event of the SQLEditor and update the outline view( if there is one )
 * and the annotations.
 * 
 * @author Hui Cao
 *  
 */
public class SQLUpdater implements Runnable, IDocumentListener, IPropertyChangeListener {
    /**
     * Key for the sql syntax and portability markers 
     */
    public static final String SOURCE_ID = "SQL_SOURCE_ID";
    private SQLEditor _editor = null;
    private SQLOutlinePage _fOutlinePage = null;
    private IEditorInput _input = null;
    private IAnnotationModel _annotationModel;
    private IResource _resource;
    private String _portableTarget = "";
    private ArrayList _staleAnnotations = new ArrayList();
    private boolean _remembered = false;
    private ParsingResult _result;
    private boolean _needToParse = true;

    /**
     * @param editor
     */
    public SQLUpdater(SQLEditor editor) {
        this._editor = editor;
    }

    /////////////////////////////////////////////////////////////////////////////////
    //  IDocumentListener Interface
    /////////////////////////////////////////////////////////////////////////////////
    public void documentAboutToBeChanged(DocumentEvent event) {
        setInput(_editor.getEditorInput());
        rememberAnnotations();
    }

    public void documentChanged(DocumentEvent event) {
        setNeedToParse(true);
        _editor.getSite().getShell().getDisplay().timerExec(1000, this);
    }

    /////////////////////////////////////////////////////////////////////////////////
    //  IPropertyChangeListener Interface
    /////////////////////////////////////////////////////////////////////////////////
    public void propertyChange(PropertyChangeEvent event) {
        if (event.getProperty().equals(PreferenceConstants.EDITOR_PORTABILITY_CHECK_TARGET)) {
            rememberAnnotations();
            run();
        }
    }

    public void setOutlinePage(SQLOutlinePage outlinePage) {
        _fOutlinePage = outlinePage;
        if (_fOutlinePage != null && _result != null) {
            _fOutlinePage.setInput(_result.getRootNode());
        }

    }

    public void run() {
        setInput(_editor.getEditorInput());
        if (enableSyntaxValidation() == false) {
            removeMarkers();
            return;
        }

        try {
            //use ISchedulingRule to avoid locking the whole workspace
            IResourceRuleFactory ruleFactory = ResourcesPlugin.getWorkspace().getRuleFactory();
            ISchedulingRule rule = ruleFactory.markerRule(_resource);
            //TODO: Here I tried to use WorkspaceJob to let the update operation run in background, 
            //but I failed because SQLParser isn't thread safe. (by Hui Cao) 
            _resource.getWorkspace().run(new IWorkspaceRunnable() {
                public void run(IProgressMonitor monitor) throws CoreException {
                    update();
                }
            }, rule, IResource.NONE, new NullProgressMonitor());
        } catch (CoreException e) {
            SQLEditorPlugin.getDefault().log(SQLEditorResources.SQLUpdater_error_annotation, e);
        }

        // XXX: There seemed to have a bug in eclipse's jface text. When in the AnnotationModel
        // an annotation is removed, AnnotationPainter seemed not to handle it correctly. 
        // So we are forcing a repaint here, to work around the problem
        if (_editor != null) {
            ISourceViewer sv = _editor.getSV();
            if (sv != null) {
                StyledText text = sv.getTextWidget();
                if (text != null && !text.isDisposed()) {
                    text.redraw();
                }
            }
        }
        setNeedToParse(false);
    }

    /**
     *  
     */
    private void update() {
        //both outline and annotation depend on the same parsing process
        SQLDevToolsConfiguration f = SQLToolsFacade
                .getConfigurationByVendorIdentifier(_editor.getConnectionInfo().getDatabaseVendorDefinitionId());
        SQLParser p = f.getSQLService().getSQLParser();
        ISourceViewer viewer = _editor.getSV();
        if (viewer == null) {
            return;
        }
        String content = viewer.getDocument().get();
        //Add '\n' to avoid parser to throw exception when last line is single line comment.
        content = content + "\n";
        if (content == null || p == null) {
            removeMarkers(false);
            return;
        }

        boolean useDelimiter = _editor.getSQLType() == SQLParserConstants.TYPE_SQL_ROOT;
        ParserParameters parserParameters = new ParserParameters(useDelimiter, _editor.getSQLType());
        _result = p.parse(content, parserParameters);

        IDocument document = _editor.getDocumentProvider().getDocument(_input);
        _result.getRootNode().setDocument(document);
        _editor.setParsingResult(_result);

        removeMarkers(false);

        SQLParser pp = null;
        _portableTarget = SQLEditorPlugin.getDefault().getPreferenceStore()
                .getString(PreferenceConstants.EDITOR_PORTABILITY_CHECK_TARGET);
        if (_portableTarget != null) {
            SQLDevToolsConfiguration pf = SQLToolsFacade.getConfigurationByDBDefName(_portableTarget);
            pp = pf.getSQLService().getSQLParser();
            if (pp != null && !(pp.getClass().equals(p.getClass()))) {
                ParsingResult presult = pp.parse(content, parserParameters);
                updatePortableAnnotation(presult.getExceptions());
            }
        }

        //update error markers
        ArrayList exceptions = _result.getExceptions();
        updateErrorAnnotation(exceptions);

        if (_fOutlinePage != null) {
            _fOutlinePage.setInput(_result.getRootNode());
        }
    }

    /**
     * create markers according to the parse exceptions
     * 
     * @param exceptions Collections of ParseException
     * @throws CoreException
     */
    private void updateErrorAnnotation(ArrayList exceptions) {
        try {
            for (Iterator iter = exceptions.iterator(); iter.hasNext();) {
                //create markers according to the parse exceptions
                ParseException ex = (ParseException) iter.next();
                HashMap attrs = new HashMap();
                attrs.put(IMarker.SEVERITY, new Integer(IMarker.SEVERITY_ERROR));
                String storageName = "";//$NON-NLS-1$
                if (!(_input instanceof IFileEditorInput)) {
                    storageName = _input.getName() + ": ";//$NON-NLS-1$
                }
                attrs.put(IMarker.MESSAGE, storageName + ex.getMessage());
                attrs.put(ISQLEditorMarker.SHORT_MESSAGE, storageName + ex.getShortMessage());
                updateAnnotation(ex, EditorConstants.SYNTAX_MARKER_TYPE, attrs);
            }
        } catch (CoreException e) {
            SQLEditorPlugin.getDefault().log(SQLEditorResources.SQLUpdater_error_annotation, e);
        }
    }

    /**
     * create portable task markers according to the parse exceptions
     * 
     * @param exceptions Collections of ParseException
     * @throws CoreException
     */
    private void updatePortableAnnotation(ArrayList exceptions) {
        try {
            for (Iterator iter = exceptions.iterator(); iter.hasNext();) {
                //create markers according to the parse exceptions
                ParseException ex = (ParseException) iter.next();
                HashMap attrs = new HashMap();
                attrs.put(IMarker.USER_EDITABLE, Boolean.FALSE);
                String storageName = "";//$NON-NLS-1$
                if (!(_input instanceof IFileEditorInput)) {
                    storageName = _input.getName() + ": ";//$NON-NLS-1$
                }
                attrs.put(IMarker.MESSAGE, storageName
                        + NLS.bind(SQLEditorResources.SQLUpdater_nonportable, (new String[] { _portableTarget }))
                        + ex.getMessage());
                updateAnnotation(ex, EditorConstants.PORTABILITY_MARKER_TYPE, attrs);
            }
        } catch (CoreException e) {
            SQLEditorPlugin.getDefault().log(SQLEditorResources.SQLUpdater_error_annotation, e);
        }
    }

    /**
     * @param ex
     * @param markerType see IMarker.TASK ...
     * @param attributes String Object pairs for the new marker
     * @throws CoreException
     */
    private void updateAnnotation(ParseException ex, String markerType, Map attributes) throws CoreException {
        if (_annotationModel == null)
            return;
        IMarker marker = _resource.createMarker(markerType);
        marker.setAttributes(attributes);
        //since we will always refresh the task markers each time we parse the sql text, there's no
        //need to persist them.
        marker.setAttribute(IMarker.TRANSIENT, true);
        marker.setAttribute(SOURCE_ID, getMarkerSourceId());
        Token errorToken = ex.currentToken.next;
        int start = 0;
        int end = 0;
        int line = 0;
        try {
            IDocument document = _editor.getSV().getDocument();
            if (ex.currentToken.beginLine < 1 && (errorToken.image == null || "".equals(errorToken.image))) {
                return;
            }
            if (errorToken.image == null || "".equals(errorToken.image)) {
                //if the errorToken is not visible (<EOF>), select the current token
                start = document.getLineOffset(ex.currentToken.beginLine - 1) + ex.currentToken.beginColumn - 1;
                end = document.getLineOffset(ex.currentToken.endLine - 1) + ex.currentToken.endColumn;
                line = ex.currentToken.beginLine;
            } else {
                start = document.getLineOffset(errorToken.beginLine - 1) + errorToken.beginColumn - 1;
                end = document.getLineOffset(errorToken.endLine - 1) + errorToken.endColumn;
                line = errorToken.beginLine;
            }
            if (start < 0 || end - start < 0) {
                return;
            }
        } catch (BadLocationException e1) {
            SQLEditorPlugin.getDefault().log(SQLEditorResources.SQLUpdater_error_location, e1);
        }
        marker.setAttributes(new String[] { IMarker.CHAR_START, IMarker.CHAR_END, IMarker.LINE_NUMBER },
                new Object[] { new Integer(start), new Integer(end), new Integer(line) });
        MarkerAnnotation anno = new MarkerAnnotation(marker);
        Position position = new Position(start, end - start);
        //once the annotation is added to the annotation model, the platform knows how to present it
        _annotationModel.addAnnotation(anno, position);
    }

    /**
     * initialize _input, _resource and _annotaionModel . Since the editor input may change during a SQL 
     * Editor's lifecycle, this method will be called everytime before update.
     * 
     * @param input
     */
    void setInput(IEditorInput input) {
        _input = input;
        _resource = input instanceof IFileEditorInput ? (IResource) ((IFileEditorInput) input).getFile()
                : (IResource) ResourcesPlugin.getWorkspace().getRoot();
        // the annotationModel is created in Editor.doSetInput(input) by the platform
        _annotationModel = _editor.getDocumentProvider().getAnnotationModel(input);
    }

    /**
     *  
     */
    private void rememberAnnotations() {
        synchronized (_staleAnnotations) {
            //we must record the stale annotations before the document is changed so that there will be no dangling markers
            _staleAnnotations = new ArrayList();
            if (_annotationModel != null) {
                for (Iterator iter = _annotationModel.getAnnotationIterator(); iter.hasNext();) {
                    Annotation anno = (Annotation) iter.next();
                    if (anno instanceof MarkerAnnotation) {
                        IMarker marker = ((MarkerAnnotation) anno).getMarker();
                        String type = "";
                        try {
                            type = marker.getType();
                        } catch (CoreException e2) {
                            // marker does not exist
                            _staleAnnotations.add(anno);
                            continue;
                        }
                        // preserve user editable tasks
                        if (type.equals(EditorConstants.SYNTAX_MARKER_TYPE)
                                || type.equals(EditorConstants.PORTABILITY_MARKER_TYPE)) {
                            _staleAnnotations.add(anno);
                        }
                    }
                }
            }
            _remembered = true;
        }
    }

    /**
    * remove all the problem markers. Remember to call it when the file is
    * closing
    * 
    * @see removeMarkers(boolean group), this is a shortcut to
    *      removeMarkers(true)
    */
    public void removeMarkers() {
        removeMarkers(true);
    }

    /**
     * remove all the problem markers. Remember to call it when the file is closing
     * 
     * @param group whether to use IWorkspaceRunnable to group the changes
     */
    public void removeMarkers(boolean group) {
        //if the document is not changed but the editor already contains syntax errors
        if (!_remembered) {
            rememberAnnotations();
        }
        if (group) {
            try {
                IResourceRuleFactory ruleFactory = ResourcesPlugin.getWorkspace().getRuleFactory();
                ISchedulingRule rule = ruleFactory.markerRule(_resource);
                _resource.getWorkspace().run(new IWorkspaceRunnable() {
                    public void run(IProgressMonitor monitor) throws CoreException {
                        removeRememberedMarkers();
                    }
                }, rule, IResource.NONE, new NullProgressMonitor());
            } catch (CoreException e) {
            }
        } else {
            removeRememberedMarkers();
        }
    }

    /**
     *  
     */
    private void removeRememberedMarkers() {
        synchronized (_staleAnnotations) {
            for (Iterator iter = _staleAnnotations.iterator(); iter.hasNext();) {
                Annotation anno = (Annotation) iter.next();
                if (anno instanceof MarkerAnnotation) {
                    IMarker marker = ((MarkerAnnotation) anno).getMarker();
                    try {
                        //after marker is removed, it disappeared in the problem view
                        marker.delete();
                        anno.markDeleted(true);
                    } catch (CoreException e2) {
                        SQLEditorPlugin.getDefault().log(SQLEditorResources.SQLUpdater_error_removemarker, e2);
                    }
                }
                //after annotation is removed, it disappeared from the side bar
                _annotationModel.removeAnnotation(anno);
            }
            _remembered = false;
        }
        try {
            if (_resource instanceof IFile) {
                _resource.deleteMarkers(EditorConstants.SYNTAX_MARKER_TYPE, false, IResource.DEPTH_ZERO);
                _resource.deleteMarkers(EditorConstants.PORTABILITY_MARKER_TYPE, false, IResource.DEPTH_ZERO);
            } else {
                IMarker[] markers = _resource.findMarkers(EditorConstants.SYNTAX_MARKER_TYPE, false,
                        IResource.DEPTH_ZERO);
                String markerSourceId = getMarkerSourceId();
                for (int i = 0; i < markers.length; i++) {
                    if (markerSourceId.equals(markers[i].getAttribute(SOURCE_ID))) {
                        markers[i].delete();
                    }
                }
                markers = _resource.findMarkers(EditorConstants.PORTABILITY_MARKER_TYPE, false,
                        IResource.DEPTH_ZERO);
                for (int i = 0; i < markers.length; i++) {
                    if (markerSourceId.equals(markers[i].getAttribute(SOURCE_ID))) {
                        markers[i].delete();
                    }
                }
            }
        } catch (CoreException e) {
            SQLEditorPlugin.getDefault().log(SQLEditorResources.SQLUpdater_error_removemarker, e);
        }

    }

    /**
     * Enable syntax validation based on preference options and user's selection.
     * 
     * @return true means syntax validation is enabled.
     * @author lihuang
     */
    private boolean enableSyntaxValidation() {
        if (_editor != null && _editor.getConnectionInfo() != null) {
            ISQLEditorConnectionInfo connInfo = _editor.getConnectionInfo();
            SQLDevToolsConfiguration conf = SQLToolsFacade.getConfiguration(_editor.getDatabaseIdentifier(),
                    connInfo.getDatabaseVendorDefinitionId());
            SQLParser p = conf.getSQLService().getSQLParser();
            if (p == null || !p.isComplete()) {
                return false;
            }
        }
        IPreferenceStore preferenceStore = SQLEditorPlugin.getDefault().getPreferenceStore();

        boolean enableSyntaxValidation = true;
        boolean isMaxLineLimitation = preferenceStore.getBoolean(PreferenceConstants.SYNTAX_VALIDATION_MAX_LINE);
        // just return false if syntax validation is turned off.
        if (preferenceStore.getBoolean(PreferenceConstants.SYNTAX_VALIDATION) == false) {
            enableSyntaxValidation = false;
        } else if (isMaxLineLimitation) {

            // show prompt dialog when the number of lines is more than maximun number of lines.
            int lines = ((SQLEditor) _editor).getSV().getDocument().getNumberOfLines();
            int maximunLines = Integer
                    .parseInt(preferenceStore.getString(PreferenceConstants.SYNTAX_VALIDATION_MAX_LINE_NUMBER));
            if (preferenceStore.getBoolean(PreferenceConstants.SHOW_DAILOG_FOR_SYNTAX_VALIDATION) == true) {
                if (lines >= maximunLines) {
                    Shell shell = SQLEditorPlugin.getActiveWorkbenchShell();

                    // Activate the shell if necessary so the prompt is visible
                    if (shell.getMinimized()) {
                        shell.setMinimized(false);
                    }
                    MessageDialogWithToggle dialog = MessageDialogWithToggle.openYesNoQuestion(shell,
                            SQLEditorResources.SQLUpdate_dialog_title,
                            NLS.bind(SQLEditorResources.SQLUpdate_dialog_message,
                                    (new Object[] { Integer.toString(maximunLines) })),
                            SQLEditorResources.SQLUpdate_dialog_toggle, false,
                            SQLEditorPlugin.getDefault().getPreferenceStore(),
                            PreferenceConstants.SHOW_DAILOG_FOR_SYNTAX_VALIDATION);
                    if (dialog.getReturnCode() == IDialogConstants.YES_ID) {
                        enableSyntaxValidation = false;
                    } else if (dialog.getReturnCode() == IDialogConstants.NO_ID
                            && preferenceStore.getString(PreferenceConstants.SHOW_DAILOG_FOR_SYNTAX_VALIDATION)
                                    .equals(MessageDialogWithToggle.NEVER)) {
                        enableSyntaxValidation = true;
                        preferenceStore.setValue(PreferenceConstants.SYNTAX_VALIDATION_MAX_LINE, false);
                    } else {
                        enableSyntaxValidation = true;
                    }
                }
            } else {
                if (lines >= maximunLines) {
                    enableSyntaxValidation = false;
                } else {
                    enableSyntaxValidation = true;
                }
            }

        }
        if (enableSyntaxValidation == false) {
            if (_fOutlinePage != null) {
                _fOutlinePage.update(null);
            }
            if (_annotationModel != null) {
                removeMarkers();
            }
            return false;
        } else {
            return true;
        }
    }

    public boolean needToParse() {
        return _needToParse;
    }

    public void setNeedToParse(boolean needToParse) {
        _needToParse = needToParse;
    }

    private String getMarkerSourceId() {
        return ((ISQLEditorInput) _editor.getEditorInput()).getId();
    }
}