com.nextep.designer.sqlgen.oracle.parser.OraclePackageParser.java Source code

Java tutorial

Introduction

Here is the source code for com.nextep.designer.sqlgen.oracle.parser.OraclePackageParser.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.oracle.parser;

import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.rules.IToken;
import com.nextep.datadesigner.dbgm.impl.Datatype;
import com.nextep.datadesigner.dbgm.impl.ParseData;
import com.nextep.datadesigner.dbgm.impl.ProcedureParameter;
import com.nextep.datadesigner.dbgm.model.IDatatype;
import com.nextep.datadesigner.dbgm.model.IParseData;
import com.nextep.datadesigner.dbgm.model.IParseable;
import com.nextep.datadesigner.dbgm.model.IProcedure;
import com.nextep.datadesigner.dbgm.model.IProcedureParameter;
import com.nextep.datadesigner.dbgm.model.IVariable;
import com.nextep.datadesigner.dbgm.model.ParameterType;
import com.nextep.datadesigner.dbgm.model.Variable;
import com.nextep.datadesigner.sqlgen.impl.LightProcedure;
import com.nextep.datadesigner.sqlgen.model.IPackage;
import com.nextep.designer.dbgm.model.ITypedSqlParser;
import com.nextep.designer.dbgm.sql.TextPosition;

public class OraclePackageParser implements ITypedSqlParser {

    private final static Log log = LogFactory.getLog(OraclePackageParser.class);

    @Override
    public IParseData parse(IParseable p, String sql) {
        try {
            return internalParse(p, sql);
        } catch (BadLocationException e) {
            log.warn("Problems during parse", e);
            return null;
        }
    }

    private ParseData internalParse(IParseable p, String sql) throws BadLocationException {
        IDocument doc = createDocument(sql);
        final ParseData parseData = new ParseData();
        IPackage pkg = (IPackage) p;
        pkg.clearVariables();
        pkg.clearProcedures();
        // Reseting
        p.setParsed(false);
        // Parsing block
        PackageScanner pkgScanner = new PackageScanner();
        pkgScanner.setRange(doc, 0, doc.getLength());
        // Parsing procedures
        IToken token = pkgScanner.nextToken();
        int beginCount = 0;
        int procStartOffset = 0;
        /** A flag indicating a package has started */
        boolean packageStarted = false;
        boolean pendingPackage = false;
        /** A flag indicating a procedure declaration has started */
        boolean procedureDecl = false;
        // Procedure declaration parsing variables
        IProcedureParameter currentParam = null;
        boolean paramDefinition = false;
        boolean returnDefinition = false;
        /** The previous token */
        IToken previousToken = null;
        /** currently parsed procedure */
        IProcedure currentProc = null;
        /** Currently parsed variable */
        IVariable currentVar = null;
        /** Pending END */
        boolean pendingEnd = false;
        while (!token.isEOF()) {
            if (token == PackageScanner.PACKAGE_TOKEN) {
                pendingPackage = true;
            } else if (token == PackageScanner.PROC_ENDSPEC_TOKEN) {
                if (pendingPackage) {
                    packageStarted = true;
                    pendingPackage = false;
                }
                procedureDecl = false;
            } else if (token == PackageScanner.SEMICOLON_TOKEN && procedureDecl) {
                // Here we have a semicolon before the IS / AS => only a pre-declaration without
                // body
                procedureDecl = false;
                currentProc = null;

            } else if (procedureDecl) {
                if (token == PackageScanner.DECLSTART_TOKEN) {
                    paramDefinition = true;
                } else if (token == PackageScanner.DECLEND_TOKEN) {
                    currentProc.addParameter(currentParam);
                    currentParam = null;
                    paramDefinition = false;
                } else if (paramDefinition) {
                    if (token == PackageScanner.NEWPARAM_TOKEN) {
                        currentProc.addParameter(currentParam);
                        currentParam = null;
                    } else if (previousToken == PackageScanner.ASSIGN_TOKEN) {
                        currentParam
                                .setDefaultExpr(doc.get(pkgScanner.getTokenOffset(), pkgScanner.getTokenLength()));
                    } else if (token == PackageScanner.WORD_TOKEN) {
                        String word = doc.get(pkgScanner.getTokenOffset(), pkgScanner.getTokenLength());
                        if (currentParam == null) {
                            currentParam = new ProcedureParameter(word, null, null);
                        } else {
                            currentParam.setDatatype(new Datatype(word));
                        }
                    } else if (token.getData() instanceof ParameterType && currentParam != null) {
                        currentParam.setParameterType((ParameterType) token.getData());
                    }
                } else if (token == PackageScanner.RETURN_TOKEN && !paramDefinition) {
                    returnDefinition = true;
                } else if (returnDefinition && token == PackageScanner.WORD_TOKEN) {
                    // Here we are defining the return datatype
                    if (currentProc.getReturnType() == null) {
                        currentProc.setReturnType(
                                new Datatype(doc.get(pkgScanner.getTokenOffset(), pkgScanner.getTokenLength())));
                    } else {
                        IDatatype d = currentProc.getReturnType();
                        d.setName(d.getName() + " " //$NON-NLS-1$
                                + doc.get(pkgScanner.getTokenOffset(), pkgScanner.getTokenLength()));
                    }
                }

            } else if (packageStarted && token == PackageScanner.WORD_TOKEN) {
                // If we have a pending 'END' tag, we only check the word to
                // know if it is a closure or a 'end if' like tag.
                // END will always be closed by the SEMICOLON rule
                if (pendingEnd) {
                    String word = doc.get(pkgScanner.getTokenOffset(), pkgScanner.getTokenLength());
                    if (currentProc != null) {
                        if (!word.equals(currentProc.getName())) {
                            pendingEnd = false;
                        }
                    } else {
                        if (!word.equals(pkg.getName())) {
                            pendingEnd = false;
                        }
                    }
                    // If we have started a proc we initialize it
                } else if (previousToken == PackageScanner.PROC_TOKEN) {
                    // We got the proc / func name
                    currentProc = new LightProcedure(
                            doc.get(pkgScanner.getTokenOffset(), pkgScanner.getTokenLength()), null);
                    // Adding procedure
                    currentProc.setParent(pkg);
                    pkg.addProcedure(currentProc);
                    // Starting declaration
                    procedureDecl = true;
                    currentParam = null;
                } else if (currentVar == null && beginCount == 0) {
                    currentVar = new Variable(doc.get(pkgScanner.getTokenOffset(), pkgScanner.getTokenLength()));
                    if (currentProc == null) {
                        pkg.addVariable(currentVar);
                    } else {
                        currentProc.addVariable(currentVar);
                    }
                } else if (beginCount == 0 && currentVar.getDatatypeName() == null) {
                    currentVar.setDatatypeName(doc.get(pkgScanner.getTokenOffset(), pkgScanner.getTokenLength()));
                }
            } else if (packageStarted && token == PackageScanner.SEMICOLON_TOKEN) {
                if (currentVar != null) {
                    parseData.setPosition(currentVar,
                            new TextPosition(pkgScanner.getTokenOffset() - currentVar.getName().length(),
                                    currentVar.getName().length()));
                }
                currentVar = null;
                if (pendingEnd) {
                    beginCount--;
                    if (beginCount <= 0 && currentProc != null) {
                        // Setting parse data
                        parseData.setPosition(currentProc, new TextPosition(procStartOffset,
                                pkgScanner.getTokenOffset() + pkgScanner.getTokenLength() - procStartOffset));
                        // Resetting proc
                        currentProc = null;
                    }
                    pendingEnd = false;
                }
            } else if (token == PackageScanner.PROC_TOKEN) {
                // Nothing, check is made against previous token in a word token
                procStartOffset = pkgScanner.getTokenOffset();
            } else if (token == PackageScanner.BEGIN_TOKEN) {
                beginCount++;
            } else if (token == PackageScanner.END_TOKEN) {
                pendingEnd = true;
            }
            // Always set our previous token and switch to next one
            previousToken = token.isUndefined() || token.isWhitespace() ? previousToken : token;
            token = pkgScanner.nextToken();
        }
        return parseData;
    }

    protected IDocument createDocument(String parsedString) {
        IDocument document = new Document();
        if (parsedString != null) {
            document.set(parsedString.toUpperCase());
        } else {
            document.set(""); //$NON-NLS-1$
        }
        return document;
    }

    @Override
    public String parseName(String sql) {
        String tag = null;
        if (isBody(sql)) {
            tag = "body"; //$NON-NLS-1$
        } else {
            tag = "package"; //$NON-NLS-1$
        }
        // Extracting procedure or function name from the SQL source
        Pattern pattern = Pattern.compile("\\s*(" + tag + ")\\s+((\\w)+)"); //$NON-NLS-1$ //$NON-NLS-2$ 
        Matcher m = pattern.matcher(sql.toLowerCase());
        // Looking for first occurrence
        String parsedName = null;
        if (m.find()) {
            parsedName = sql.substring(m.start(2), m.end(2));
        }
        return parsedName;
    }

    @Override
    public String rename(String sqlToRename, String newName) {
        // Detecting whether we have a spec or a body
        String tag = null;
        if (isBody(sqlToRename)) {
            tag = "body"; //$NON-NLS-1$
        } else {
            tag = "package"; //$NON-NLS-1$
        }
        String renamed = renameSqlHeader(sqlToRename, newName, tag);
        renamed = renameSqlEnd(renamed, newName);
        return renamed;
    }

    /**
     * Detects whether the supplied SQL string corresponds to the body part or to the spec part
     * 
     * @param sql sql to recognize
     * @return <code>true</code> if this is a body SQL source, <code>false</code> for spec sql
     *         source
     */
    private boolean isBody(String sql) {
        final Pattern pattern = Pattern.compile("package(\\s)+body(\\s)+");
        final Matcher m = pattern.matcher(sql.toLowerCase());
        return m.find();
        //      return Pattern.matches("package(\\s)+body(\\s)+", sql.toLowerCase()); //$NON-NLS-1$
    }

    @Override
    public void rename(IParseable parseable, String newName) {
        final IPackage pkg = (IPackage) parseable;

        // Renaming SPEC HEADER package declaration
        String renamedSpec = renameSqlHeader(pkg.getSpecSourceCode(), newName, "package"); //$NON-NLS-1$
        // Renaming SPEC END package declaration
        renamedSpec = renameSqlEnd(renamedSpec, newName);
        pkg.setSpecSourceCode(renamedSpec);
        // Renaming BODY HEADER package declaration
        String renamedBody = renameSqlHeader(pkg.getBodySourceCode(), newName, "body"); //$NON-NLS-1$
        // Renaming BODY END package declaration
        renamedBody = renameSqlEnd(renamedBody, newName);
        pkg.setBodySourceCode(renamedBody);
    }

    /**
     * Renames the provided SQL string by the new name considering that the name will follow the
     * supplied token
     * 
     * @param sql entire SQL source to rename
     * @param newName new name to inject in the SQL source
     * @param firstTokenBeforeName first token before the name information
     * @return the full SQL, renamed
     */
    private String renameSqlHeader(String sql, String newName, String firstTokenBeforeName) {
        // Extracting procedure or function name from the SQL source
        Pattern pattern = Pattern.compile("\\s*(" + firstTokenBeforeName + ")\\s+((\\w)+)"); //$NON-NLS-1$ //$NON-NLS-2$
        String lowerCased = sql.toLowerCase();
        Matcher m = pattern.matcher(lowerCased);
        String newSql = sql;
        // Looking for first occurrence
        if (m.find()) {
            // Building the "renamed" SQL source declaration
            newSql = sql.substring(0, m.start(2)) + newName + sql.substring(m.end(2));
        }
        return newSql;
    }

    private String renameSqlEnd(String sql, String newName) {
        // Matching the END tag
        final Pattern pattern = Pattern.compile("end\\s+((\\w)+)(;|/|\\s)*$"); //$NON-NLS-1$
        final Matcher m = pattern.matcher(sql.toLowerCase());
        String newSql = sql;
        if (m.find()) {
            newSql = sql.substring(0, m.start(1)) + newName + sql.substring(m.end(1));
        }
        return newSql;
    }
}