/******************************************************************************* * Copyright © 2011, 2013 IBM Corporation 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: * IBM Corporation - initial API and implementation * *******************************************************************************/ package org.eclipse.edt.ide.ui.internal.quickfix.proposals; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.ResourceBundle; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.edt.compiler.core.ast.AddStatement; import org.eclipse.edt.compiler.core.ast.ArrayAccess; import org.eclipse.edt.compiler.core.ast.DeleteStatement; import org.eclipse.edt.compiler.core.ast.Expression; import org.eclipse.edt.compiler.core.ast.GetByKeyStatement; import org.eclipse.edt.compiler.core.ast.Name; import org.eclipse.edt.compiler.core.ast.NestedFunction; import org.eclipse.edt.compiler.core.ast.Node; import org.eclipse.edt.compiler.core.ast.OpenStatement; import org.eclipse.edt.compiler.core.ast.Part; import org.eclipse.edt.compiler.core.ast.ReplaceStatement; import org.eclipse.edt.compiler.core.ast.Statement; import org.eclipse.edt.compiler.core.ast.UsingKeysClause; import org.eclipse.edt.compiler.internal.IEGLConstants; import org.eclipse.edt.compiler.internal.util.EGLMessage; import org.eclipse.edt.ide.core.Logger; import org.eclipse.edt.ide.core.internal.compiler.workingcopy.IWorkingCopyCompileRequestor; import org.eclipse.edt.ide.core.internal.compiler.workingcopy.WorkingCopyCompilationResult; import org.eclipse.edt.ide.core.internal.compiler.workingcopy.WorkingCopyCompiler; import org.eclipse.edt.ide.core.model.EGLCore; import org.eclipse.edt.ide.core.model.IEGLFile; import org.eclipse.edt.ide.core.model.IWorkingCopy; import org.eclipse.edt.ide.core.model.document.IEGLDocument; import org.eclipse.edt.ide.sql.SQLConstants; import org.eclipse.edt.ide.ui.internal.EGLUI; import org.eclipse.edt.ide.ui.internal.EGLUIMessageKeys; import org.eclipse.edt.ide.ui.internal.UINlsStrings; import org.eclipse.edt.ide.ui.internal.editor.CodeConstants; import org.eclipse.edt.ide.ui.internal.editor.EGLEditor; import org.eclipse.edt.ide.ui.internal.editor.EditorUtility; import org.eclipse.edt.ide.ui.internal.editor.sql.SQLIOStatementActionInfo; import org.eclipse.edt.ide.ui.internal.editor.sql.SQLIOStatementUtility; import org.eclipse.edt.ide.ui.internal.quickfix.IInvocationContext; import org.eclipse.edt.ide.ui.internal.quickfix.proposals.sql.EGLSQLAddStatementFactory; import org.eclipse.edt.ide.ui.internal.quickfix.proposals.sql.EGLSQLDeclareStatementFactory; import org.eclipse.edt.ide.ui.internal.quickfix.proposals.sql.EGLSQLDeleteStatementFactory; import org.eclipse.edt.ide.ui.internal.quickfix.proposals.sql.EGLSQLGetByKeyForUpdateStatementFactory; import org.eclipse.edt.ide.ui.internal.quickfix.proposals.sql.EGLSQLGetByKeyStatementFactory; import org.eclipse.edt.ide.ui.internal.quickfix.proposals.sql.EGLSQLOpenForUpdateStatementFactory; import org.eclipse.edt.ide.ui.internal.quickfix.proposals.sql.EGLSQLOpenStatementFactory; import org.eclipse.edt.ide.ui.internal.quickfix.proposals.sql.EGLSQLReplaceStatementFactory; import org.eclipse.edt.ide.ui.internal.quickfix.proposals.sql.EGLSQLStatementFactory; import org.eclipse.edt.mof.egl.Field; import org.eclipse.edt.mof.egl.Member; import org.eclipse.edt.mof.egl.Type; import org.eclipse.edt.mof.eglx.persistence.sql.ext.Utils; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IRegion; import org.eclipse.swt.graphics.Image; public abstract class AbstractSQLStatementProposal extends ASTRewriteCorrectionProposal { protected EGLEditor editor; protected String sqlStatement; protected String intoClause; protected String usingClause; boolean isExecuteStatement; boolean isForUpdateStatement; protected boolean addIntoClause; boolean actionFailed; boolean hasDynamicArrayError; List<String> messages; protected SQLIOStatementActionInfo info; Member sqlRecordBinding; /** * Any code referencing the bindings MUST be handled from within this method, you must not store the bound node for * later processing. From the WorkingCopyCompiler documentation: * "The bound node that is returned to the requestor is only valid for the life of the requestor call. The binding should NOT be cached." */ public static interface IBoundNodeProcessor { void processBoundNode(Node boundNode, Node containerNode); } public AbstractSQLStatementProposal(String name, IEGLFile eglFile, int relevance, Image image, IEGLDocument document) { super(name, eglFile, relevance, image, document); } protected void initialize() { messages = null; sqlStatement = null; usingClause = null; intoClause = null; actionFailed = false; hasDynamicArrayError = false; } protected List<String> getMessages() { if (messages == null) { messages = new ArrayList<String>(); } return messages; } protected EGLSQLStatementFactory createSQLStatementFactory(Statement statement) { isExecuteStatement = false; isForUpdateStatement = false; EGLSQLStatementFactory statementFactory = null; boolean isSelectStatement = false; Member sqlRecordBinding = null; // Create the right type of factory to create the default SQL statement. sqlRecordBinding = getSqlRecordBinding(); if (sqlRecordBinding == null) { actionFailed = true; if (!hasDynamicArrayError) { addErrorMessage(EGLUIMessageKeys.SQL_DLI_MESSAGE_ERROR_RECORD_PART_REQUIRED, new String[] {info.getIOType(), SQLConstants.SQL}); } return null; } if (statement instanceof AddStatement) { statementFactory = new EGLSQLAddStatementFactory(sqlRecordBinding, info.getIOObjectName()); } else if (statement instanceof DeleteStatement) { String[][] keyItemAndColumnNames = getKeyItemAndColumnNames(sqlRecordBinding); statementFactory = new EGLSQLDeleteStatementFactory(sqlRecordBinding, info.getIOObjectName(), keyItemAndColumnNames, info.isNoCursor()); } else if (info.getStatement() instanceof GetByKeyStatement) { String[][] keyItemAndColumnNames = getKeyItemAndColumnNames(sqlRecordBinding); if (isGetForUpdateStatement()) { statementFactory = new EGLSQLGetByKeyForUpdateStatementFactory( sqlRecordBinding, info.getIOObjectName(), null, keyItemAndColumnNames, false); isForUpdateStatement = true; } else { statementFactory = new EGLSQLGetByKeyStatementFactory( sqlRecordBinding, info.getIOObjectName(), null, keyItemAndColumnNames, info.isDynamicArrayRecord()); } isSelectStatement = true; } else if (info.getStatement() instanceof OpenStatement) { String[][] keyItemAndColumnNames = getKeyItemAndColumnNames(sqlRecordBinding); if (isOpenForUpdateStatement()) { statementFactory = new EGLSQLOpenForUpdateStatementFactory(sqlRecordBinding, info.getIOObjectName(), null, keyItemAndColumnNames); isForUpdateStatement = true; } else { statementFactory = new EGLSQLOpenStatementFactory(sqlRecordBinding, info.getIOObjectName(), null, keyItemAndColumnNames); } isSelectStatement = true; } else if (statement instanceof ReplaceStatement) { String[][] keyItemAndColumnNames = getKeyItemAndColumnNames(sqlRecordBinding); statementFactory = new EGLSQLReplaceStatementFactory(sqlRecordBinding, info.getIOObjectName(), keyItemAndColumnNames, info.isNoCursor()); } if (statementFactory != null && isSelectStatement) { EGLSQLDeclareStatementFactory factory = ((EGLSQLDeclareStatementFactory) statementFactory); if (addIntoClause) { factory.setBuildIntoClauseForEditor(true); factory.setAddIntoClauseToStatement(false); } else { factory.setBuildIntoClause(false); } } return statementFactory; } protected Member getSqlRecordBinding() { sqlRecordBinding = info.getSqlRecordBinding(); if (sqlRecordBinding != null) { return sqlRecordBinding; } /*if (SQLEditorUtility.saveEditors()) { sqlRecordBinding = searchForPart(); info.setSqlRecordBinding(sqlRecordBinding); return sqlRecordBinding; }*/ return null; } protected void addErrorMessage(String messageID) { getMessages().add(EGLMessage.createEGLEditorErrorMessage(getResourceBundle(), messageID).getBuiltMessage()); } protected void addErrorMessage(String messageID, String[] inserts) { getMessages().add(EGLMessage.createEGLEditorErrorMessage(getResourceBundle(), messageID, inserts).getBuiltMessage()); } protected void addInfoMessage(String messageID) { getMessages().add(EGLMessage.createEGLEditorInformationalMessage(getResourceBundle(), messageID).getBuiltMessage()); } protected void addInfoMessage(String messageID, String[] inserts) { getMessages().add(EGLMessage.createEGLEditorInformationalMessage(getResourceBundle(), messageID, inserts).getBuiltMessage()); } private ResourceBundle getResourceBundle() { return UINlsStrings.getResourceBundleForConstructedKeys(); } protected boolean hasEGLSQLStatementErrors() { boolean isValid = true; if (info.getStatement() == null) { addErrorMessage(EGLUIMessageKeys.SQL_MESSAGE_ERROR_SQL_STATEMENT_ACTIONS_SUPPORTED_ONLY_FOR_EGL_SQL_STATEMENTS); addInfoMessage(EGLUIMessageKeys.SQL_DLI_MESSAGE_INFO_EGL_STATEMENT_MUST_BE_SYNTACTICALLY_CORRECT, new String[] { SQLConstants.SQL }); isValid = false; } return isValid; } protected boolean ensureSQLStatementIsNotCloseOrDelete() { if (isCloseStatement()) { addErrorMessage(EGLUIMessageKeys.SQL_MESSAGE_ERROR_ONLY_VIEW_SUPPORTED_FOR_CLOSE_STATEMENT); return false; } return true; } protected boolean ensureExplicitSQLStatementAllowed() { if (isExplicitSQLStatementAllowed()) { return true; } if (shouldIssueExplicitSQLStatementNotAllowedMessage()) { addErrorMessage(EGLUIMessageKeys.SQL_MESSAGE_ERROR_SQL_STATEMENT_NOT_ALLOWED_FOR_EGL_STATEMENT, new String[] { info.getIOType() }); } else { return true; } return false; } protected boolean ensureIntoClauseNotSpecified() { if (info.isDynamicArrayRecord() && isGetStatement() && info.getIntoClauseNode() != null) { addErrorMessage(EGLUIMessageKeys.SQL_MESSAGE_ERROR_INTO_CLAUSE_NOT_ALLOWED_FOR_DYNAMIC_ARRAYS); return false; } return true; } protected boolean ensureSQLRecordVariableIsSpecified() { if (isSQLRecordVariableSpecified()) { return true; } addErrorMessage(EGLUIMessageKeys.SQL_MESSAGE_ERROR_SQL_RECORD_VARIABLE_NOT_SPECIFIED_ON_STATEMENT, new String[] { info.getIOType() }); return false; } public boolean isAddIntoClause() { return addIntoClause; } protected boolean isAddStatement() { if (info.getIOType().equalsIgnoreCase(SQLConstants.ADD_IO_TYPE)) { return true; } return false; } protected boolean isCloseStatement() { if (info.getIOType().equalsIgnoreCase(SQLConstants.CLOSE_IO_TYPE)) { return true; } return false; } protected boolean isGetStatement() { if (info.getIOType().equalsIgnoreCase(SQLConstants.GET_IO_TYPE)) { return true; } return false; } protected boolean isGetByPositionStatement() { if (info.getIOType().equalsIgnoreCase(SQLConstants.GET_BY_POSITION_IO_TYPE)) { return true; } return false; } protected boolean isGetForUpdateStatement() { if (info.getIOType().equalsIgnoreCase(SQLConstants.GET_FORUPDATE_IO_TYPE)) { return true; } return false; } protected boolean isOpenForUpdateStatement() { if (info.getIOType().equalsIgnoreCase(SQLConstants.OPEN_FORUPDATE_IO_TYPE)) { return true; } return false; } private boolean isDynamicArraySupported() { return isGetStatement(); } protected boolean isSQLStatementSpecified() { return info.getSqlStatement() != null; } protected boolean isPreparedStatementReferenceSpecified() { return info.hasPreparedStatementReference(); } protected boolean isExplicitSQLStatementAllowed() { if (isCloseStatement() || isGetByPositionStatement()) { return false; } return true; } protected boolean isSQLRecordVariableSpecified() { boolean isSpecified = !info.getStatement().getIOObjects().isEmpty(); if(!isSpecified && info.getStatement() instanceof DeleteStatement) { isSpecified = (((DeleteStatement)info.getStatement()).getDataSource().getExpression() != null); } return isSpecified; } protected Member getSQLRecordBindingFromTarget() { List exprList = info.getStatement().getIOObjects(); Expression recordVar; if (exprList.isEmpty()) { if(info.getStatement() instanceof DeleteStatement) { DeleteStatement deleteStatement = (DeleteStatement)info.getStatement(); recordVar = deleteStatement.getDataSource().getExpression(); } else { return null; } } else { recordVar = (Expression) exprList.get(0); } if (recordVar instanceof ArrayAccess) { info.setDynamicArrayRecord(true); } info.setSqlRecordVariable(recordVar); info.setIOObjectName(recordVar.getCanonicalString()); Member dataBinding = recordVar.resolveMember(); Type typeBinding = recordVar.resolveType(); if(dataBinding != null && SQLIOStatementUtility.isEntityRecord(typeBinding)){ info.setIOObjectName(dataBinding.getName()); return dataBinding; } else if (dataBinding != null && SQLIOStatementUtility.isBasicRecord(typeBinding)){ info.setIOObjectName(dataBinding.getName()); return dataBinding; } return null; } protected boolean ensureSQLStatementIsNotSpecified() { if (isSQLStatementSpecified()) { addErrorMessage(EGLUIMessageKeys.SQL_DLI_MESSAGE_ERROR_STATEMENT_ALREADY_SPECIFIED, new String[] { info.getIOType(), SQLConstants.SQL }); return false; } return true; } protected boolean ensurePreparedStatementReferenceIsNotSpecified() { if (isPreparedStatementReferenceSpecified()) { addErrorMessage(EGLUIMessageKeys.SQL_MESSAGE_ERROR_SQL_STATEMENT_NOT_ALLOWED_WITH_PREPARED_STATEMENT_REFERENCE); return false; } return true; } protected boolean ensureDynamicArrayAllowed() { if (info.isDynamicArrayRecord() && !isDynamicArraySupported()) { if (isAddStatement() && shouldIssueExplicitSQLStatementNotAllowedMessage()) { hasDynamicArrayError = issueExplicitSQLStatementNotAllowedWithDynamicArray(); } else { addErrorMessage(EGLUIMessageKeys.SQL_MESSAGE_ERROR_DYNAMIC_RECORD_NOT_ALLOWED, new String[] { info.getIOType()}); hasDynamicArrayError = true; } if (hasDynamicArrayError) { return false; } } return true; } protected boolean shouldIssueExplicitSQLStatementNotAllowedMessage() { if (isGetByPositionStatement()) { return false; } return true; } protected boolean issueExplicitSQLStatementNotAllowedWithDynamicArray() { addErrorMessage(EGLUIMessageKeys.SQL_MESSAGE_ERROR_SQL_STATEMENT_NOT_ALLOWED_FOR_ADD_STATEMENT_WITH_DYNAMIC_ARRAY); return true; } protected void bindASTNode(IEGLDocument document, String name, int offset, IFile file, IBoundNodeProcessor boundNodeProcessor) { IProject proj = file.getProject(); String pkgName = EditorUtility.getPackageName(file); //bind the ast tree with live env and scope IWorkingCopy[] currRegedWCs = EGLCore.getSharedWorkingCopies(EGLUI.getBufferFactory()); IWorkingCopyCompileRequestor requestor = new BoundNodeWorkingCopyCompileRequestor(document, offset, file, boundNodeProcessor); if (name != null) { WorkingCopyCompiler.getInstance().compilePart(proj, pkgName, file, currRegedWCs, name, requestor); } else { WorkingCopyCompiler.getInstance().compileAllParts(proj, pkgName, file, currRegedWCs, requestor); } } public class BoundNodeWorkingCopyCompileRequestor implements IWorkingCopyCompileRequestor { IEGLDocument document; int documentOffset; IBoundNodeProcessor boundNodeProcessor; protected BoundNodeWorkingCopyCompileRequestor(IEGLDocument document, int documentOffset, IFile file, IBoundNodeProcessor boundNodeProcessor) { super(); this.document = document; this.documentOffset = documentOffset; this.boundNodeProcessor = boundNodeProcessor; } public void acceptResult(WorkingCopyCompilationResult result) { Node nodeAtOffset = document.getNewModelNodeAtOffset(documentOffset, result.getBoundPart()); if(nodeAtOffset != null) { boundNodeProcessor.processBoundNode(nodeAtOffset, result.getBoundPart()); } } } protected String getStatementText() { StringBuffer buffer = new StringBuffer(); String indentString = determineIndentString() + SQLConstants.TAB; if (addIntoClause && intoClause != null) { addIntoClause(buffer, indentString); } addSQLStatement(buffer, indentString); if (isExecuteStatement) { buffer.append(" "); //$NON-NLS-1$ } return buffer.toString(); } private void addSQLStatement(StringBuffer buffer, String indentString) { if (info.isShouldAddSQLStatement() || isResetAction()) { if (sqlStatement == null) { return; } else { sqlStatement = trimTrailingCRLF(sqlStatement); } if (!isExecuteStatement) { buffer.append(" "); //$NON-NLS-1$ if(usingClause != null) { usingClause = trimTrailingCRLF(usingClause); buffer.append(usingClause); buffer.append(" "); //$NON-NLS-1$ } buffer.append(IEGLConstants.KEYWORD_WITH); } if (!(isExecuteStatement && isResetAction())) { buffer.append(SQLConstants.CRLF); buffer.append(indentString); } else { buffer.append(SQLConstants.TAB); } buffer.append(CodeConstants.EGL_SQL_PARTITION_START); buffer.append(SQLConstants.CRLF); // Need to break up the statement into separate lines to add the // indentation to the beginning of each line List lines = getLineList(sqlStatement); for (Iterator iter = lines.iterator(); iter.hasNext();) { String line = (String) iter.next(); buffer.append(indentString); buffer.append(SQLConstants.TAB); buffer.append(line); } buffer.append(SQLConstants.CRLF); buffer.append(indentString); buffer.append(CodeConstants.EGL_SQL_PARTITION_END); } } private String trimTrailingCRLF(String str) { int index = str.length(); if (index > 2 && str.endsWith(SQLConstants.CRLF)) { return str.substring(0, index - 2); } else { return str; } } private String determineIndentString() { int position = ((Node) info.getStatement()).getOffset(); String line = getLineText(position); int index = line.indexOf(line.trim()); return line.substring(0, index); } private String getLineText(int position) { String text = ""; //$NON-NLS-1$ try { IRegion region = info.getDocument().getLineInformationOfOffset(position); text = info.getDocument().get(region.getOffset(), region.getLength()); } catch (BadLocationException e) { Logger.log(this, e); } return text; } private void addIntoClause(StringBuffer buffer, String indentString) { // Need to break up the clause into separate lines to add the // indentation to the beginning of each line if (isResetAction()) { buffer.append(SQLConstants.TAB); } else { buffer.append(SQLConstants.CRLF); buffer.append(indentString); } List lines = getLineList(intoClause); for (Iterator iter = lines.iterator(); iter.hasNext();) { String line = (String) iter.next(); buffer.append(line); if (iter.hasNext()) { buffer.append(indentString); } } } protected boolean isResetAction() { return false; } private List getLineList(String string) { ArrayList lines = new ArrayList(); String workingString = string; int index; String line; while (workingString != null && workingString.length() > 0) { index = workingString.indexOf(SQLConstants.CRLF); if (index > 0) { line = workingString.substring(0, index + 2); workingString = workingString.substring(index + 2); } else { line = workingString; workingString = ""; //$NON-NLS-1$ } lines.add(line); } return lines; } protected String[][] getKeyItemAndColumnNames(Member sqlRecordBinding) { if (info.getUsingKeysNode() != null) { return getKeyItemAndColumnNames((UsingKeysClause)info.getUsingKeysNode(), sqlRecordBinding); } return null; } private String[][] getKeyItemAndColumnNames(UsingKeysClause keyClause, Member sqlRecordBinding) { String[][] keyItemAndColumnNames = null; List usingKeys = keyClause.getExpressions(); if (!usingKeys.isEmpty()) { keyItemAndColumnNames = new String[usingKeys.size()][2]; Member itemBinding; String columnName; for (int i = 0; i < usingKeys.size(); i++) { columnName = null; Expression key = (Expression) usingKeys.get(i); keyItemAndColumnNames[i][0] = key.getCanonicalString(); itemBinding = ((Expression) usingKeys.get(i)).resolveMember(); if (itemBinding instanceof Field) { columnName = Utils.getColumnName((Field)itemBinding); } if (columnName == null) { if (key.isName()) { columnName = ((Name) key).getIdentifier(); } else { columnName = key.getCanonicalString(); } } keyItemAndColumnNames[i][1] = columnName; } } return keyItemAndColumnNames; } public static Statement SQLStatementFinder(IInvocationContext context){ Node astNode = context.getCoveringNode(); if(null != astNode){ if(astNode instanceof Statement){ return (Statement)astNode; } } while(!(astNode instanceof NestedFunction || astNode instanceof Part)){ astNode = astNode.getParent(); if(astNode instanceof Statement){ return (Statement)astNode; } } return null; } }