diff --git a/groovy.support/nbproject/project.properties b/groovy.support/nbproject/project.properties --- a/groovy.support/nbproject/project.properties +++ b/groovy.support/nbproject/project.properties @@ -36,7 +36,7 @@ # # Contributor(s): javac.compilerargs=-Xlint -Xlint:-serial -javac.source=1.6 +javac.source=1.8 cp.extra=${tools.jar} nbm.homepage=http://wiki.netbeans.org/groovy nbm.module.author=Martin Adamek, Petr Hejl, Matthias Schmidt diff --git a/groovy.support/nbproject/project.xml b/groovy.support/nbproject/project.xml --- a/groovy.support/nbproject/project.xml +++ b/groovy.support/nbproject/project.xml @@ -108,6 +108,14 @@ + org.netbeans.api.templates + + + + 1.0 + + + org.netbeans.modules.gsf.testrunner @@ -141,7 +149,7 @@ 1 1.62 - + org.netbeans.modules.java.source.base @@ -160,6 +168,14 @@ + org.netbeans.modules.libs.groovy + + + + 1.0 + + + org.netbeans.modules.maven @@ -323,14 +339,6 @@ - org.netbeans.api.templates - - - - 1.0 - - - org.openide.nodes @@ -347,14 +355,6 @@ - org.openide.util.ui - - - - 9.3 - - - org.openide.util @@ -371,6 +371,14 @@ + org.openide.util.ui + + + + 9.3 + + + org.openide.windows @@ -379,7 +387,59 @@ - + + + unit + + org.netbeans.libs.junit4 + + + + org.netbeans.insane + + + + org.netbeans.modules.nbjunit + + + + org.openide.filesystems + + + + org.openide.filesystems.nb + + + + org.openide.io + + + + org.openide.loaders + + + + org.openide.nodes + + + + org.openide.text + + + + org.openide.util + + + + org.openide.util.lookup + + + + org.netbeans.modules.libs.groovy + + + + org.netbeans.modules.groovy.antproject org.netbeans.modules.groovy.editor diff --git a/groovy.support/src/org/netbeans/modules/groovy/support/actions/DebugMethodAction.java b/groovy.support/src/org/netbeans/modules/groovy/support/actions/DebugMethodAction.java deleted file mode 100644 --- a/groovy.support/src/org/netbeans/modules/groovy/support/actions/DebugMethodAction.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2012 Oracle and/or its affiliates. All rights reserved. - * - * Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners. - * - * The contents of this file are subject to the terms of either the GNU - * General Public License Version 2 only ("GPL") or the Common - * Development and Distribution License("CDDL") (collectively, the - * "License"). You may not use this file except in compliance with the - * License. You can obtain a copy of the License at - * http://www.netbeans.org/cddl-gplv2.html - * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the - * specific language governing permissions and limitations under the - * License. When distributing the software, include this License Header - * Notice in each file and include the License file at - * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the GPL Version 2 section of the License file that - * accompanied this code. If applicable, add the following below the - * License Header, with the fields enclosed by brackets [] replaced by - * your own identifying information: - * "Portions Copyrighted [year] [name of copyright owner]" - * - * If you wish your version of this file to be governed by only the CDDL - * or only the GPL Version 2, indicate your decision by adding - * "[Contributor] elects to include this software in this distribution - * under the [CDDL or GPL Version 2] license." If you do not indicate a - * single choice of license, a recipient has the option to distribute - * your version of this file under either the CDDL, the GPL Version 2 or - * to extend the choice of license to its licensees as provided above. - * However, if you add GPL Version 2 code and therefore, elected the GPL - * Version 2 license, then the option applies only if the new code is - * made subject to such option by the copyright holder. - * - * Contributor(s): - * - * Portions Copyrighted 2012 Sun Microsystems, Inc. - */ - -package org.netbeans.modules.groovy.support.actions; - -import java.util.Collection; -import org.netbeans.modules.gsf.testrunner.ui.api.TestMethodDebuggerProvider; -import org.openide.awt.ActionID; -import org.openide.awt.ActionReference; -import org.openide.awt.ActionRegistration; -import org.openide.nodes.Node; -import org.openide.util.HelpCtx; -import org.openide.util.Lookup; -import org.openide.util.NbBundle; -import org.openide.util.actions.NodeAction; - -/** - * - * @author Martin Janicek - */ -//@ActionID(id = "org.netbeans.modules.groovy.support.actions.DebugMethodAction", category = "CommonTestRunner") -//@ActionRegistration(displayName = "#LBL_Action_DebugTestMethod") -//@ActionReference(path = "Editors/text/x-groovy/Popup", position=860) -//@NbBundle.Messages({"LBL_Action_DebugTestMethod=Debug Focused Test Method"}) -public class DebugMethodAction extends NodeAction { - - public DebugMethodAction() { - putValue("noIconInMenu", Boolean.TRUE); //NOI18N - } - - @Override - public String getName() { - return NbBundle.getMessage(DebugMethodAction.class, "LBL_Action_DebugTestMethod"); - } - - @Override - public HelpCtx getHelpCtx() { - return HelpCtx.DEFAULT_HELP; - } - - @Override - public boolean asynchronous() { - return false; - } - - @Override - protected void performAction(Node[] activatedNodes) { - Collection providers = Lookup.getDefault().lookupAll(TestMethodDebuggerProvider.class); - for (TestMethodDebuggerProvider provider : providers) { - if(provider.canHandle(activatedNodes[0])) { - provider.debugTestMethod(activatedNodes[0]); - break; - } - } - } - - @Override - protected boolean enable(Node[] activatedNodes) { - return false; -// if (activatedNodes.length == 0) { -// return false; -// } -// Collection providers = Lookup.getDefault().lookupAll(TestMethodDebuggerProvider.class); -// for (TestMethodDebuggerProvider provider : providers) { -// if(provider.canHandle(activatedNodes[0])) { -// return true; -// } -// } -// return false; - } - -} diff --git a/groovy.support/src/org/netbeans/modules/groovy/support/actions/GroovyTestClassInfoTask.java b/groovy.support/src/org/netbeans/modules/groovy/support/actions/GroovyTestClassInfoTask.java deleted file mode 100644 --- a/groovy.support/src/org/netbeans/modules/groovy/support/actions/GroovyTestClassInfoTask.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2012 Oracle and/or its affiliates. All rights reserved. - * - * Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners. - * - * The contents of this file are subject to the terms of either the GNU - * General Public License Version 2 only ("GPL") or the Common - * Development and Distribution License("CDDL") (collectively, the - * "License"). You may not use this file except in compliance with the - * License. You can obtain a copy of the License at - * http://www.netbeans.org/cddl-gplv2.html - * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the - * specific language governing permissions and limitations under the - * License. When distributing the software, include this License Header - * Notice in each file and include the License file at - * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the GPL Version 2 section of the License file that - * accompanied this code. If applicable, add the following below the - * License Header, with the fields enclosed by brackets [] replaced by - * your own identifying information: - * "Portions Copyrighted [year] [name of copyright owner]" - * - * If you wish your version of this file to be governed by only the CDDL - * or only the GPL Version 2, indicate your decision by adding - * "[Contributor] elects to include this software in this distribution - * under the [CDDL or GPL Version 2] license." If you do not indicate a - * single choice of license, a recipient has the option to distribute - * your version of this file under either the CDDL, the GPL Version 2 or - * to extend the choice of license to its licensees as provided above. - * However, if you add GPL Version 2 code and therefore, elected the GPL - * Version 2 license, then the option applies only if the new code is - * made subject to such option by the copyright holder. - * - * Contributor(s): - * - * Portions Copyrighted 2012 Sun Microsystems, Inc. - */ -package org.netbeans.modules.groovy.support.actions; - -import com.sun.source.tree.Tree.Kind; -import com.sun.source.util.TreePath; -import java.util.Iterator; -import java.util.List; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.TypeElement; -import javax.lang.model.util.Elements; -import org.netbeans.api.java.source.CancellableTask; -import org.netbeans.api.java.source.CompilationController; -import org.netbeans.api.java.source.JavaSource.Phase; -import org.openide.filesystems.FileObject; - -/** - * - * @author Martin Janicek - */ -public final class GroovyTestClassInfoTask implements CancellableTask { - - private FileObject fileObject; - - private final int caretPosition; - private String className; - private String methodName; - - private static String ANNOTATION = "org.junit.Test"; //NOI18N - private static String TESTCASE = "junit.framework.TestCase"; //NOI18N - - GroovyTestClassInfoTask(int caretPosition) { - this.caretPosition = caretPosition; - } - - @Override - public void cancel() { - } - - @Override - public void run(CompilationController controller) throws Exception { - controller.toPhase(Phase.RESOLVED); - fileObject = controller.getFileObject(); - TypeElement typeElement = null; - List topLevelElements = controller.getTopLevelElements(); - for (Iterator it = topLevelElements.iterator(); it.hasNext();) { - typeElement = it.next(); - if (typeElement.getKind() == ElementKind.CLASS) { - className = typeElement.getSimpleName().toString(); - break; - } - } - Elements elements = controller.getElements(); - TypeElement testcase = elements.getTypeElement(TESTCASE); - boolean junit3 = (testcase != null && typeElement != null) ? controller.getTypes().isSubtype(typeElement.asType(), testcase.asType()) : false; - TreePath tp = controller.getTreeUtilities().pathFor(caretPosition); - while (tp != null && tp.getLeaf().getKind() != Kind.METHOD) { - tp = tp.getParentPath(); - } - if (tp != null) { - Element element = controller.getTrees().getElement(tp); - String mn = element.getSimpleName().toString(); - if (junit3){ - methodName = mn.startsWith("test") ? mn : null; //NOI18N - }else{ - List allAnnotationMirrors = elements.getAllAnnotationMirrors(element); - for (Iterator it = allAnnotationMirrors.iterator(); it.hasNext();) { - AnnotationMirror annotationMirror = it.next(); - typeElement = (TypeElement) annotationMirror.getAnnotationType().asElement(); - if (typeElement.getQualifiedName().contentEquals(ANNOTATION)) { - methodName = mn; - break; - } - } - } - } - } - - public FileObject getFileObject() { - return fileObject; - } - - String getClassName() { - return className; - } - - String getMethodName() { - return methodName; - } -} diff --git a/groovy.support/src/org/netbeans/modules/groovy/support/actions/TestMethodAction.java b/groovy.support/src/org/netbeans/modules/groovy/support/actions/TestMethodAction.java deleted file mode 100644 --- a/groovy.support/src/org/netbeans/modules/groovy/support/actions/TestMethodAction.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2012 Oracle and/or its affiliates. All rights reserved. - * - * Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners. - * - * The contents of this file are subject to the terms of either the GNU - * General Public License Version 2 only ("GPL") or the Common - * Development and Distribution License("CDDL") (collectively, the - * "License"). You may not use this file except in compliance with the - * License. You can obtain a copy of the License at - * http://www.netbeans.org/cddl-gplv2.html - * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the - * specific language governing permissions and limitations under the - * License. When distributing the software, include this License Header - * Notice in each file and include the License file at - * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the GPL Version 2 section of the License file that - * accompanied this code. If applicable, add the following below the - * License Header, with the fields enclosed by brackets [] replaced by - * your own identifying information: - * "Portions Copyrighted [year] [name of copyright owner]" - * - * If you wish your version of this file to be governed by only the CDDL - * or only the GPL Version 2, indicate your decision by adding - * "[Contributor] elects to include this software in this distribution - * under the [CDDL or GPL Version 2] license." If you do not indicate a - * single choice of license, a recipient has the option to distribute - * your version of this file under either the CDDL, the GPL Version 2 or - * to extend the choice of license to its licensees as provided above. - * However, if you add GPL Version 2 code and therefore, elected the GPL - * Version 2 license, then the option applies only if the new code is - * made subject to such option by the copyright holder. - * - * Contributor(s): - * - * Portions Copyrighted 2012 Sun Microsystems, Inc. - */ - -package org.netbeans.modules.groovy.support.actions; - -import java.util.Collection; -import org.netbeans.modules.gsf.testrunner.ui.api.TestMethodRunnerProvider; -import org.openide.awt.ActionID; -import org.openide.awt.ActionReference; -import org.openide.awt.ActionRegistration; -import org.openide.nodes.Node; -import org.openide.util.*; -import org.openide.util.actions.NodeAction; - -/** - * - * @author mjanicek - */ -//@ActionID(id = "org.netbeans.modules.groovy.support.actions.TestMethodAction", category = "CommonTestRunner") -//@ActionRegistration(displayName = "#LBL_Action_RunTestMethod") -//@ActionReference(path = "Editors/text/x-groovy/Popup", position=850) -//@NbBundle.Messages({"LBL_Action_RunTestMethod=Run Focused Test Method"}) -public class TestMethodAction extends NodeAction { - - public TestMethodAction() { - putValue("noIconInMenu", Boolean.TRUE); //NOI18N - } - - @Override - public String getName() { - return NbBundle.getMessage(TestMethodAction.class, "LBL_Action_RunTestMethod"); - } - - @Override - public HelpCtx getHelpCtx() { - return HelpCtx.DEFAULT_HELP; - } - - @Override - public boolean asynchronous() { - return false; - } - - @Override - protected void performAction(Node[] activatedNodes) { - Collection providers = Lookup.getDefault().lookupAll(TestMethodRunnerProvider.class); - for (TestMethodRunnerProvider provider : providers) { - if (provider.canHandle(activatedNodes[0])) { - provider.runTestMethod(activatedNodes[0]); - break; - } - } - } - - @Override - protected boolean enable(Node[] activatedNodes) { - return false; -// if (activatedNodes.length == 0) { -// return false; -// } -// Collection providers = Lookup.getDefault().lookupAll(TestMethodRunnerProvider.class); -// for (TestMethodRunnerProvider provider : providers) { -// if (provider.canHandle(activatedNodes[0])) { -// return true; -// } -// } -// return false; - } -} diff --git a/groovy.support/src/org/netbeans/modules/groovy/support/actions/TestMethodUtil.java b/groovy.support/src/org/netbeans/modules/groovy/support/actions/TestMethodUtil.java --- a/groovy.support/src/org/netbeans/modules/groovy/support/actions/TestMethodUtil.java +++ b/groovy.support/src/org/netbeans/modules/groovy/support/actions/TestMethodUtil.java @@ -39,17 +39,29 @@ * * Portions Copyrighted 2012 Sun Microsystems, Inc. */ - package org.netbeans.modules.groovy.support.actions; +import java.awt.EventQueue; import java.io.IOException; -import java.lang.ref.Reference; -import java.util.concurrent.Future; -import java.util.logging.Level; -import java.util.logging.Logger; +import java.io.LineNumberReader; +import java.io.StringReader; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import javax.swing.JEditorPane; import javax.swing.text.Document; -import org.netbeans.api.java.source.JavaSource; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.ast.ModuleNode; +import org.netbeans.modules.parsing.api.ParserManager; +import org.netbeans.modules.parsing.api.ResultIterator; +import org.netbeans.modules.parsing.api.Source; +import org.netbeans.modules.parsing.api.UserTask; +import org.netbeans.modules.parsing.spi.ParseException; +import org.netbeans.modules.parsing.spi.Parser.Result; import org.netbeans.spi.project.SingleMethod; import org.openide.cookies.EditorCookie; import org.openide.filesystems.FileObject; @@ -58,6 +70,7 @@ import org.openide.loaders.DataObject; import org.openide.nodes.Node; import org.openide.text.NbDocument; +import org.openide.util.Parameters; /** * @@ -65,37 +78,246 @@ */ public final class TestMethodUtil { - private static final Logger LOGGER = Logger.getLogger(TestMethodUtil.class.getName()); - private Reference resolver; + private static final String GROOVY_PARSER_RESULT_CLASS_NAME = "org.netbeans.modules.groovy.editor.api.parser.GroovyParserResult"; + + private static final String GPR_GET_ROOT_ELEMENT = "getRootElement"; + + private static final String AST_GET_MODULE_NODE = "getModuleNode"; static boolean isTestClass(Node activatedNode) { FileObject fo = getFileObjectFromNode(activatedNode); if (fo != null) { - if (!isGroovyFile(fo)) { - return false; - } //TODO add more checks here when action gets enabled? + return isGroovyFile(fo); } return false; } - static SingleMethod getTestMethod(Document doc, int cursor){ - SingleMethod sm = null; - if (doc != null){ - JavaSource js = JavaSource.forDocument(doc); - GroovyTestClassInfoTask task = new GroovyTestClassInfoTask(cursor); + /** + * Given the text from a Document, will read through the lines, adding 1 to + * a counter for each line, plus the length of the given line until the + * value is greater than or equal to the cursor, and this gives us the line. + * Next, the offset in the line which would make the counter equal to the + * cursor value is taken to represent the column. This is done to map to the + * Groovy AST nodes line and columns to match to classes and methods. The + * return from this method can be used to find the class and method the + * cursor is in. Remember, the Groovy lines and columns are index 1 with + * relation to the AST node classes. + * + * @param srcText the Groovy source as represented in the edited Groovy + * document; it must be this text as the expectation is a line will always + * end in a \n + * @param cursor the cursor offset in the given text. + * @return the line and column of the cursor offset in the text as an + * integer array such that index 0 is the line, and index 1 is the column + */ + public static int[] getLineAndColumn(final String srcText, final int cursor) { + Parameters.notNull("srcText", srcText); + Parameters.notEmpty("srcText", srcText); + int[] ret = new int[]{-1, -1}; + if (cursor > 0) { try { - if (js != null) { - Future f = js.runWhenScanFinished(task, true); - if (f.isDone() && task.getFileObject() != null && task.getMethodName() != null){ - sm = new SingleMethod(task.getFileObject(), task.getMethodName()); + final StringReader sr = new StringReader(srcText); + final LineNumberReader lr = new LineNumberReader(sr); + int counter = 0; + String line; + while ((line = lr.readLine()) != null) { + //remember, we went over a line, and it has a \n + counter += line.length(); + if (counter >= cursor) { + //lr incs line number at every line it reads, + //and started at 0 + ret[0] = lr.getLineNumber(); + //if we take away what we just added, then take the whole + //away from the cursor, then we are left with the number + //it takes to equal the cursor, and then since the chars + //and positions in the line are 0 indexed, we need to add + //1; if 0 or 0 length, then that should also work out + //correctly as if the cursor is on the line, it is in + //column 1 + ret[1] = (cursor - (counter - line.length())) + 1; + break; } + //account for the newline now as we don't care about it + //until we are done with the "current" line. The extra value + //will throw off the above calculation if added too soon, but + //if not added at all will also throw it off. So, add it here. + counter++; } } catch (IOException ex) { - LOGGER.log(Level.WARNING, null, ex); + throw new RuntimeException(ex); + } + } else if (cursor == 0) { + //first line and start of first line + ret[0] = 1; + ret[1] = 1; + } + return ret; + } + + /** + * Given a start and end line and a start and end column plus a given line + * and column, tests if the given line and column falls between the start + * and end values in relation to Groovy code and the Groovy AST. + * + * @param startLine the start line + * @param startCol the start column + * @param endLine the end line + * @param endCol the end column + * @param line the line to test + * @param col the column to test + * @return whether the line and column fall between the given start and end + * values + */ + public static boolean isBetweenLinesAndColumns(final int startLine, + final int startCol, + final int endLine, + final int endCol, + final int line, + final int col) { + boolean ret = false; + if (line >= startLine && line <= endLine) { + //at the momment we are approximately + //in the method bounds, so we'll assume + //true, then adjust accordingly + //as this is simpler than embedded ifs + //and logic as we only care about the col + //if we are on one of the bounding lines + ret = true; + + //even though above we say true, we may take it back + //if on the same line as the start line and not in + //the method bounds + if (line == startLine && !(col >= startCol)) { + ret = false; + } + + //even though above we may say true, we may take it back + //if on the same line as the end line and not in + //the method bounds + if (line == endLine && !(col <= endCol)) { + ret = false; } } - return sm; + return ret; + } + + /** + * Gets the Groovy AST ModuleNode associated with the given Source result. + * This is currently done with reflection due to the dependency graph of + * "Groovy Support" and "Groovy Editor". This is hopefully just temporary + * until the modules can be refactored into a better DAG. + * + * @param r the Result to extract from + * @return the ModuleNode if the Result is in fact a "GroovyParserResult", + * the sources have been parsed, and the {@link ModuleNode ModuleNode} has + * been set. If not set or the wrong "Result" time, then null will be + * returned. + */ + public static ModuleNode extractModuleNode(final Result r) { + //below line long to show up properly in enumerations + //TODO TestMethodUtil.extractModuleNode refactor "Groovy Support" and "Groovy Editor" to have a better DAG to remove need to use reflection to extract Groovy ModuleNode + ModuleNode ret = null; + try { + //no need to test result type as it will have the method or it won't + //and this makes it easier to test + final Method getRE = r.getClass().getMethod(GPR_GET_ROOT_ELEMENT); + final Object astRoot = getRE.invoke(r); + if (astRoot != null) { + final Method getMN = astRoot.getClass().getMethod(AST_GET_MODULE_NODE); + final ModuleNode lmn = ModuleNode.class.cast(getMN.invoke(astRoot)); + ret = lmn; + } + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + //push it up to the NB caller + throw new RuntimeException("The given result doesn't appear to come from parsing a Groovy file.", e); + } + return ret; + } + + /** + * Given a root {@link ModuleNode ModuleNode}, finds and returns the + * {@link ClassNode ClassNode} for the given line and column. It is possible + * in some situations for this to be null. + * + * @param root the root ModuleNode + * @param line the line + * @param col the column + * @return the ClassNode for the line and column (cursor) + */ + public static ClassNode getClassNodeForLineAndColumn(final ModuleNode root, + final int line, + final int col) { + ClassNode ret = null; + if (root != null) { + final List classes = root.getClasses(); + for (ClassNode cn : classes) { + if (isBetweenLinesAndColumns(cn.getLineNumber(), cn.getColumnNumber(), + cn.getLastLineNumber(), cn.getLastColumnNumber(), + line, col)) { + return cn; + } + } + } + return ret; + } + + /** + * Given a {@link ClassNode ClassNode}, finds and returns the + * {@link MethodNode MethodNode} for the given line and column. It is + * possible in some situations for this to be null. + * + * @param cn the ClassNode + * @param line the line + * @param col the column + * @return the MethodNode for the line and column (cursor) + */ + public static MethodNode getMethodNodeForLineAndColumn(final ClassNode cn, + final int line, + final int col) { + MethodNode ret = null; + if (cn != null) { + final List methods = cn.getMethods(); + for (MethodNode mn : methods) { + if (isBetweenLinesAndColumns(mn.getLineNumber(), mn.getColumnNumber(), + mn.getLastLineNumber(), mn.getLastColumnNumber(), + line, col)) { + return mn; + } + } + } + return ret; + } + + public static SingleMethod getTestMethod(final Document doc, final int cursor) { + final AtomicReference sm = new AtomicReference<>(); + if (doc != null) { + + Source s = Source.create(doc); + try { + ParserManager.parseWhenScanFinished(Collections.singleton(s), new UserTask() { + @Override + public void run(ResultIterator rit) throws Exception { + Result r = rit.getParserResult(); + //0:line, 1:column + final int[] lc = getLineAndColumn(doc.getText(0, doc.getLength()), cursor); + final int line = lc[0]; + final int col = lc[1]; + final ModuleNode root = extractModuleNode(r); + final ClassNode cn = getClassNodeForLineAndColumn(root, line, col); + final MethodNode mn = getMethodNodeForLineAndColumn(cn, line, col); + if (mn != null) { + final SingleMethod lsm = new SingleMethod(s.getFileObject(), mn.getName()); + sm.set(lsm); + } + } + + }); + } catch (ParseException e) { + throw new RuntimeException(e); + } + } + return sm.get(); } static boolean canHandle(Node activatedNode) { @@ -105,12 +327,24 @@ return false; } - EditorCookie ec = activatedNode.getLookup().lookup(EditorCookie.class); + final EditorCookie ec = activatedNode.getLookup().lookup(EditorCookie.class); if (ec != null) { - JEditorPane pane = NbDocument.findRecentEditorPane(ec); - if (pane != null) { - SingleMethod sm = getTestMethod(pane.getDocument(), pane.getCaret().getDot()); - if(sm != null) { + final AtomicReference doc = new AtomicReference<>(); + final AtomicInteger dot = new AtomicInteger(); + try { + EventQueue.invokeAndWait(() -> { + final JEditorPane pane = NbDocument.findRecentEditorPane(ec); + if (pane != null) { + doc.set(pane.getDocument()); + dot.set(pane.getCaret().getDot()); + } + }); + } catch (InterruptedException | InvocationTargetException e) { + throw new RuntimeException(e); + } + if (doc.get() != null) { + SingleMethod sm = getTestMethod(doc.get(), dot.get()); + if (sm != null) { return true; } } @@ -136,6 +370,8 @@ } private static boolean isGroovyFile(FileObject fileObj) { - return "groovy".equals(fileObj.getExt()) || "text/x-groovy".equals(FileUtil.getMIMEType(fileObj)); //NOI18N + final String ext = fileObj.getExt(); + final String mtype = FileUtil.getMIMEType(fileObj); + return "groovy".equals(ext) || "text/x-groovy".equals(mtype); //NOI18N } } diff --git a/groovy.support/src/org/netbeans/modules/groovy/support/resources/layer.xml b/groovy.support/src/org/netbeans/modules/groovy/support/resources/layer.xml --- a/groovy.support/src/org/netbeans/modules/groovy/support/resources/layer.xml +++ b/groovy.support/src/org/netbeans/modules/groovy/support/resources/layer.xml @@ -53,6 +53,14 @@ + + + + + + + + diff --git a/groovy.support/test/unit/src/org/netbeans/modules/groovy/support/actions/TestMethodUtilTest.java b/groovy.support/test/unit/src/org/netbeans/modules/groovy/support/actions/TestMethodUtilTest.java new file mode 100644 --- /dev/null +++ b/groovy.support/test/unit/src/org/netbeans/modules/groovy/support/actions/TestMethodUtilTest.java @@ -0,0 +1,199 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2016 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2016 Sun Microsystems, Inc. + */ +package org.netbeans.modules.groovy.support.actions; + +import groovy.lang.GroovyClassLoader; +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.UUID; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.ast.ModuleNode; +import org.codehaus.groovy.control.CompilationUnit; +import org.codehaus.groovy.control.CompilerConfiguration; +import org.codehaus.groovy.control.SourceUnit; +import org.netbeans.junit.NbTestCase; +import org.netbeans.modules.parsing.spi.Parser.Result; + +public class TestMethodUtilTest extends NbTestCase { + + private File compileDir; + + private StringBuilder groovyScript; + + private ModuleNode moduleNode; + + public TestMethodUtilTest(final String name) { + super(name); + } + + @Override + protected void setUp() throws IOException { + compileDir = new File(this.getWorkDir(), UUID.randomUUID().toString()); + compileDir.mkdir(); + groovyScript = new StringBuilder() + .append("class AGroovyClass {\n") + .append("public void method1() {\n") + .append("//some comment\n") + .append("}\n") + .append("//just some comment not in a method\n") + .append("}\n") + .append("//comment outside of class"); + moduleNode = createGroovyModuleNode(); + } + + @Override + protected void tearDown() { + compileDir.deleteOnExit(); + } + + private ModuleNode createGroovyModuleNode() { + final CompilerConfiguration conf = new CompilerConfiguration(); + conf.setTargetDirectory(compileDir); + final CompilationUnit cu + = new CompilationUnit( + new GroovyClassLoader( + getClass().getClassLoader() + ) + ); + cu.configure(conf); + final SourceUnit sn = cu.addSource("AGroovyClass.groovy", groovyScript.toString()); + try { + cu.compile(); + } catch (Exception e) { + //this groovy compile bit didn't work when running tests + //but did work when debugging them, so odd, but the AST is in + //place either way, and is all we need for this + this.log(e.getMessage()); + } + final ModuleNode mn = sn.getAST(); + return mn; + } + + public void testGetLineAndColumn() { + final StringBuilder g = new StringBuilder() + .append("class MyClass {\n") + .append("public void method1() {\n") + .append("//cursor here before h\n") + .append("}\n") + .append("}"); + //3rd line, 10th column ... 3,10 + int cursor = g.lastIndexOf("here"); + int[] lc = TestMethodUtil.getLineAndColumn(g.toString(), cursor); + assert lc[0] == 3; + assert lc[1] == 10; + + cursor = g.lastIndexOf("//"); + lc = TestMethodUtil.getLineAndColumn(g.toString(), cursor); + assert lc[0] == 3; + assert lc[1] == 1; + + lc = TestMethodUtil.getLineAndColumn(g.toString(), 0); + assert lc[0] == 1; + assert lc[1] == 1; + } + + public void testIsBetweenLinesAndColumns() { + assert TestMethodUtil.isBetweenLinesAndColumns(0, 0, 0, 0, 0, 0); + assert TestMethodUtil.isBetweenLinesAndColumns(1, 5, 2, 10, 2, 10); + assert !TestMethodUtil.isBetweenLinesAndColumns(1, 5, 2, 10, 2, 11); + assert !TestMethodUtil.isBetweenLinesAndColumns(1, 5, 2, 10, 3, 10); + assert TestMethodUtil.isBetweenLinesAndColumns(1, 5, 1, 30, 1, 20); + assert !TestMethodUtil.isBetweenLinesAndColumns(1, 5, 1, 30, 1, 31); + assert !TestMethodUtil.isBetweenLinesAndColumns(1, 5, 1, 30, 1, 4); + assert !TestMethodUtil.isBetweenLinesAndColumns(2, 5, 4, 30, 1, 4); + } + + //setup some simple classes to mock behavior of the + //reflection code in TestMethodUtil + private class TestASTRoot { + + public ModuleNode getModuleNode() { + return moduleNode; + } + } + + private class TestGroovyParserResult extends Result { + + public TestGroovyParserResult() { + super(null); + } + + @Override + protected void invalidate() { + + } + + public TestASTRoot getRootElement() { + return new TestASTRoot(); + } + + } + + public void testExtractModuleNode() { + final TestGroovyParserResult r = new TestGroovyParserResult(); + final ModuleNode lmn = TestMethodUtil.extractModuleNode(r); + assert moduleNode.equals(lmn); + } + + public void testGetClassNodeForLineAndColumn() throws IOException { + List classes = moduleNode.getClasses(); + + assert classes.size() > 0; + assert TestMethodUtil.getClassNodeForLineAndColumn(moduleNode, 0, 0) == null; + final ClassNode cn = TestMethodUtil.getClassNodeForLineAndColumn(moduleNode, 5, 2); + assert cn != null; + assert "AGroovyClass".equals(cn.getNameWithoutPackage()); + assert TestMethodUtil.getClassNodeForLineAndColumn(moduleNode, 7, 5) == null; + } + + public void testGetMethodNodeForLineAndColumn() { + final ClassNode cn = TestMethodUtil.getClassNodeForLineAndColumn(moduleNode, 5, 2); + assert cn != null; + MethodNode mn = TestMethodUtil.getMethodNodeForLineAndColumn(cn, 5, 2); + assert mn == null; + mn = TestMethodUtil.getMethodNodeForLineAndColumn(cn, 3, 2); + assert mn != null; + assert "method1".equals(mn.getName()); + } +} diff --git a/gsf.testrunner.ui/src/org/netbeans/modules/gsf/testrunner/ui/TestMethodDebuggerAction.java b/gsf.testrunner.ui/src/org/netbeans/modules/gsf/testrunner/ui/TestMethodDebuggerAction.java --- a/gsf.testrunner.ui/src/org/netbeans/modules/gsf/testrunner/ui/TestMethodDebuggerAction.java +++ b/gsf.testrunner.ui/src/org/netbeans/modules/gsf/testrunner/ui/TestMethodDebuggerAction.java @@ -65,14 +65,18 @@ * @author Theofanis Oikonomou */ @ActionID(id = "org.netbeans.modules.gsf.testrunner.TestMethodDebuggerAction", category = "CommonTestRunner") -@ActionRegistration(displayName = "#LBL_Action_DebugTestMethod") -@ActionReferences(value = {@ActionReference(path = "Editors/text/x-java/Popup", position=1797)}) +@ActionRegistration(lazy = false, displayName = "#LBL_Action_DebugTestMethod") +@ActionReferences(value = { + @ActionReference(path = "Editors/text/x-java/Popup", position = 1797)}) @NbBundle.Messages({"LBL_Action_DebugTestMethod=Debug Focused Test Method"}) public class TestMethodDebuggerAction extends NodeAction { + private RequestProcessor.Task debugMethodTask; private TestMethodDebuggerProvider debugMethodProvider; - - /** Creates a new instance of TestMethodDebuggerAction */ + + /** + * Creates a new instance of TestMethodDebuggerAction + */ public TestMethodDebuggerAction() { putValue("noIconInMenu", Boolean.TRUE); //NOI18N } @@ -86,7 +90,7 @@ public HelpCtx getHelpCtx() { return HelpCtx.DEFAULT_HELP; } - + @Override public boolean asynchronous() { return false; @@ -96,31 +100,31 @@ protected void performAction(final Node[] activatedNodes) { final Collection providers = Lookup.getDefault().lookupAll(TestMethodDebuggerProvider.class); RequestProcessor RP = new RequestProcessor("TestMethodDebuggerAction", 1, true); // NOI18N - debugMethodTask = RP.create(new Runnable() { - @Override - public void run() { - for (TestMethodDebuggerProvider provider : providers) { - if (provider.canHandle(activatedNodes[0])) { - debugMethodProvider = provider; - break; - } - } - } - }); - final ProgressHandle ph = ProgressHandleFactory.createHandle(Bundle.Search_For_Provider(), debugMethodTask); - debugMethodTask.addTaskListener(new TaskListener() { - @Override - public void taskFinished(org.openide.util.Task task) { - ph.finish(); - if(debugMethodProvider == null) { - StatusDisplayer.getDefault().setStatusText(Bundle.No_Provider_Found()); - } else { - debugMethodProvider.debugTestMethod(activatedNodes[0]); - } - } - }); - ph.start(); - debugMethodTask.schedule(0); + debugMethodTask = RP.create(new Runnable() { + @Override + public void run() { + for (TestMethodDebuggerProvider provider : providers) { + if (provider.canHandle(activatedNodes[0])) { + debugMethodProvider = provider; + break; + } + } + } + }); + final ProgressHandle ph = ProgressHandleFactory.createHandle(Bundle.Search_For_Provider(), debugMethodTask); + debugMethodTask.addTaskListener(new TaskListener() { + @Override + public void taskFinished(org.openide.util.Task task) { + ph.finish(); + if (debugMethodProvider == null) { + StatusDisplayer.getDefault().setStatusText(Bundle.No_Provider_Found()); + } else { + debugMethodProvider.debugTestMethod(activatedNodes[0]); + } + } + }); + ph.start(); + debugMethodTask.schedule(0); } @Override @@ -128,9 +132,9 @@ if (activatedNodes.length == 0) { return false; } - if(debugMethodTask != null && !debugMethodTask.isFinished()) { - return false; - } + if (debugMethodTask != null && !debugMethodTask.isFinished()) { + return false; + } Collection providers = Lookup.getDefault().lookupAll(TestMethodDebuggerProvider.class); for (TestMethodDebuggerProvider provider : providers) { if (provider.isTestClass(activatedNodes[0])) { @@ -139,5 +143,5 @@ } return false; } - + } diff --git a/gsf.testrunner.ui/src/org/netbeans/modules/gsf/testrunner/ui/TestMethodRunnerAction.java b/gsf.testrunner.ui/src/org/netbeans/modules/gsf/testrunner/ui/TestMethodRunnerAction.java --- a/gsf.testrunner.ui/src/org/netbeans/modules/gsf/testrunner/ui/TestMethodRunnerAction.java +++ b/gsf.testrunner.ui/src/org/netbeans/modules/gsf/testrunner/ui/TestMethodRunnerAction.java @@ -60,14 +60,18 @@ * @author Theofanis Oikonomou */ @ActionID(id = "org.netbeans.modules.gsf.testrunner.TestMethodRunnerAction", category = "CommonTestRunner") -@ActionRegistration(displayName = "#LBL_Action_RunTestMethod") -@ActionReferences(value = {@ActionReference(path = "Editors/text/x-java/Popup", position=1795)}) +@ActionRegistration(lazy = false, displayName = "#LBL_Action_RunTestMethod") +@ActionReferences(value = { + @ActionReference(path = "Editors/text/x-java/Popup", position = 1795)}) @NbBundle.Messages({"LBL_Action_RunTestMethod=Run Focused Test Method"}) public class TestMethodRunnerAction extends NodeAction { + private RequestProcessor.Task runMethodTask; private TestMethodRunnerProvider runMethodProvider; - - /** Creates a new instance of TestMethodRunnerAction */ + + /** + * Creates a new instance of TestMethodRunnerAction + */ public TestMethodRunnerAction() { putValue("noIconInMenu", Boolean.TRUE); //NOI18N } @@ -81,7 +85,7 @@ public HelpCtx getHelpCtx() { return HelpCtx.DEFAULT_HELP; } - + @Override public boolean asynchronous() { return false; @@ -89,37 +93,37 @@ @Override @NbBundle.Messages({"Search_For_Provider=Searching for provider to handle the test method", - "No_Provider_Found=No provider can handle the test method", - "Scanning_In_Progress=Scanning in progress, cannot yet identify the name of the test method"}) + "No_Provider_Found=No provider can handle the test method", + "Scanning_In_Progress=Scanning in progress, cannot yet identify the name of the test method"}) protected void performAction(final Node[] activatedNodes) { final Collection providers = Lookup.getDefault().lookupAll(TestMethodRunnerProvider.class); - RequestProcessor RP = new RequestProcessor("TestMethodRunnerAction", 1, true); // NOI18N - runMethodTask = RP.create(new Runnable() { - @Override - public void run() { - for (TestMethodRunnerProvider provider : providers) { - if (provider.canHandle(activatedNodes[0])) { - runMethodProvider = provider; - break; - } - } - } - }); - final ProgressHandle ph = ProgressHandleFactory.createHandle(Bundle.Search_For_Provider(), runMethodTask); - runMethodTask.addTaskListener(new TaskListener() { - @Override - public void taskFinished(org.openide.util.Task task) { - ph.finish(); - if(runMethodProvider == null) { + RequestProcessor RP = new RequestProcessor("TestMethodRunnerAction", 1, true); // NOI18N + runMethodTask = RP.create(new Runnable() { + @Override + public void run() { + for (TestMethodRunnerProvider provider : providers) { + if (provider.canHandle(activatedNodes[0])) { + runMethodProvider = provider; + break; + } + } + } + }); + final ProgressHandle ph = ProgressHandleFactory.createHandle(Bundle.Search_For_Provider(), runMethodTask); + runMethodTask.addTaskListener(new TaskListener() { + @Override + public void taskFinished(org.openide.util.Task task) { + ph.finish(); + if (runMethodProvider == null) { boolean isIndexing = IndexingManager.getDefault().isIndexing(); StatusDisplayer.getDefault().setStatusText(isIndexing ? Bundle.Scanning_In_Progress() : Bundle.No_Provider_Found()); - } else { - runMethodProvider.runTestMethod(activatedNodes[0]); - } - } - }); - ph.start(); - runMethodTask.schedule(0); + } else { + runMethodProvider.runTestMethod(activatedNodes[0]); + } + } + }); + ph.start(); + runMethodTask.schedule(0); } @Override @@ -127,9 +131,9 @@ if (activatedNodes.length == 0) { return false; } - if(runMethodTask != null && !runMethodTask.isFinished()) { - return false; - } + if (runMethodTask != null && !runMethodTask.isFinished()) { + return false; + } Collection providers = Lookup.getDefault().lookupAll(TestMethodRunnerProvider.class); for (TestMethodRunnerProvider provider : providers) { if (provider.isTestClass(activatedNodes[0])) { diff --git a/libs.groovy/nbproject/project.xml b/libs.groovy/nbproject/project.xml --- a/libs.groovy/nbproject/project.xml +++ b/libs.groovy/nbproject/project.xml @@ -46,6 +46,7 @@ org.netbeans.modules.groovy.editor + org.netbeans.modules.groovy.support org.netbeans.modules.groovy.refactoring groovy.lang groovyjarjarantlr