[NETBEANS-3588] Code Templates not working in Java Editor in for loops (#2444)

* [NETBEANS-3588] Code Templates not working in Java Editor in for loops

* [NETBEANS-3588] Code Templates not working in Java Editor in for loops
diff --git a/java/java.editor/src/org/netbeans/modules/editor/java/JavaCodeTemplateFilter.java b/java/java.editor/src/org/netbeans/modules/editor/java/JavaCodeTemplateFilter.java
index 3d8b6ef..bbfbe4f 100644
--- a/java/java.editor/src/org/netbeans/modules/editor/java/JavaCodeTemplateFilter.java
+++ b/java/java.editor/src/org/netbeans/modules/editor/java/JavaCodeTemplateFilter.java
@@ -20,27 +20,28 @@
 package org.netbeans.modules.editor.java;
 
 import com.sun.source.tree.Tree;
-
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.EnumSet;
 import java.util.HashSet;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.logging.Logger;
 import javax.swing.text.JTextComponent;
-
 import com.sun.source.tree.CaseTree;
 import com.sun.source.tree.ClassTree;
 import com.sun.source.tree.ExpressionTree;
 import com.sun.source.util.SourcePositions;
+import com.sun.source.util.TreePath;
 import org.netbeans.api.java.lexer.JavaTokenId;
 import org.netbeans.api.java.source.CompilationController;
 import org.netbeans.api.java.source.JavaSource.Phase;
 import org.netbeans.api.java.source.SourceUtils;
 import org.netbeans.api.java.source.TreeUtilities;
+import org.netbeans.api.lexer.Token;
+import org.netbeans.api.lexer.TokenHierarchy;
+import org.netbeans.api.lexer.TokenId;
 import org.netbeans.api.lexer.TokenSequence;
-import org.netbeans.api.progress.ProgressUtils;
+import org.netbeans.api.progress.BaseProgressUtils;
 import org.netbeans.lib.editor.codetemplates.api.CodeTemplate;
 import org.netbeans.lib.editor.codetemplates.spi.CodeTemplateFilter;
 import org.netbeans.modules.parsing.api.ParserManager;
@@ -58,7 +59,6 @@
  */
 public class JavaCodeTemplateFilter implements CodeTemplateFilter {
     
-    private static final Logger LOG = Logger.getLogger(JavaCodeTemplateFilter.class.getName());
     private static final String EXPRESSION = "EXPRESSION"; //NOI18N
     private static final String CLASS_HEADER = "CLASS_HEADER"; //NOI18N
     
@@ -72,88 +72,121 @@
             final Source source = Source.create(component.getDocument());
             if (source != null) {
                 final AtomicBoolean cancel = new AtomicBoolean();
-                ProgressUtils.runOffEventDispatchThread(new Runnable() {
-                    @Override
-                    public void run() {
-                        try {
-                            ParserManager.parse(Collections.singleton(source), new UserTask() {
-                                @Override
-                                public void run(ResultIterator resultIterator) throws Exception {
-                                    if (cancel.get()) {
-                                        return;
+                BaseProgressUtils.runOffEventDispatchThread(() -> {
+                    try {
+                        ParserManager.parse(Collections.singleton(source), new UserTask() {
+                            @Override
+                            public void run(ResultIterator resultIterator) throws Exception {
+                                if (cancel.get()) {
+                                    return;
+                                }
+                                Parser.Result result = resultIterator.getParserResult(startOffset);
+                                CompilationController controller = result != null ? CompilationController.get(result) : null;
+                                if (controller != null && Phase.PARSED.compareTo(controller.toPhase(Phase.PARSED)) <= 0) {
+                                    TreeUtilities tu = controller.getTreeUtilities();
+                                    int eo = endOffset;
+                                    int so = startOffset;
+                                    if (so >= 0) {
+                                        so = result.getSnapshot().getEmbeddedOffset(startOffset);
                                     }
-                                    Parser.Result result = resultIterator.getParserResult(startOffset);
-                                    CompilationController controller = result != null ? CompilationController.get(result) : null;
-                                    if (controller != null && Phase.PARSED.compareTo(controller.toPhase(Phase.PARSED)) <= 0) {
-                                        TreeUtilities tu = controller.getTreeUtilities();
-                                        int eo = endOffset;
-                                        int so = startOffset;
-                                        if (so >= 0) {
-                                            so = result.getSnapshot().getEmbeddedOffset(startOffset);
-                                        }
-                                        if (endOffset >= 0) {
-                                            eo = result.getSnapshot().getEmbeddedOffset(endOffset);
-                                            TokenSequence<JavaTokenId> ts = SourceUtils.getJavaTokenSequence(controller.getTokenHierarchy(), so);
-                                            int delta = ts.move(so);
+                                    if (endOffset >= 0) {
+                                        eo = result.getSnapshot().getEmbeddedOffset(endOffset);
+                                        TokenSequence<JavaTokenId> ts = SourceUtils.getJavaTokenSequence(controller.getTokenHierarchy(), so);
+                                        int delta = ts.move(so);
+                                        if (delta == 0 || ts.moveNext() && ts.token().id() == JavaTokenId.WHITESPACE) {
+                                            delta = ts.move(eo);
                                             if (delta == 0 || ts.moveNext() && ts.token().id() == JavaTokenId.WHITESPACE) {
-                                                delta = ts.move(eo);
-                                                if (delta == 0 || ts.moveNext() && ts.token().id() == JavaTokenId.WHITESPACE) {
-                                                    String selectedText = controller.getText().substring(so, eo).trim();
-                                                    SourcePositions[] sp = new SourcePositions[1];
-                                                    ExpressionTree expr = selectedText.length() > 0 ? tu.parseExpression(selectedText, sp) : null;
-                                                    if (expr != null && expr.getKind() != Tree.Kind.IDENTIFIER && !Utilities.containErrors(expr) && sp[0].getEndPosition(null, expr) >= selectedText.length()) {
-                                                        stringCtx = EXPRESSION;
-                                                    }
+                                                String selectedText = controller.getText().substring(so, eo).trim();
+                                                SourcePositions[] sp = new SourcePositions[1];
+                                                ExpressionTree expr = selectedText.length() > 0 ? tu.parseExpression(selectedText, sp) : null;
+                                                if (expr != null && expr.getKind() != Tree.Kind.IDENTIFIER && !Utilities.containErrors(expr) && sp[0].getEndPosition(null, expr) >= selectedText.length()) {
+                                                    stringCtx = EXPRESSION;
                                                 }
                                             }
                                         }
-                                        Tree tree = tu.pathFor(so).getLeaf();
-                                        if (eo >= 0 && so != eo) {
-                                            if (tu.pathFor(eo).getLeaf() != tree) {
-                                                return;
-                                            }
+                                    }
+                                    Tree tree = tu.pathFor(so).getLeaf();
+                                    if (eo >= 0 && so != eo) {
+                                        if (tu.pathFor(eo).getLeaf() != tree) {
+                                            return;
                                         }
-                                        treeKindCtx = tree.getKind();
-                                        switch (treeKindCtx) {
-                                            case CASE:
-                                                if (so < controller.getTrees().getSourcePositions().getEndPosition(controller.getCompilationUnit(), ((CaseTree)tree).getExpression())) {
-                                                    treeKindCtx = null;
-                                                }
-                                                break;
-                                            case CLASS:
-                                                SourcePositions sp = controller.getTrees().getSourcePositions();
-                                                int startPos = (int)sp.getEndPosition(controller.getCompilationUnit(), ((ClassTree)tree).getModifiers());
-                                                if (startPos <= 0) {
-                                                    startPos = (int)sp.getStartPosition(controller.getCompilationUnit(), tree);
-                                                }
-                                                String headerText = controller.getText().substring(startPos, so);
-                                                int idx = headerText.indexOf('{'); //NOI18N
-                                                if (idx < 0) {
-                                                    treeKindCtx = null;
-                                                    stringCtx = CLASS_HEADER;
-                                                }
-                                                break;
-                                            case FOR_LOOP:
-                                            case ENHANCED_FOR_LOOP:
-                                            case WHILE_LOOP:
-                                                sp = controller.getTrees().getSourcePositions();
+                                    }
+                                    treeKindCtx = tree.getKind();
+                                    switch (treeKindCtx) {
+                                        case CASE:
+                                            if (so < controller.getTrees().getSourcePositions().getEndPosition(controller.getCompilationUnit(), ((CaseTree)tree).getExpression())) {
+                                                treeKindCtx = null;
+                                            }
+                                            break;
+                                        case CLASS:
+                                            SourcePositions sp = controller.getTrees().getSourcePositions();
+                                            int startPos = (int)sp.getEndPosition(controller.getCompilationUnit(), ((ClassTree)tree).getModifiers());
+                                            if (startPos <= 0) {
                                                 startPos = (int)sp.getStartPosition(controller.getCompilationUnit(), tree);
-                                                String text = controller.getText().substring(startPos, so);
-                                                if (!text.trim().endsWith(")")) {
+                                            }
+                                            String headerText = controller.getText().substring(startPos, so);
+                                            int idx = headerText.indexOf('{'); //NOI18N
+                                            if (idx < 0) {
+                                                treeKindCtx = null;
+                                                stringCtx = CLASS_HEADER;
+                                            }
+                                            break;
+                                        case FOR_LOOP:
+                                        case ENHANCED_FOR_LOOP:
+                                            if (!isRightParenthesisOfLoopPresent(controller, so)) {
+                                                treeKindCtx = null;
+                                            }
+                                            break;
+                                        case PARENTHESIZED:
+                                            if (isPartOfWhileLoop(controller, so)) {
+                                                if (!isRightParenthesisOfLoopPresent(controller, so)) {
                                                     treeKindCtx = null;
                                                 }
-                                        }
+                                            }
+                                            break;
                                     }
                                 }
-                            });
-                        } catch (ParseException ex) {
-                            Exceptions.printStackTrace(ex);
-                        }
+                            }
+                        });
+                    } catch (ParseException ex) {
+                        Exceptions.printStackTrace(ex);
                     }
                 }, NbBundle.getMessage(JavaCodeTemplateProcessor.class, "JCT-init"), cancel, false); //NOI18N
             }
         }
     }
+    
+    private boolean isRightParenthesisOfLoopPresent(CompilationController controller, int abbrevStartOffset) {
+        TokenHierarchy<?> tokenHierarchy = controller.getTokenHierarchy();
+        TokenSequence<?> tokenSequence = tokenHierarchy.tokenSequence();
+        tokenSequence.move(abbrevStartOffset);
+        if (tokenSequence.moveNext()) {
+            TokenId tokenId = skipNextWhitespaces(tokenSequence);
+            return tokenId == null ? false : (tokenId == JavaTokenId.RPAREN);
+        }
+        return false;
+    }
+    
+    private TokenId skipNextWhitespaces(TokenSequence<?> tokenSequence) {
+        TokenId tokenId = null;
+        while (tokenSequence.moveNext()) {
+            Token<?> token = tokenSequence.token();
+            if (token != null) {
+                tokenId = token.id();
+            }
+            if (tokenId != JavaTokenId.WHITESPACE) {
+                break;
+            }
+        }
+        return tokenId;
+    }
+    
+    private boolean isPartOfWhileLoop(CompilationController controller, int abbrevStartOffset) {
+        TreeUtilities treeUtilities = controller.getTreeUtilities();
+        TreePath currentPath = treeUtilities.pathFor(abbrevStartOffset);
+        TreePath parentPath = treeUtilities.getPathElementOfKind(Tree.Kind.WHILE_LOOP, currentPath);
+        return parentPath != null;
+    }
 
     @Override
     public synchronized boolean accept(CodeTemplate template) {
diff --git a/java/java.editor/test/unit/src/org/netbeans/modules/editor/java/JavaCodeTemplateProcessorTest.java b/java/java.editor/test/unit/src/org/netbeans/modules/editor/java/JavaCodeTemplateProcessorTest.java
index 08b5d41..e4843ca 100644
--- a/java/java.editor/test/unit/src/org/netbeans/modules/editor/java/JavaCodeTemplateProcessorTest.java
+++ b/java/java.editor/test/unit/src/org/netbeans/modules/editor/java/JavaCodeTemplateProcessorTest.java
@@ -157,7 +157,52 @@
                              "}");
         assertFileObjectTextMatchesRegex("(?s)\\s*?public class Test.*?");
     }
-    
+
+    public void testCodeTemplatesShouldWorkInsideParenthesesOfForEachLoop() throws Exception {
+         doTestTemplateInsert("${name newVarName}",
+                             "public class Test {\n" +
+                             "    private void t(String... args) {\n" +
+                             "        for (String |) {\n" +
+                             "        }\n" +
+                             "    }\n" +
+                             "}",
+                             "public class Test {\n" +
+                             "    private void t(String... args) {\n" +
+                             "        for (String name|) {\n" +
+                             "        }\n" +
+                             "    }\n" +
+                             "}");
+         doTestTemplateInsert("${names iterable}",
+                             "public class Test {\n" +
+                             "    private void t(String... args) {\n" +
+                             "        for (String name: |) {\n" +
+                             "        }\n" +
+                             "    }\n" +
+                             "}",
+                             "public class Test {\n" +
+                             "    private void t(String... args) {\n" +
+                             "        for (String name: args|) {\n" +
+                             "        }\n" +
+                             "    }\n" +
+                             "}");
+    }
+
+    public void testCodeTemplatesShouldWorkInsideParenthesesOfWhileLoop() throws Exception {
+         doTestTemplateInsert("${list instanceof=\"java.util.List\"}.isEmpty()",
+                             "public class Test {\n" +
+                             "    private void t(String... args) {\n" +
+                             "        while (|) {\n" +
+                             "        }\n" +
+                             "    }\n" +
+                             "}",
+                             "public class Test {\n" +
+                             "    private void t(String... args) {\n" +
+                             "        while (list|.isEmpty()) {\n" +
+                             "        }\n" +
+                             "    }\n" +
+                             "}");
+    }
+
     private void assertFileObjectTextMatchesRegex(String regex) throws IOException {
         String text = testFile.asText();
         assertTrue("The file text must match the regular expression", text.matches(regex));