com.nextep.designer.sqlgen.ui.editors.sql.SQLCompletionProcessor.java Source code

Java tutorial

Introduction

Here is the source code for com.nextep.designer.sqlgen.ui.editors.sql.SQLCompletionProcessor.java

Source

/*******************************************************************************
 * Copyright (c) 2011 neXtep Software and contributors.
 * All rights reserved.
 *
 * This file is part of neXtep designer.
 *
 * NeXtep designer is free software: you can redistribute it 
 * and/or modify it under the terms of the GNU General Public 
 * License as published by the Free Software Foundation, either 
 * version 3 of the License, or any later version.
 *
 * NeXtep designer is distributed in the hope that it will be 
 * useful, but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Foobar.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Contributors:
 *     neXtep Softwares - initial API and implementation
 *******************************************************************************/
package com.nextep.designer.sqlgen.ui.editors.sql;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import org.apache.commons.collections.map.MultiValueMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.FindReplaceDocumentAdapter;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TextPresentation;
import org.eclipse.jface.text.contentassist.CompletionProposal;
import org.eclipse.jface.text.contentassist.ContextInformation;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.eclipse.jface.text.contentassist.IContextInformationPresenter;
import org.eclipse.jface.text.contentassist.IContextInformationValidator;
import org.eclipse.jface.text.projection.Segment;
import org.eclipse.jface.text.rules.IToken;
import com.nextep.datadesigner.Designer;
import com.nextep.datadesigner.dbgm.impl.Datatype;
import com.nextep.datadesigner.dbgm.model.IBasicColumn;
import com.nextep.datadesigner.dbgm.model.IBasicTable;
import com.nextep.datadesigner.dbgm.model.IColumnable;
import com.nextep.datadesigner.dbgm.model.IDatatype;
import com.nextep.datadesigner.dbgm.model.IProcedure;
import com.nextep.datadesigner.dbgm.model.IProcedureParameter;
import com.nextep.datadesigner.dbgm.model.ISequence;
import com.nextep.datadesigner.dbgm.model.ITrigger;
import com.nextep.datadesigner.dbgm.model.IVariable;
import com.nextep.datadesigner.dbgm.model.IVariableContainer;
import com.nextep.datadesigner.dbgm.model.IView;
import com.nextep.datadesigner.dbgm.services.DBGMHelper;
import com.nextep.datadesigner.exception.ErrorException;
import com.nextep.datadesigner.model.ChangeEvent;
import com.nextep.datadesigner.model.IElementType;
import com.nextep.datadesigner.model.IEventListener;
import com.nextep.datadesigner.model.INamedObject;
import com.nextep.datadesigner.model.IObservable;
import com.nextep.datadesigner.model.ITypedObject;
import com.nextep.datadesigner.sqlgen.impl.GeneratorFactory;
import com.nextep.datadesigner.sqlgen.impl.LightProcedure;
import com.nextep.datadesigner.sqlgen.impl.PackagePrototypeMatcher;
import com.nextep.datadesigner.sqlgen.impl.Prototype;
import com.nextep.datadesigner.sqlgen.model.IPackage;
import com.nextep.datadesigner.sqlgen.model.IPrototype;
import com.nextep.datadesigner.vcs.services.VersionHelper;
import com.nextep.designer.core.CorePlugin;
import com.nextep.designer.core.model.IConnection;
import com.nextep.designer.dbgm.sql.TableAlias;
import com.nextep.designer.dbgm.ui.DBGMImages;
import com.nextep.designer.helper.DatatypeHelper;
import com.nextep.designer.sqlgen.model.ISQLParser;
import com.nextep.designer.sqlgen.services.ICaptureService;
import com.nextep.designer.sqlgen.ui.SQLGenImages;
import com.nextep.designer.ui.factories.ImageFactory;
import com.nextep.designer.vcs.model.IVersionable;

/**
 * @author Christophe Fondacci
 */
public class SQLCompletionProcessor implements IContentAssistProcessor {

    private static final Log LOGGER = LogFactory.getLog(SQLCompletionProcessor.class);

    protected IContextInformationValidator fValidator = new Validator();
    private Object editedObject;
    private ISQLParser parser;
    private Map<String, IColumnable> tablesMap;
    private boolean specEditor = false;
    private MultiValueMap proceduresMap;
    private Collection<IPackage> packageList;

    // private Map<String,IBasicColumn> columnsMap;
    public SQLCompletionProcessor(Object o) {
        this(o, false);
    }

    public SQLCompletionProcessor(Object o, boolean specEditor) {
        proceduresMap = new MultiValueMap();
        packageList = new ArrayList<IPackage>();
        this.specEditor = specEditor;
        this.editedObject = o;
        // Registering listener for model switch (checkout / checkin)
        // TODO: implement a more generic model switch
        if (editedObject instanceof IObservable) {
            Designer.getListenerService().registerListener(this, (IObservable) editedObject, new IEventListener() {

                @Override
                public void handleEvent(ChangeEvent event, IObservable source, Object data) {
                    if (source != editedObject && source != null) {
                        editedObject = source;
                    }
                }
            });
        }
        parser = GeneratorFactory.getSQLParser(DBGMHelper.getCurrentVendor());
        // Initializing tables
        Job j = new Job("Computing completion proposals...") {

            @Override
            protected IStatus run(IProgressMonitor monitor) {
                List<IVersionable<?>> vTables = new ArrayList<IVersionable<?>>();
                List<IVersionable<?>> vProcs = new ArrayList<IVersionable<?>>();
                List<IVersionable<?>> vPackages = new ArrayList<IVersionable<?>>();
                boolean failed = true;
                if (editedObject instanceof IConnection) {
                    // If we have a connection we try to extract proposals form the connection
                    final ICaptureService captureService = CorePlugin.getService(ICaptureService.class);
                    try {
                        // Getting proposals
                        final Collection<IVersionable<?>> dbObjects = captureService
                                .getContentsForCompletion((IConnection) editedObject, monitor);
                        // Processing fetched objects to compute proposals structures
                        for (IVersionable<?> dbObject : dbObjects) {
                            if (dbObject.getType() == IElementType.getInstance(IBasicTable.TYPE_ID)
                                    || dbObject.getType().equals(IElementType.getInstance(IView.TYPE_ID))) {
                                vTables.add(dbObject);
                            } else if (dbObject.getType() == IElementType.getInstance(IProcedure.TYPE_ID)) {
                                vProcs.add(dbObject);
                            } else if (dbObject.getType() == IElementType.getInstance(IPackage.TYPE_ID)) {
                                vPackages.add(dbObject);
                            }
                        }
                        failed = false;
                    } catch (RuntimeException e) {
                        LOGGER.error("Unable to fetch completion proposals from database : " + e.getMessage(), e);
                    }
                }
                // If we failed or if not in connected mode, we compute proposals from workspace
                if (failed) {
                    vTables = VersionHelper.getAllVersionables(VersionHelper.getCurrentView(),
                            IElementType.getInstance(IBasicTable.TYPE_ID));
                    vTables.addAll(VersionHelper.getAllVersionables(VersionHelper.getCurrentView(),
                            IElementType.getInstance(IView.TYPE_ID)));
                    vProcs = VersionHelper.getAllVersionables(VersionHelper.getCurrentView(),
                            IElementType.getInstance(IProcedure.TYPE_ID));
                    vPackages = VersionHelper.getAllVersionables(VersionHelper.getCurrentView(),
                            IElementType.getInstance(IPackage.TYPE_ID));
                }
                tablesMap = new HashMap<String, IColumnable>();
                for (IVersionable<?> v : vTables) {
                    ITypedObject t = (ITypedObject) v.getVersionnedObject().getModel();
                    tablesMap.put(((INamedObject) t).getName().toUpperCase(), (IColumnable) t);
                }
                if (editedObject instanceof ITrigger) {
                    if (((ITrigger) editedObject).getTriggableRef().getType() == IElementType
                            .getInstance(IBasicTable.TYPE_ID)) {
                        try {
                            final IBasicTable t = (IBasicTable) VersionHelper
                                    .getReferencedItem(((ITrigger) editedObject).getTriggableRef());
                            tablesMap.put("NEW", t); //$NON-NLS-1$
                            tablesMap.put("OLD", t); //$NON-NLS-1$
                        } catch (ErrorException e) {
                            LOGGER.warn("Unable to locate trigger parent: " + e.getMessage(), e);
                        }
                    }
                }
                // Getting procedure
                for (IVersionable<?> v : vProcs) {
                    proceduresMap.put("", (IProcedure) v.getVersionnedObject().getModel()); //$NON-NLS-1$
                }
                for (IVersionable<?> v : vPackages) {
                    final IPackage pkg = (IPackage) v.getVersionnedObject().getModel();
                    packageList.add(pkg);
                    if (!pkg.isParsed()) {
                        DBGMHelper.parse(pkg);
                    }
                    final Collection<IProcedure> procs = pkg.getProcedures();
                    if (procs != null) {
                        for (IProcedure proc : new ArrayList<IProcedure>(procs)) {
                            proceduresMap.put(pkg.getName().toUpperCase(), proc);
                            if (editedObject == pkg) {
                                proceduresMap.put("", proc); //$NON-NLS-1$
                            }
                        }
                    }
                }
                return Status.OK_STATUS;
            }
        };
        j.schedule();

    }

    /**
     * Simple content assist tip closer. The tip is valid in a range of 5 characters around its
     * popup location.
     */
    protected static class Validator implements IContextInformationValidator, IContextInformationPresenter {

        protected int fInstallOffset;

        public boolean isContextInformationValid(int offset) {
            return Math.abs(fInstallOffset - offset) < 5;
        }

        public void install(IContextInformation info, ITextViewer viewer, int offset) {
            fInstallOffset = offset;
        }

        public boolean updatePresentation(int documentPosition, TextPresentation presentation) {
            return false;
        }
    }

    @SuppressWarnings("unchecked")
    public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) {
        try {
            return doComputeCompletionProposals(viewer, offset);
        } catch (RuntimeException e) {
            // Here we catch everything because completion processor should never
            // fail up to the user
            LOGGER.error("Problems while computing completions proposals : " + e.getMessage(), e);
            return null;
        }
    }

    private ICompletionProposal[] doComputeCompletionProposals(ITextViewer viewer, int offset) {
        List<ICompletionProposal> result = new ArrayList<ICompletionProposal>();
        try {
            int prefixStart = getPrefixStart(viewer, offset);
            String prefix = viewer.getDocument().get(prefixStart, offset - prefixStart).toUpperCase();
            // Have we got a variable prefix ?
            // if(prefixStart>0 &&
            // parser.getVarSeparator().equals(viewer.getDocument().get(prefixStart-1, 1))) {

            // } else {
            // If not we might be in a SQL statement, so we parse
            DMLParseResult r = parseSQL(viewer, offset);
            if (r != null) {
                // Are we in a section which should display table proposals (FROM)
                if (matchTableSegment(offset, r)) {
                    final List<String> tabNames = new ArrayList<String>(tablesMap.keySet());
                    Collections.sort(tabNames);
                    for (String s : tabNames) {
                        if (s.toUpperCase().startsWith(prefix)) {
                            ITypedObject tab = (ITypedObject) tablesMap.get(s);
                            String realName = ((INamedObject) tab).getName();
                            result.add(new CompletionProposal(realName, prefixStart, offset - prefixStart,
                                    realName.length(), ImageFactory.getImage(tab.getType().getTinyIcon()),
                                    realName + " " //$NON-NLS-1$
                                            + tab.getType().getName(),
                                    null, "")); //$NON-NLS-1$
                        }
                    }
                } else if (".".equals(viewer.getDocument().get(prefixStart - 1, 1))) { //$NON-NLS-1$
                    // We are after a '.' so we try to locate the corresponding table alias
                    String alias = getLastWord(viewer, prefixStart - 1).toUpperCase();
                    TableAlias a = r.getTableAlias(alias);
                    if (("NEW".equals(alias) || "OLD".equals(alias)) //$NON-NLS-1$ //$NON-NLS-2$
                            && (editedObject instanceof ITrigger)) {
                        a = new TableAlias(alias);
                        a.setTable(tablesMap.get(alias));
                    }
                    if (a != null && a.getTable() != null) {
                        addColumnsProposal(result, a, prefix, prefixStart, offset, false);
                    }
                    addPackageProcProposals(viewer, prefix, prefixStart, offset, result);
                } else {
                    // Otherwise we propose all columns from all known tables of the statement
                    if (r.getFromTables().size() > 0) {
                        for (TableAlias a : r.getFromTables()) {
                            if (a.getTable() != null) {
                                addColumnsProposal(result, a, prefix, prefixStart, offset, true);
                            }
                        }
                    }
                    // And we propose database function prototypes as well
                    List<String> funcs = parser.getTypedTokens().get(ISQLParser.FUNC);
                    for (String f : (funcs == null ? (List<String>) Collections.EMPTY_LIST : funcs)) {
                        if (f.startsWith(prefix)) {
                            final String func = f.toLowerCase() + "()"; //$NON-NLS-1$
                            result.add(new CompletionProposal(func, prefixStart, offset - prefixStart,
                                    func.length() - 1, DBGMImages.ICON_FUNC, func + " - Database function", null,
                                    "")); //$NON-NLS-1$
                        }
                    }
                    addPackageProcProposals(viewer, prefix, prefixStart, offset, result);
                }
                if (editedObject instanceof IPackage) {
                    final IPackage pkg = (IPackage) editedObject;
                    // First saving contents to not alter package state
                    DBGMHelper.parse(pkg, viewer.getDocument().get());
                    addPackageInnerProposals(pkg, prefix, result, offset, prefixStart);
                }
            } else {
                List<IPrototype> prototypes = new ArrayList<IPrototype>();
                // We may have a package prefix, adding procs here
                boolean pkgAdded = false;
                if (".".equals(viewer.getDocument().get(prefixStart - 1, 1))) { //$NON-NLS-1$
                    addPackageProcProposals(viewer, prefix, prefixStart, offset, result);
                    pkgAdded = true;
                }
                // Specification additions
                if (editedObject instanceof IPackage) {
                    final IPackage pkg = (IPackage) editedObject;
                    // First saving contents to not alter package state
                    DBGMHelper.parse(pkg, viewer.getDocument().get());
                    addPackageInnerProposals(pkg, prefix, result, offset, prefixStart);
                    // Building specification proposals
                    for (IProcedure p : pkg.getProcedures()) {
                        prototypes
                                .add(new Prototype(p.getName(),
                                        "Insert procedure specification for '" + p.getName() + "'", p //$NON-NLS-2$
                                                .getHeader(),
                                        p.getHeader().length(), new PackagePrototypeMatcher()));
                    }
                }
                if (!pkgAdded) {
                    addPackageProcProposals(viewer, prefix, prefixStart, offset, result);
                }
                // We are not in a SQL statement so we propose SQL prototypes
                prototypes.addAll(parser.getPrototypes());
                for (IPrototype p : prototypes) {
                    if (p.getName().toUpperCase().startsWith(prefix)) {
                        // Retrieving context
                        Object contextualEntity = (editedObject instanceof IPackage)
                                ? ((IPackage) editedObject).getParseData().getEntity(offset)
                                : null;
                        if (contextualEntity == null || specEditor) {
                            contextualEntity = editedObject;
                        }
                        if (p.getPrototypeMatcher().match(contextualEntity) > 0) {
                            result.add(new CompletionProposal(p.getTemplate(), prefixStart, offset - prefixStart,
                                    p.getCursorPosition(), SQLGenImages.ICON_HINT,
                                    p.getName() + " - " + p.getDescription(), null, p.getTemplate())); //$NON-NLS-1$
                        }
                    }
                }
                // We are not in a SQL statement, so we add DML hints
                List<String> dmlStmts = parser.getTypedTokens().get(ISQLParser.DML);
                for (String s : dmlStmts) {
                    if (s.startsWith(prefix)) {
                        final String func = s.toLowerCase();
                        // IContextInformation info = new
                        // ContextInformation("ContextDisplayString","InformationDisplayString");
                        result.add(new CompletionProposal(func, prefixStart, offset - prefixStart, func.length(),
                                SQLGenImages.ICON_HINT, func + " - SQL statement", null, "")); //$NON-NLS-2$
                    }
                }
                // And we add datatypes proposals
                List<String> datatypes = parser.getTypedTokens().get(ISQLParser.DATATYPE);
                for (String s : datatypes) {
                    if (s.startsWith(prefix)) {
                        final String type = s;
                        // Instantiating datatype
                        final IDatatype d = new Datatype(s);
                        result.add(new CompletionProposal(type, prefixStart, offset - prefixStart, type.length(),
                                DatatypeHelper.getDatatypeIcon(d), type + " - Datatype", null, "")); //$NON-NLS-2$
                    }
                }
            }

            // }
        } catch (BadLocationException e) {
            // Logging any bad location error only for debug
            LOGGER.debug("Error while generating completion proposals", e);
        }
        // Returning the array of proposals
        return result.toArray(new ICompletionProposal[result.size()]);
    }

    private void addPackageProcProposals(ITextViewer viewer, String prefix, int prefixStart, int offset,
            List<ICompletionProposal> result) throws BadLocationException {
        // Adding proposals for packages / procs
        if (".".equals(viewer.getDocument().get(prefixStart - 1, 1))) { //$NON-NLS-1$
            final String last = getLastWord(viewer, prefixStart - 1);
            Collection procs = proceduresMap.getCollection(last.toUpperCase());
            if (procs != null) {
                for (Object o : procs) {
                    IProcedure p = (IProcedure) o;
                    if (p.getName().toUpperCase().startsWith(prefix)) {
                        result.add(new CompletionProposal(p.getName(), prefixStart, offset - prefixStart,
                                p.getName().length(), ImageFactory.getImage(p.getType().getTinyIcon()),
                                p.getName() + " - procedure", null, "")); //$NON-NLS-2$
                    }
                }
            }
        } else {
            Collection procs = proceduresMap.getCollection(""); //$NON-NLS-1$
            if (procs != null) {
                for (Object o : procs) {
                    IProcedure p = (IProcedure) o;
                    if (p.getName().toUpperCase().startsWith(prefix)) {
                        result.add(new CompletionProposal(p.getName(), prefixStart, offset - prefixStart,
                                p.getName().length(), ImageFactory.getImage(p.getType().getTinyIcon()),
                                p.getName() + " - procedure", null, "")); //$NON-NLS-2$
                    }
                }
            }
            for (IPackage p : packageList) {
                if (p.getName().toUpperCase().startsWith(prefix)) {
                    result.add(new CompletionProposal(p.getName(), prefixStart, offset - prefixStart,
                            p.getName().length(), ImageFactory.getImage(p.getType().getTinyIcon()),
                            p.getName() + " - package", null, "")); //$NON-NLS-2$
                }
            }
        }
    }

    /**
     * Adds proposal from the specified package. This includes global package variables, parameter
     * declaration of the procedure and procedure inner variable declaration.
     * 
     * @param pkg
     * @param prefix
     * @param result
     * @param offset
     * @param prefixStart
     */
    private void addPackageInnerProposals(IPackage pkg, String prefix, List<ICompletionProposal> result, int offset,
            int prefixStart) {
        // Getting entity
        Object entity = pkg.getParseData().getEntity(offset);
        if (entity instanceof LightProcedure) {
            final LightProcedure p = (LightProcedure) entity;
            addParametersProposal(p, prefix, result, offset, prefixStart);
        }
        IVariableContainer varContainer = null;
        if (entity instanceof IVariableContainer) {
            // Adding proposals from the entity which contains proposals
            varContainer = (IVariableContainer) entity;
            addVariablesProposal(varContainer, prefix, result, offset, prefixStart);
        }

        // Adding package variables
        addVariablesProposal(pkg, prefix, result, offset, prefixStart);

    }

    /**
     * Indicates if the given offset is inside a table declaration segment.
     * 
     * @param offset offset to check
     * @param r the result of the parse of the SQL statement at this offset
     * @return <code>true</code> if the offset is inside a table declaration segment, else
     *         <code>false</code>.
     */
    private boolean matchTableSegment(int offset, DMLParseResult r) {
        if (r == null) {
            return false;
        }
        for (Segment s : r.getTableSegments()) {
            if (s.includes(offset)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Adds the column proposals from the given table alias to the list of
     * {@link ICompletionProposal} using the specified parameters.
     * 
     * @param result the array of {@link ICompletionProposal} to fill with column proposals
     * @param a the table alias to use for retrieving columns definitions
     * @param prefix the prefix already entered by the user which should match the start of the
     *        column name being added.
     * @param prefixStart the prefix offset
     * @param offset the caret offset
     * @param appendAlias whether or not the method should append any existing table alias before
     *        the column name
     */
    private void addColumnsProposal(List<ICompletionProposal> result, TableAlias a, String prefix, int prefixStart,
            int offset, boolean appendAlias) {
        if (a != null && a.getTable() != null) {
            for (IBasicColumn c : a.getTable().getColumns()) {
                if (c.getName().toUpperCase().startsWith(prefix)) {
                    String proposal = (a.getTableAlias() != null && appendAlias ? a.getTableAlias() + "." : "") //$NON-NLS-1$ //$NON-NLS-2$
                            + c.getName();
                    result.add(new CompletionProposal(proposal, prefixStart, offset - prefixStart,
                            proposal.length(), DatatypeHelper.getDatatypeIcon(c.getDatatype(), true),
                            c.getName() + " - Column of '" + c.getParent().getName() + "'", null, "")); //$NON-NLS-2$ //$NON-NLS-3$
                }
            }
        }
        // Adding sequences names
        List<IVersionable<?>> seqV = VersionHelper.getAllVersionables(VersionHelper.getCurrentView(),
                IElementType.getInstance(ISequence.TYPE_ID));
        for (IVersionable<?> seq : seqV) {
            final String name = seq.getName();
            if (name.toUpperCase().startsWith(prefix)) {
                result.add(new CompletionProposal(name, prefixStart, offset - prefixStart, name.length(),
                        ImageFactory.getImage(seq.getType().getIcon()), name + " - " //$NON-NLS-1$
                                + seq.getType().getName(),
                        null, "")); //$NON-NLS-1$
            }
        }
    }

    /**
     * Adds variable proposals from the given variable container.
     * 
     * @param varContainer the variable container from which we extract the proposals
     * @param prefix prefix of the user-typed text
     * @param result collection of ICompletionProposal to fill in
     * @param offset offset at which the proposal should be inserted
     * @param prefixStart the start of the prefix (semi-entered text to complete)
     */
    private void addVariablesProposal(IVariableContainer varContainer, String prefix,
            List<ICompletionProposal> result, int offset, int prefixStart) {
        for (IVariable v : varContainer.getVariables()) {
            if (v.getName().toUpperCase().startsWith(prefix)) {
                result.add(new CompletionProposal(v.getName().toLowerCase(), prefixStart, offset - prefixStart,
                        v.getName().length(), SQLGenImages.ICON_PUBLIC_FIELD, v.getName().toLowerCase() + " " //$NON-NLS-1$
                                + v.getDatatypeName() + " - Variable of " + ((INamedObject) varContainer).getName()
                                + "", //$NON-NLS-1$
                        null, "")); //$NON-NLS-1$
            }
        }
    }

    /**
     * Adds variable proposals from the given variable container.
     * 
     * @param varContainer the variable container from which we extract the proposals
     * @param prefix prefix of the user-typed text
     * @param result collection of ICompletionProposal to fill in
     * @param offset offset at which the proposal should be inserted
     * @param prefixStart the start of the prefix (semi-entered text to complete)
     */
    private void addParametersProposal(IProcedure procedure, String prefix, List<ICompletionProposal> result,
            int offset, int prefixStart) {
        for (IProcedureParameter p : procedure.getParameters()) {
            if (p.getName().toUpperCase().startsWith(prefix)) {
                result.add(new CompletionProposal(p.getName().toLowerCase(), prefixStart, offset - prefixStart,
                        p.getName().length(), SQLGenImages.ICON_PUBLIC_FIELD, p.getName().toLowerCase() + " " //$NON-NLS-1$
                                + p.getDatatype().getName() + " - Argument of " + procedure.getName() + "", //$NON-NLS-2$
                        null, "")); //$NON-NLS-1$
            }
        }
    }

    public IContextInformation[] computeContextInformation(ITextViewer viewer, int offset) {
        IContextInformation[] result = new IContextInformation[5];
        for (int i = 0; i < result.length; i++)
            result[i] = new ContextInformation("context " + i, //$NON-NLS-1$
                    "info " + i); //$NON-NLS-1$
        return result;

    }

    /**
     * Retrieves the prefix from the given offset
     * 
     * @param viewer text viewer
     * @param offset offset at which the content assist has been triggerred
     * @return the string prefix
     * @throws BadLocationException
     */
    private int getPrefixStart(ITextViewer viewer, int offset) {
        IDocument doc = viewer.getDocument();
        if (doc == null || offset > doc.getLength())
            return -1;

        int length = 0;
        try {
            while (--offset >= 0 && Character.isJavaIdentifierPart(doc.getChar(offset)))
                length++;

            return offset + 1;
        } catch (BadLocationException e) {
            LOGGER.debug("Error while retrieving completion prefix: BadLocation.");
            return -1;
        }

    }

    /**
     * Retrieves the last word immediately before the specified offset in the text viewer. Any space
     * will be ignored.
     * 
     * @param viewer viewer to look into
     * @param offset offset to start the search from.
     * @return the last word.
     */
    private String getLastWord(ITextViewer viewer, int offset) {
        IDocument doc = viewer.getDocument();
        if (doc == null || offset > doc.getLength())
            return ""; //$NON-NLS-1$

        int length = 0;
        try {
            boolean wordStarted = false;
            while (--offset >= 0
                    && (!wordStarted || wordStarted && Character.isJavaIdentifierPart(doc.getChar(offset)))) {
                // We continue on spaces
                if (' ' == doc.getChar(offset)) {
                    continue;
                } else if (',' == doc.getChar(offset)) {
                    int lastWordStart = getPrefixStart(viewer, offset - 1);
                    return getLastWord(viewer, lastWordStart);
                } else if (!wordStarted) {
                    wordStarted = true;
                }
                length++;
            }

            return doc.get(offset + 1, length);
        } catch (BadLocationException e) {
            LOGGER.debug("Error while retrieving completion prefix: BadLocation.");
            return ""; //$NON-NLS-1$
        }
    }

    public char[] getCompletionProposalAutoActivationCharacters() {
        return new char[] { '.', '(', ':', ',', '=' };
    }

    public char[] getContextInformationAutoActivationCharacters() {
        return new char[] { '#' };
    }

    public IContextInformationValidator getContextInformationValidator() {
        return fValidator;
    }

    public String getErrorMessage() {
        return null;
    }

    public void resyncPackage() {
    }

    /**
     * This method parses the SQL statement defined at the current start offset. The method will
     * retrieve any SQL statement which encapsulate the start offset, parse it and return the result
     * of this parse for completion proposals.
     * 
     * @param viewer viewer of the document to parse
     * @param start start offset
     * @return a {@link DMLParseResult} which contains information about the parse of the found SQL
     *         statement, or <code>null</code> if no SQL statement has been found from the given
     *         start offset.
     */
    private DMLParseResult parseSQL(ITextViewer viewer, int start) {
        // Retrieving the corresponding statement start
        IDocument doc = new Document();
        doc.set(viewer.getDocument().get() + " "); //$NON-NLS-1$

        FindReplaceDocumentAdapter finder = new FindReplaceDocumentAdapter(doc);
        try {
            IRegion lastSemicolonRegion = finder.find(start - 1, ";", false, false, false, false); //$NON-NLS-1$
            if (lastSemicolonRegion == null) {
                lastSemicolonRegion = new Region(0, 1);
            }
            IRegion selectRegion = finder.find(lastSemicolonRegion.getOffset(), "SELECT|INSERT|UPDATE|DELETE", true, //$NON-NLS-1$
                    false, false, true);

            IRegion endSemicolonRegion = finder.find(start == doc.getLength() ? start - 1 : start, ";", true, false, //$NON-NLS-1$
                    false, false);
            if (endSemicolonRegion == null) {
                endSemicolonRegion = new Region(doc.getLength() - 1, 0);
            }
            if (selectRegion == null || lastSemicolonRegion == null || endSemicolonRegion == null) {
                return null;
            }
            // The select must be found after the first semicolon, else it is not the
            // same SQL statement
            if (selectRegion.getOffset() >= lastSemicolonRegion.getOffset()
                    && endSemicolonRegion.getOffset() >= selectRegion.getOffset()) {
                DMLScanner scanner = new DMLScanner(parser);
                scanner.setRange(doc, selectRegion.getOffset(),
                        endSemicolonRegion.getOffset() - selectRegion.getOffset());
                IToken token = scanner.nextToken();
                DMLParseResult result = new DMLParseResult();
                Stack<DMLParseResult> stack = new Stack<DMLParseResult>();
                Map<Segment, DMLParseResult> results = new HashMap<Segment, DMLParseResult>();
                while (!token.isEOF()) {
                    // Counting parenthethis
                    if (token == DMLScanner.LEFTPAR_TOKEN) {
                        result.parCount++;
                    } else if (token == DMLScanner.RIGHTPAR_TOKEN) {
                        result.parCount--;
                    }

                    if (token == DMLScanner.SELECT_TOKEN) { // && (result.tableSegStart>0 ||
                        // result.whereSegStart>0)) {
                        stack.push(result);
                        result = new DMLParseResult();
                        result.stackStart = scanner.getTokenOffset();
                    } else if (token == DMLScanner.RIGHTPAR_TOKEN && result.parCount < 0) { // &&
                        // stack.size()>0)
                        // {
                        results.put(new Segment(result.stackStart, scanner.getTokenOffset() - result.stackStart),
                                result);
                        result = stack.pop();
                    } else if (token == DMLScanner.INSERT_TOKEN) {
                        result.ignoreInto = false;
                    } else if (token == DMLScanner.FROM_TOKEN || token == DMLScanner.UPDATE_TOKEN
                            || (token == DMLScanner.INTO_TOKEN && !result.ignoreInto)) {
                        result.ignoreInto = true;
                        // We have a table segment start
                        result.tableSegStart = scanner.getTokenOffset();
                        result.tableStartToken = token;
                    } else if (token == DMLScanner.WORD_TOKEN && result.tableSegStart > 0) {
                        // We are in a table segment so we instantiate appropriate table references
                        // and aliases
                        // in the parse result
                        if (result.lastAlias == null) {
                            // This is a new table definition, we add it
                            result.lastAlias = new TableAlias(
                                    doc.get(scanner.getTokenOffset(), scanner.getTokenLength()).toUpperCase());
                            result.lastAlias.setTable(tablesMap.get(result.lastAlias.getTableName()));
                            result.addFromTable(result.lastAlias);
                        } else if (result.lastAlias.getTableAlias() == null) {
                            // This is an alias of a defined table
                            final String alias = doc.get(scanner.getTokenOffset(), scanner.getTokenLength());
                            final List<String> reservedWords = parser.getTypedTokens().get(ISQLParser.DML);
                            if (!reservedWords.contains(alias.toUpperCase())) {
                                result.lastAlias.setAlias(alias);
                            } else {
                                result.lastAlias = null;
                            }
                        }
                    } else if (token == DMLScanner.COMMA_TOKEN) {
                        // On a comma, we reset any table reference
                        result.lastAlias = null;
                    } else if (token == DMLScanner.DML_TOKEN) {
                        result.lastAlias = null;
                        if (result.tableSegStart != -1) {
                            int tableSegEnd = scanner.getTokenOffset();
                            result.addTableSegment(
                                    new Segment(result.tableSegStart, tableSegEnd - result.tableSegStart));
                            result.tableSegStart = -1;
                        }
                    } else if (result.tableSegStart != -1
                            && ((result.tableStartToken == DMLScanner.FROM_TOKEN && token == DMLScanner.WHERE_TOKEN)
                                    || (result.tableStartToken == DMLScanner.UPDATE_TOKEN
                                            && token == DMLScanner.SET_TOKEN)
                                    || (result.tableStartToken == DMLScanner.INTO_TOKEN
                                            && token == DMLScanner.LEFTPAR_TOKEN))) {
                        // We have matched a table segment end, so we close the segment
                        // and we add it to the parse result's table segments
                        int tableSegEnd = scanner.getTokenOffset();
                        result.addTableSegment(
                                new Segment(result.tableSegStart, tableSegEnd - result.tableSegStart));
                        result.tableSegStart = -1;
                        if (token == DMLScanner.WHERE_TOKEN) {
                            result.whereSegStart = scanner.getTokenOffset() + scanner.getTokenLength();
                        }
                    }
                    token = scanner.nextToken();
                }
                // If the table segment is still opened, we close it at the end of the SQL statement
                if (result.tableSegStart > -1) {
                    int tableSegEnd = endSemicolonRegion.getOffset();
                    result.addTableSegment(
                            new Segment(result.tableSegStart, tableSegEnd - result.tableSegStart + 1));
                }
                // Locating the appropriate result
                for (Segment s : results.keySet()) {
                    if (s.getOffset() <= start && s.getOffset() + s.getLength() > start) {
                        return results.get(s);
                    }
                }
                return result;
            }
        } catch (BadLocationException e) {
            LOGGER.debug("Problems while retrieving SQL statement");
        }
        return null;
    }

}