PLSQL/Annotation/src/org/netbeans/modules/plsql/annotation/PlsqlMethodAnnotationUtil.java
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4 * Copyright 2011 Oracle and/or its affiliates. All rights reserved.
6 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
7 * Other names may be trademarks of their respective owners.
9 * The contents of this file are subject to the terms of either the GNU
10 * General Public License Version 2 only ("GPL") or the Common
11 * Development and Distribution License("CDDL") (collectively, the
12 * "License"). You may not use this file except in compliance with the
13 * License. You can obtain a copy of the License at
14 * http://www.netbeans.org/cddl-gplv2.html
15 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
16 * specific language governing permissions and limitations under the
17 * License. When distributing the software, include this License Header
18 * Notice in each file and include the License file at
19 * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
20 * particular file as subject to the "Classpath" exception as provided
21 * by Oracle in the GPL Version 2 section of the License file that
22 * accompanied this code. If applicable, add the following below the
23 * License Header, with the fields enclosed by brackets [] replaced by
24 * your own identifying information:
25 * "Portions Copyrighted [year] [name of copyright owner]"
27 * If you wish your version of this file to be governed by only the CDDL
28 * or only the GPL Version 2, indicate your decision by adding
29 * "[Contributor] elects to include this software in this distribution
30 * under the [CDDL or GPL Version 2] license." If you do not indicate a
31 * single choice of license, a recipient has the option to distribute
32 * your version of this file under either the CDDL, the GPL Version 2 or
33 * to extend the choice of license to its licensees as provided above.
34 * However, if you add GPL Version 2 code and therefore, elected the GPL
35 * Version 2 license, then the option applies only if the new code is
36 * made subject to such option by the copyright holder.
40 * Portions Copyrighted 2011 Sun Microsystems, Inc.
42 package org.netbeans.modules.plsql.annotation;
44 import org.netbeans.modules.plsql.lexer.PlsqlBlock;
45 import org.netbeans.modules.plsql.lexer.PlsqlBlockType;
46 import org.netbeans.modules.plsql.lexer.PlsqlTokenId;
47 import org.netbeans.modules.plsql.utilities.PlsqlParserUtil;
48 import java.util.ArrayList;
49 import java.util.Collections;
50 import java.util.Comparator;
51 import java.util.List;
52 import java.util.Locale;
53 import javax.swing.JOptionPane;
54 import javax.swing.text.BadLocationException;
55 import javax.swing.text.Document;
56 import org.netbeans.api.lexer.Token;
57 import org.netbeans.api.lexer.TokenHierarchy;
58 import org.netbeans.api.lexer.TokenSequence;
59 import org.netbeans.modules.editor.NbEditorUtilities;
60 import org.openide.windows.WindowManager;
63 * Util class for annotations added for methods
67 public class PlsqlMethodAnnotationUtil {
69 static int hasReturn = 0;
70 private static final int HAS_RETURNS = 1;
71 private static final int NO_RETURNS = 2;
72 private static Comparator<PlsqlBlock> comparator = new Comparator<PlsqlBlock>() {
74 public int compare(PlsqlBlock o1, PlsqlBlock o2) {
76 if (o1.getStartOffset() > -1 && o2.getStartOffset() > -1) {
77 o1pos = new Integer(o1.getStartOffset());
78 o2pos = new Integer(o2.getStartOffset());
80 o1pos = new Integer(o1.getEndOffset());
81 o2pos = new Integer(o2.getEndOffset());
83 return o1pos.compareTo(o2pos);
87 public static int getOffsetToInsert(final Document doc, final int startOffset, final int endOffset) {
88 final TokenHierarchy tokenHierarchy = TokenHierarchy.get(doc);
89 @SuppressWarnings("unchecked")
90 final TokenSequence<PlsqlTokenId> ts = tokenHierarchy.tokenSequence(PlsqlTokenId.language());
95 Token<PlsqlTokenId> token = ts.token();
96 while (ts.moveNext() && ts.offset() < endOffset) {
98 if ((token.id() == PlsqlTokenId.KEYWORD)
99 && (token.text().toString().equalsIgnoreCase("BEGIN"))) {
101 offset = ts.offset();
110 public static PlsqlBlock findMatchingMethod(final List<PlsqlBlock> blockHier, final Document source,
111 final Document dest, final PlsqlBlock sourceBlock) {
112 PlsqlBlock match = null;
113 final List<PlsqlBlock> matchList = new ArrayList<PlsqlBlock>();
114 PlsqlParserUtil.findMatchingDefBlocks(blockHier, sourceBlock.getName(), matchList);
116 //There are several methods with the same name. Have to check the signature
117 final List<String> usageParams = PlsqlParserUtil.fetchMethodDefParamTypes(source, sourceBlock.getStartOffset());
119 //Take PlsqlBlock one by one and compare the parameters
120 for (int x = 0; x < matchList.size(); x++) {
121 final PlsqlBlock block = matchList.get(x);
122 if (!(block.getParent() != null && sourceBlock.getParent() != null ? block.getParent().getName().equalsIgnoreCase(sourceBlock.getParent().getName()) : true)) {
123 continue; //If the parent block name is not the same this is not a match
126 final List<String> params = PlsqlParserUtil.fetchMethodDefParamTypes(dest, block.getStartOffset());
127 final int defaultNo = PlsqlParserUtil.fetchDefaultParams(dest, block.getStartOffset());
128 if (PlsqlParserUtil.compareParameters(usageParams, params, defaultNo)) {
137 public static String getMethodSpecification(final Document doc, final PlsqlBlock block) {
138 String methodSpec = "";
139 final TokenHierarchy tokenHierarchy = TokenHierarchy.get(doc);
140 @SuppressWarnings("unchecked")
141 final TokenSequence<PlsqlTokenId> ts = tokenHierarchy.tokenSequence(PlsqlTokenId.language());
144 ts.move(block.getStartOffset());
145 Token<PlsqlTokenId> token = ts.token();
146 while (ts.moveNext() && block.getEndOffset() > ts.offset()) {
148 if (token.text().toString().equalsIgnoreCase("IS")) {
151 methodSpec = methodSpec + token.toString();
154 return methodSpec.trim();
157 public static PlsqlBlock findMethod(final List<PlsqlBlock> specBlockHier, final String packageName, final String methodName) {
158 PlsqlBlock match = null;
159 if (!packageName.equals("")) {
160 PlsqlBlock packageBlock = getPackageBody(specBlockHier, PlsqlBlockType.PACKAGE_BODY, packageName);
161 if (packageBlock != null) {
162 for (int i = 0; i < packageBlock.getChildCount(); i++) {
163 final PlsqlBlock temp = packageBlock.getChildBlocks().get(i);
164 if (temp.getName().equals(methodName)) {
174 public static PlsqlBlock getPackageBody(final List<PlsqlBlock> specBlockHier, final PlsqlBlockType blockType, final String packageName) {
175 PlsqlBlock packageBlock = null;
176 for (int i = 0; i < specBlockHier.size(); i++) {
177 final PlsqlBlock temp = specBlockHier.get(i);
178 if (temp.getType() == blockType && temp.getName().equalsIgnoreCase(packageName)) {
186 public static int getOffsetToInsert(final Document doc, final List<PlsqlBlock> specBlockHier, final String packageName, final PlsqlBlock searchBlock, final int searchPlace) throws BadLocationException {
189 if (!packageName.equals("")) {
190 PlsqlBlock packageBlock = getPackageBody(specBlockHier, PlsqlBlockType.PACKAGE, packageName);
192 if (packageBlock != null) {
193 for (int i = 0; i < packageBlock.getChildCount(); i++) {
194 final PlsqlBlock temp = packageBlock.getChildBlocks().get(i);
195 if (!temp.getType().equals(PlsqlBlockType.COMMENT) && (temp.getType().equals(PlsqlBlockType.FUNCTION_DEF) || temp.getType().equals(PlsqlBlockType.PROCEDURE_DEF))) {
196 if (temp.getName().contains(searchBlock.getName())) {
197 if (searchPlace == -1) {
198 offset = temp.getStartOffset();
200 offset = packageBlock.getChildBlocks().get(i + 1).getStartOffset() - 1;
204 } else if (temp.getType().equals(PlsqlBlockType.COMMENT) && searchBlock.getType().equals(PlsqlBlockType.COMMENT)) {
206 //Get block content and check; comments can be merged to one comment block
207 final String text = doc.getText(temp.getStartOffset(), temp.getEndOffset() - temp.getStartOffset());
208 int index = text.indexOf(searchBlock.getName());
210 index = text.indexOf("\n", index);
212 index = text.indexOf("\n", index + 1);
214 offset = temp.getStartOffset() + index;
216 offset = temp.getEndOffset();
225 //If the comment is not found insert some where
227 offset = packageBlock.getChildBlocks().get(0).getEndOffset();
235 public static boolean changeParam(final Document doc, final int offset, final String methodName) {
236 if (PlsqlAnnotationUtil.isFileReadOnly(doc)) {
237 JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), "File is read-only", "Error", JOptionPane.ERROR_MESSAGE);
241 final TokenHierarchy tokenHierarchy = TokenHierarchy.get(doc);
242 @SuppressWarnings("unchecked")
243 final TokenSequence<PlsqlTokenId> ts = tokenHierarchy.tokenSequence(PlsqlTokenId.language());
248 Token<PlsqlTokenId> token = ts.token();
249 while (token.id() == PlsqlTokenId.WHITESPACE && ts.movePrevious()) {
252 //We have the token now
253 if (PlsqlFileAnnotationUtil.changeLineOfOffset(doc, ts.offset(), token.toString(), "'" + methodName + "'")) {
260 public static int isReturnExist(final Document doc, final PlsqlBlock block) {
262 boolean isMissing = isReturn(doc, block);
265 if (hasReturn == HAS_RETURNS) {
275 public static boolean isReturn(final Document doc, final PlsqlBlock block) {
276 boolean isReturn = false;
277 final List<PlsqlBlock> children = block.getChildBlocks();
278 Collections.sort(children, comparator);
280 int startOffset = findBlockImplStart(doc, block);
281 for (PlsqlBlock child : children) {
282 if (child.getType() != PlsqlBlockType.CURSOR
283 && child.getType() != PlsqlBlockType.CUSTOM_FOLD
284 && startOffset < child.getStartOffset()) {
285 if (!isReturnMissing(startOffset, child.getStartOffset(), doc, block, true)) {
290 startOffset = child.getEndOffset();
294 isReturn = checkReturnInChildren(children, doc);
297 //Return not complete in child blocks, check for default return at the end
298 //check for exception block, it is assumed that there will be no child blocks after EXCEPTION
299 if (children.size() > 0) {
300 startOffset = children.get(children.size() - 1).getEndOffset();
303 isReturn = !isReturnMissing(startOffset, block.getEndOffset(), doc, block, !isReturn);
308 private static int checkExceptionBlock(final Document doc, final PlsqlBlock block) {
310 int startOffset = findBlockImplStart(doc, block);
311 final TokenHierarchy tokenHierarchy = TokenHierarchy.get(doc);
312 @SuppressWarnings("unchecked")
313 final TokenSequence<PlsqlTokenId> ts = tokenHierarchy.tokenSequence(PlsqlTokenId.language());
316 ts.move(startOffset);
317 Token<PlsqlTokenId> token = ts.token();
318 while (ts.moveNext() && ts.offset() < block.getEndOffset()) {
320 if (token.toString().equalsIgnoreCase("EXCEPTION")) {
328 private static boolean checkReturnInChildren(final List<PlsqlBlock> children, final Document doc) {
329 boolean isConstrusctReturn = false;
331 for (PlsqlBlock child : children) {
332 if ((child.getType() == PlsqlBlockType.IF && child.getName().toUpperCase(Locale.ENGLISH).startsWith("IF"))
333 || (child.getType() == PlsqlBlockType.CASE && child.getName().toUpperCase(Locale.ENGLISH).startsWith("CASE"))) {
334 isConstrusctReturn = false;
335 isConstrusctReturn = isReturn(doc, child);
336 } else if ((child.getType() == PlsqlBlockType.IF && child.getName().toUpperCase(Locale.ENGLISH).startsWith("ELSIF"))
337 || (child.getType() == PlsqlBlockType.IF && child.getName().toUpperCase(Locale.ENGLISH).startsWith("WHEN"))) {
338 if (isConstrusctReturn) {
339 isConstrusctReturn = isReturn(doc, child);
341 } else if ((child.getType() == PlsqlBlockType.IF || child.getType() == PlsqlBlockType.CASE)
342 && child.getName().equalsIgnoreCase("ELSE")) {
343 if (isConstrusctReturn) {
344 isConstrusctReturn = isReturn(doc, child);
345 //If is returning and else if also returning
346 if (isConstrusctReturn) {
356 private static boolean isReturnMissing(final int startOffset, final int endOffset, final Document doc, PlsqlBlock block, boolean isReturnMissing) {
357 final TokenHierarchy tokenHierarchy = TokenHierarchy.get(doc);
358 @SuppressWarnings("unchecked")
359 final TokenSequence<PlsqlTokenId> ts = tokenHierarchy.tokenSequence(PlsqlTokenId.language());
362 ts.move(startOffset);
363 Token<PlsqlTokenId> token = ts.token();
364 boolean isException = false;
366 while (ts.moveNext() && ts.offset() < endOffset) {
369 if (token.toString().equalsIgnoreCase("RETURN")
370 || token.toString().equalsIgnoreCase("RAISE")) {
371 if (moveToReturnEnd(ts, endOffset, doc, block)) {
372 isReturnMissing = false;
373 hasReturn = HAS_RETURNS;
375 } else if (token.toString().equalsIgnoreCase("ERROR_SYS")
376 || token.toString().equalsIgnoreCase("APPLICATION_SEARCH_SYS")
377 || token.toString().equalsIgnoreCase("VMO_ERROR_SYS")) {
380 if (token.id() == PlsqlTokenId.DOT) {
383 if (!token.toString().toLowerCase(Locale.ENGLISH).startsWith("check")) {
384 if (moveToReturnEnd(ts, endOffset, doc, block)) {
385 isReturnMissing = false;
386 hasReturn = HAS_RETURNS;
392 } else if (token.toString().equalsIgnoreCase("EXCEPTION")) {
394 } else if (token.toString().equalsIgnoreCase("WHEN") && isException) {
395 if (!isReturnMissing) {
396 if (PlsqlParserUtil.getNextNonWhitespace(ts, true)) { //Exception name
397 if (PlsqlParserUtil.getNextNonWhitespace(ts, true)) {
399 if (token.toString().equalsIgnoreCase("THEN")) {
400 isReturnMissing = true;
405 //return missing in above WHEN
412 return isReturnMissing;
415 public static boolean getUnreachableOffsets(final Document doc, final PlsqlBlock block, final List<Integer> lstUnreachable) throws BadLocationException {
416 final List<PlsqlBlock> children = block.getChildBlocks();
417 Collections.sort(children, comparator);
418 boolean isConstrusctReturn = false;
419 boolean isChildReturn = false;
420 boolean isReturn = false;
421 boolean isException = false;
422 int startOffset = findBlockImplStart(doc, block);
423 for (PlsqlBlock child : children) {
424 if (child.getType() != PlsqlBlockType.CURSOR
425 && child.getType() != PlsqlBlockType.CUSTOM_FOLD && child.getType() != PlsqlBlockType.COMMENT) {
426 if (startOffset < child.getStartOffset()) {
427 if (getUnreachableOffsets(startOffset, child.getStartOffset(), doc, block, lstUnreachable, isReturn, isException)) {
434 isChildReturn = getUnreachableOffsets(doc, child, lstUnreachable);
436 if ((child.getType() == PlsqlBlockType.IF && child.getName().toUpperCase(Locale.ENGLISH).startsWith("IF"))
437 || (child.getType() == PlsqlBlockType.CASE && child.getName().toUpperCase(Locale.ENGLISH).startsWith("CASE"))) {
438 isConstrusctReturn = isChildReturn;
439 } else if ((child.getType() == PlsqlBlockType.IF && child.getName().toUpperCase(Locale.ENGLISH).startsWith("ELSIF"))
440 || (child.getType() == PlsqlBlockType.IF && child.getName().toUpperCase(Locale.ENGLISH).startsWith("WHEN"))) {
441 if (isConstrusctReturn) {
442 isConstrusctReturn = isChildReturn;
444 } else if ((child.getType() == PlsqlBlockType.IF || child.getType() == PlsqlBlockType.CASE)
445 && child.getName().equalsIgnoreCase("ELSE")) {
446 if (isConstrusctReturn) {
447 isConstrusctReturn = isChildReturn;
448 if (isConstrusctReturn) {
454 startOffset = child.getEndOffset();
456 if (checkExceptionBlock(doc, block) < startOffset) {
459 isConstrusctReturn = getUnreachableOffsets(startOffset, block.getEndOffset(), doc, block, lstUnreachable, isReturn, isException);
461 isReturn = isConstrusctReturn;
467 private static boolean getUnreachableOffsets(final int startOffset, final int endOffset, final Document doc, PlsqlBlock block, final List<Integer> lstUnreachable, boolean isReturn, boolean isException) throws BadLocationException {
468 final TokenHierarchy tokenHierarchy = TokenHierarchy.get(doc);
469 @SuppressWarnings("unchecked")
470 final TokenSequence<PlsqlTokenId> ts = tokenHierarchy.tokenSequence(PlsqlTokenId.language());
471 int endLineCount = 0;
472 String previous = null;
474 ts.move(startOffset);
475 Token<PlsqlTokenId> token = ts.token();
476 boolean isRaised = false;
478 while (ts.moveNext() && ts.offset() < endOffset) {
481 if (token.toString().equalsIgnoreCase("RETURN")) {
482 if(isReturn && endLineCount > 0){
483 lstUnreachable.add(ts.offset());
486 else if (moveToReturnEnd(ts, endOffset, doc, block)) {
489 } else if (token.toString().equalsIgnoreCase("RAISE")) {
490 if (moveToReturnEnd(ts, endOffset, doc, block)) {
493 } else if (token.toString().equalsIgnoreCase("EXCEPTION")) {
497 } else if (token.toString().equalsIgnoreCase("WHEN") && isException) {
500 if (PlsqlParserUtil.getNextNonWhitespace(ts, true)) { //Exception name
501 if (PlsqlParserUtil.getNextNonWhitespace(ts, true)) {
503 if (token.toString().equalsIgnoreCase("THEN") || token.toString().equalsIgnoreCase("OR")) {
510 } else if ((isReturn || isRaised) && token.toString().contains("\n")) {
512 } else if (endLineCount > 0 && token.toString().toUpperCase(Locale.ENGLISH).contains("END") || isRaised ) {
514 } else if (endLineCount > 0 && token.toString().contains(";")) {
515 lstUnreachable.add(ts.offset());
518 if (token.toString().contains(";") && (previous != null && previous.equals("END"))) {
524 previous = token.toString();
530 private static int findBlockImplStart(final Document doc, final PlsqlBlock block) {
531 int startOffset = block.getStartOffset();
532 final TokenHierarchy tokenHierarchy = TokenHierarchy.get(doc);
533 @SuppressWarnings("unchecked")
534 final TokenSequence<PlsqlTokenId> ts = tokenHierarchy.tokenSequence(PlsqlTokenId.language());
537 ts.move(startOffset);
538 Token<PlsqlTokenId> token = ts.token();
539 while (ts.moveNext() && ts.offset() < block.getEndOffset()) {
542 if (token.toString().equalsIgnoreCase("BEGIN")) {
543 startOffset = ts.offset();
552 private static boolean moveToReturnEnd(final TokenSequence<PlsqlTokenId> ts, final int endOffset, final Document doc, final PlsqlBlock block) {
553 Token<PlsqlTokenId> token = ts.token();
554 while (ts.moveNext() && ts.offset() < block.getEndOffset()) {
557 // to handle scenarios like Return CASE 'a' THEN 'A'
558 if (token.toString().equalsIgnoreCase("CASE") || token.toString().equalsIgnoreCase("IF")) {
559 final TokenHierarchy tokenHierarchy = TokenHierarchy.get(doc);
560 int startOffset = token.offset(tokenHierarchy);
561 final TokenSequence<PlsqlTokenId> tsChild = tokenHierarchy.tokenSequence(PlsqlTokenId.language());
562 boolean caseReturned = false;
563 if (tsChild != null) {
564 tsChild.move(startOffset);
565 Token<PlsqlTokenId> tokenChild = tsChild.token();
566 while (tsChild.moveNext() && tsChild.offset() < block.getEndOffset()) {
567 tokenChild = tsChild.token();
569 if (tokenChild.toString().equalsIgnoreCase("ELSE") || tokenChild.toString().equalsIgnoreCase("DEFAULT")) {
573 if (tokenChild.toString().equals(";")) {
574 ts.move(tokenChild.offset(tokenHierarchy));
580 else if (token.toString().equals(";")) {
588 public static PlsqlBlock findMatchingImpl(final List<PlsqlBlock> blockHier, final Document source,
589 final Document dest, final PlsqlBlock sourceBlock) {
590 PlsqlBlock match = null;
591 final List<PlsqlBlock> matchList = new ArrayList<PlsqlBlock>();
592 PlsqlParserUtil.findMatchingImplBlocks(blockHier, sourceBlock.getName(), matchList);
594 //There are several methods with the same name. Have to check the signature
595 final List<String> usageParams = PlsqlParserUtil.fetchMethodDefParamTypes(source, sourceBlock.getStartOffset());
597 //Take PlsqlBlock one by one and compare the parameters
598 for (int x = 0; x < matchList.size(); x++) {
599 final PlsqlBlock block = matchList.get(x);
600 if (!(block.getParent() != null && sourceBlock.getParent() != null ? block.getParent().getName().equals(sourceBlock.getParent().getName()) : true)) {
601 continue; //If the parent block name is not the same this is not a match
603 final List<String> params = PlsqlParserUtil.fetchMethodDefParamTypes(dest, block.getStartOffset());
604 final int defaultNo = PlsqlParserUtil.fetchDefaultParams(dest, block.getStartOffset());
605 if (PlsqlParserUtil.compareParameters(usageParams, params, defaultNo)) {