hyperlinking from testng.xml to referenced classes/tests and suites
authorLukas Jungmann <jungi@netbeans.org>
Sat, 04 Feb 2012 17:13:15 +0100
changeset 17774a39ee6fdf7c0
parent 17773 cd8dc4b8b2b8
child 17775 0549a237e89d
hyperlinking from testng.xml to referenced classes/tests and suites
testng/nbproject/project.xml
testng/src/org/netbeans/modules/contrib/testng/Bundle.properties
testng/src/org/netbeans/modules/contrib/testng/TestNGSuiteHyperlingProvider.java
testng/src/org/netbeans/modules/contrib/testng/resources/layer.xml
     1.1 --- a/testng/nbproject/project.xml	Sat Feb 04 14:25:18 2012 +0100
     1.2 +++ b/testng/nbproject/project.xml	Sat Feb 04 17:13:15 2012 +0100
     1.3 @@ -2,7 +2,7 @@
     1.4  <!--
     1.5  DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     1.6  
     1.7 -Copyright © 2008-2011 Oracle and/or its affiliates. All rights reserved.
     1.8 +Copyright © 2008-2012 Oracle and/or its affiliates. All rights reserved.
     1.9  
    1.10  
    1.11  The contents of this file are subject to the terms of either the GNU
    1.12 @@ -74,6 +74,15 @@
    1.13                      </run-dependency>
    1.14                  </dependency>
    1.15                  <dependency>
    1.16 +                    <code-name-base>org.netbeans.api.progress</code-name-base>
    1.17 +                    <build-prerequisite/>
    1.18 +                    <compile-dependency/>
    1.19 +                    <run-dependency>
    1.20 +                        <release-version>1</release-version>
    1.21 +                        <specification-version>1.27</specification-version>
    1.22 +                    </run-dependency>
    1.23 +                </dependency>
    1.24 +                <dependency>
    1.25                      <code-name-base>org.netbeans.api.xml</code-name-base>
    1.26                      <build-prerequisite/>
    1.27                      <compile-dependency/>
    1.28 @@ -100,6 +109,33 @@
    1.29                      </run-dependency>
    1.30                  </dependency>
    1.31                  <dependency>
    1.32 +                    <code-name-base>org.netbeans.modules.editor</code-name-base>
    1.33 +                    <build-prerequisite/>
    1.34 +                    <compile-dependency/>
    1.35 +                    <run-dependency>
    1.36 +                        <release-version>3</release-version>
    1.37 +                        <specification-version>1.60</specification-version>
    1.38 +                    </run-dependency>
    1.39 +                </dependency>
    1.40 +                <dependency>
    1.41 +                    <code-name-base>org.netbeans.modules.editor.lib</code-name-base>
    1.42 +                    <build-prerequisite/>
    1.43 +                    <compile-dependency/>
    1.44 +                    <run-dependency>
    1.45 +                        <release-version>3</release-version>
    1.46 +                        <specification-version>3.18</specification-version>
    1.47 +                    </run-dependency>
    1.48 +                </dependency>
    1.49 +                <dependency>
    1.50 +                    <code-name-base>org.netbeans.modules.editor.lib2</code-name-base>
    1.51 +                    <build-prerequisite/>
    1.52 +                    <compile-dependency/>
    1.53 +                    <run-dependency>
    1.54 +                        <release-version>1</release-version>
    1.55 +                        <specification-version>1.54</specification-version>
    1.56 +                    </run-dependency>
    1.57 +                </dependency>
    1.58 +                <dependency>
    1.59                      <code-name-base>org.netbeans.modules.extexecution</code-name-base>
    1.60                      <build-prerequisite/>
    1.61                      <compile-dependency/>
    1.62 @@ -143,6 +179,24 @@
    1.63                      </run-dependency>
    1.64                  </dependency>
    1.65                  <dependency>
    1.66 +                    <code-name-base>org.netbeans.modules.java.sourceui</code-name-base>
    1.67 +                    <build-prerequisite/>
    1.68 +                    <compile-dependency/>
    1.69 +                    <run-dependency>
    1.70 +                        <release-version>1</release-version>
    1.71 +                        <specification-version>1.26</specification-version>
    1.72 +                    </run-dependency>
    1.73 +                </dependency>
    1.74 +                <dependency>
    1.75 +                    <code-name-base>org.netbeans.modules.lexer</code-name-base>
    1.76 +                    <build-prerequisite/>
    1.77 +                    <compile-dependency/>
    1.78 +                    <run-dependency>
    1.79 +                        <release-version>2</release-version>
    1.80 +                        <specification-version>1.43</specification-version>
    1.81 +                    </run-dependency>
    1.82 +                </dependency>
    1.83 +                <dependency>
    1.84                      <code-name-base>org.netbeans.modules.project.ant</code-name-base>
    1.85                      <build-prerequisite/>
    1.86                      <compile-dependency/>
    1.87 @@ -179,6 +233,14 @@
    1.88                      </run-dependency>
    1.89                  </dependency>
    1.90                  <dependency>
    1.91 +                    <code-name-base>org.netbeans.modules.xml.lexer</code-name-base>
    1.92 +                    <build-prerequisite/>
    1.93 +                    <compile-dependency/>
    1.94 +                    <run-dependency>
    1.95 +                        <specification-version>1.16</specification-version>
    1.96 +                    </run-dependency>
    1.97 +                </dependency>
    1.98 +                <dependency>
    1.99                      <code-name-base>org.openide.awt</code-name-base>
   1.100                      <build-prerequisite/>
   1.101                      <compile-dependency/>
     2.1 --- a/testng/src/org/netbeans/modules/contrib/testng/Bundle.properties	Sat Feb 04 14:25:18 2012 +0100
     2.2 +++ b/testng/src/org/netbeans/modules/contrib/testng/Bundle.properties	Sat Feb 04 17:13:15 2012 +0100
     2.3 @@ -1,6 +1,6 @@
     2.4  # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     2.5  #
     2.6 -# Copyright \u00a9 1997-2011 Oracle and/or its affiliates. All rights reserved.
     2.7 +# Copyright \u00a9 1997-2012 Oracle and/or its affiliates. All rights reserved.
     2.8  #
     2.9  # The contents of this file are subject to the terms of either the GNU
    2.10  # General Public License Version 2 only ("GPL") or the Common
    2.11 @@ -48,3 +48,6 @@
    2.12  Templates/TestNG/TestNGTest.java=TestNG Test Case
    2.13  TestNG=TestNG 6.0.1
    2.14  Loaders/text/x-testng+xml/Factories/org-netbeans-modules-contrib-testng-TestNGSuiteDataLoader.instance=TestNG Suite Files
    2.15 +
    2.16 +LBL_GoToDeclaration=Go to Declaration
    2.17 +goto_source_source_not_found=Source file for {0} not found.
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/testng/src/org/netbeans/modules/contrib/testng/TestNGSuiteHyperlingProvider.java	Sat Feb 04 17:13:15 2012 +0100
     3.3 @@ -0,0 +1,342 @@
     3.4 +/*
     3.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     3.6 + *
     3.7 + * Copyright 2012 Oracle and/or its affiliates. All rights reserved.
     3.8 + *
     3.9 + * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    3.10 + * Other names may be trademarks of their respective owners.
    3.11 + *
    3.12 + * The contents of this file are subject to the terms of either the GNU
    3.13 + * General Public License Version 2 only ("GPL") or the Common
    3.14 + * Development and Distribution License("CDDL") (collectively, the
    3.15 + * "License"). You may not use this file except in compliance with the
    3.16 + * License. You can obtain a copy of the License at
    3.17 + * http://www.netbeans.org/cddl-gplv2.html
    3.18 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    3.19 + * specific language governing permissions and limitations under the
    3.20 + * License.  When distributing the software, include this License Header
    3.21 + * Notice in each file and include the License file at
    3.22 + * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
    3.23 + * particular file as subject to the "Classpath" exception as provided
    3.24 + * by Oracle in the GPL Version 2 section of the License file that
    3.25 + * accompanied this code. If applicable, add the following below the
    3.26 + * License Header, with the fields enclosed by brackets [] replaced by
    3.27 + * your own identifying information:
    3.28 + * "Portions Copyrighted [year] [name of copyright owner]"
    3.29 + *
    3.30 + * If you wish your version of this file to be governed by only the CDDL
    3.31 + * or only the GPL Version 2, indicate your decision by adding
    3.32 + * "[Contributor] elects to include this software in this distribution
    3.33 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
    3.34 + * single choice of license, a recipient has the option to distribute
    3.35 + * your version of this file under either the CDDL, the GPL Version 2 or
    3.36 + * to extend the choice of license to its licensees as provided above.
    3.37 + * However, if you add GPL Version 2 code and therefore, elected the GPL
    3.38 + * Version 2 license, then the option applies only if the new code is
    3.39 + * made subject to such option by the copyright holder.
    3.40 + *
    3.41 + * Contributor(s):
    3.42 + *
    3.43 + * Portions Copyrighted 2012 Sun Microsystems, Inc.
    3.44 + */
    3.45 +package org.netbeans.modules.contrib.testng;
    3.46 +
    3.47 +import java.io.File;
    3.48 +import java.io.IOException;
    3.49 +import java.text.MessageFormat;
    3.50 +import java.util.Collections;
    3.51 +import java.util.List;
    3.52 +import java.util.Map;
    3.53 +import java.util.concurrent.ConcurrentHashMap;
    3.54 +import java.util.concurrent.atomic.AtomicBoolean;
    3.55 +import javax.lang.model.element.Element;
    3.56 +import javax.lang.model.element.ElementKind;
    3.57 +import javax.lang.model.element.TypeElement;
    3.58 +import javax.swing.text.Document;
    3.59 +import javax.swing.text.JTextComponent;
    3.60 +import javax.swing.text.StyledDocument;
    3.61 +import org.netbeans.api.editor.EditorRegistry;
    3.62 +import org.netbeans.api.java.source.ClasspathInfo;
    3.63 +import org.netbeans.api.java.source.CompilationController;
    3.64 +import org.netbeans.api.java.source.JavaSource;
    3.65 +import org.netbeans.api.java.source.Task;
    3.66 +import org.netbeans.api.java.source.ui.ElementOpen;
    3.67 +import org.netbeans.api.lexer.Token;
    3.68 +import org.netbeans.api.lexer.TokenHierarchy;
    3.69 +import org.netbeans.api.lexer.TokenSequence;
    3.70 +import org.netbeans.api.progress.ProgressUtils;
    3.71 +import org.netbeans.api.xml.lexer.XMLTokenId;
    3.72 +import org.netbeans.lib.editor.hyperlink.spi.HyperlinkProvider;
    3.73 +import org.netbeans.modules.editor.NbEditorUtilities;
    3.74 +import org.openide.awt.StatusDisplayer;
    3.75 +import org.openide.cookies.OpenCookie;
    3.76 +import org.openide.filesystems.FileObject;
    3.77 +import org.openide.filesystems.FileUtil;
    3.78 +import org.openide.loaders.DataObject;
    3.79 +import org.openide.loaders.DataObjectNotFoundException;
    3.80 +import org.openide.nodes.Node;
    3.81 +import org.openide.util.Exceptions;
    3.82 +import org.openide.util.NbBundle;
    3.83 +
    3.84 +/**
    3.85 + *
    3.86 + * @author lukas
    3.87 + */
    3.88 +public class TestNGSuiteHyperlingProvider implements HyperlinkProvider {
    3.89 +
    3.90 +    private int startOffset;
    3.91 +    private int endOffset;
    3.92 +    private String file;
    3.93 +    private String method;
    3.94 +    private LinkType targetType;
    3.95 +    private static final Map<String, LinkType> linkMap = new ConcurrentHashMap<String, LinkType>();
    3.96 +
    3.97 +    private enum LinkType {
    3.98 +
    3.99 +        RESOURCE,
   3.100 +        JAVA_SOURCE;
   3.101 +    }
   3.102 +
   3.103 +    static {
   3.104 +        linkMap.put("suite-file#path", LinkType.RESOURCE);
   3.105 +        linkMap.put("class#name", LinkType.JAVA_SOURCE);
   3.106 +        linkMap.put("listener#class-name", LinkType.JAVA_SOURCE);
   3.107 +        linkMap.put("selector-class#name", LinkType.JAVA_SOURCE);
   3.108 +        linkMap.put("include#name", LinkType.JAVA_SOURCE);
   3.109 +        linkMap.put("exclude#name", LinkType.JAVA_SOURCE);
   3.110 +//        linkMap.put("class/methods/include#name", LinkType.JAVA_SOURCE);
   3.111 +//        linkMap.put("class/methods/exclude#name", LinkType.JAVA_SOURCE);
   3.112 +    }
   3.113 +
   3.114 +    public TestNGSuiteHyperlingProvider() {
   3.115 +    }
   3.116 +
   3.117 +    public boolean isHyperlinkPoint(Document doc, int offset) {
   3.118 +        JTextComponent target = EditorRegistry.lastFocusedComponent();
   3.119 +        final StyledDocument styledDoc = (StyledDocument) target.getDocument();
   3.120 +        if (styledDoc == null) {
   3.121 +            return false;
   3.122 +        }
   3.123 +
   3.124 +        // Work only with the open editor
   3.125 +        //and the editor has to be the active component:
   3.126 +        if ((target == null) || (target.getDocument() != doc)) {
   3.127 +            return false;
   3.128 +        }
   3.129 +
   3.130 +        TokenHierarchy hi = TokenHierarchy.get(doc);
   3.131 +        TokenSequence<XMLTokenId> ts = hi.tokenSequence(XMLTokenId.language());
   3.132 +        ts.move(offset);
   3.133 +        ts.moveNext();
   3.134 +        Token<XMLTokenId> tok = ts.token();
   3.135 +        if (tok != null) {
   3.136 +            int tokOffset = ts.offset();
   3.137 +            Token<XMLTokenId> t = getAttribute(ts);
   3.138 +            if (t == null) {
   3.139 +                return false;
   3.140 +            }
   3.141 +            String xpath = "#" + t.text().toString();
   3.142 +            t = getParentElement(ts);
   3.143 +            xpath = getElementName(t) + xpath;
   3.144 +
   3.145 +            targetType = linkMap.get(xpath);
   3.146 +            if (targetType == null) {
   3.147 +                return false;
   3.148 +            }
   3.149 +
   3.150 +            if (xpath.startsWith("include") || xpath.startsWith("exclude")) {
   3.151 +                t = getParentElement(ts);
   3.152 +                if ("methods".equals(getElementName(t))) {
   3.153 +                    method = tok.text().toString();
   3.154 +                    getParentElement(ts); // <class ...> element
   3.155 +                    file = getAttributeValue(ts, "name");
   3.156 +                } else {
   3.157 +                    return false;
   3.158 +                }
   3.159 +            } else {
   3.160 +                method = null;
   3.161 +            }
   3.162 +            startOffset = tokOffset + 1;
   3.163 +            endOffset = startOffset + tok.text().length() - 2;
   3.164 +            if (method == null) {
   3.165 +                file = tok.text().subSequence(1, tok.text().length() - 1).toString();
   3.166 +            } else {
   3.167 +                method = method.substring(1, method.length() - 1);
   3.168 +            }
   3.169 +            return true;
   3.170 +        }
   3.171 +        return false;
   3.172 +    }
   3.173 +
   3.174 +    public int[] getHyperlinkSpan(Document doc, int offset) {
   3.175 +        JTextComponent target = EditorRegistry.lastFocusedComponent();
   3.176 +        final StyledDocument styledDoc = (StyledDocument) target.getDocument();
   3.177 +        if (styledDoc == null) {
   3.178 +            return null;
   3.179 +        }
   3.180 +
   3.181 +        // Return the position, which was set in the isHyperlink method:
   3.182 +        return new int[]{startOffset, endOffset};
   3.183 +    }
   3.184 +
   3.185 +    public void performClickAction(final Document doc, final int offset) {
   3.186 +        final AtomicBoolean cancel = new AtomicBoolean();
   3.187 +        ProgressUtils.runOffEventDispatchThread(new Runnable() {
   3.188 +
   3.189 +            public void run() {
   3.190 +                performGoTo(doc, offset, file, method, targetType, cancel);
   3.191 +            }
   3.192 +        }, NbBundle.getMessage(TestNGSuiteHyperlingProvider.class, "LBL_GoToDeclaration"), cancel, false);
   3.193 +    }
   3.194 +
   3.195 +    private Token<XMLTokenId> getAttribute(TokenSequence<XMLTokenId> ts) {
   3.196 +        Token<XMLTokenId> tok = ts.token();
   3.197 +        if (tok.id() == XMLTokenId.VALUE) {
   3.198 +            while (ts.movePrevious()) {
   3.199 +                tok = ts.token();
   3.200 +                switch (tok.id()) {
   3.201 +                    case ARGUMENT:
   3.202 +                        return tok;
   3.203 +                    case OPERATOR:
   3.204 +                    case EOL:
   3.205 +                    case ERROR:
   3.206 +                    case WS:
   3.207 +                        continue;
   3.208 +                    default:
   3.209 +                        return null;
   3.210 +                }
   3.211 +            }
   3.212 +        }
   3.213 +        return null;
   3.214 +    }
   3.215 +
   3.216 +    private Token<XMLTokenId> getParentElement(TokenSequence<XMLTokenId> ts) {
   3.217 +        int depth = 0;
   3.218 +        while (ts.movePrevious()) {
   3.219 +            Token<XMLTokenId> prev = ts.token();
   3.220 +            switch (prev.id()) {
   3.221 +                case TAG:
   3.222 +                    if (prev.text().length() == 1) {
   3.223 +                        continue;
   3.224 +                    }
   3.225 +                    if (prev.text().toString().contains("/")) {
   3.226 +                        depth++;
   3.227 +                        continue;
   3.228 +                    }
   3.229 +                    depth--;
   3.230 +                    if (depth == -1) {
   3.231 +                        return prev;
   3.232 +                    }
   3.233 +                    continue;
   3.234 +                default:
   3.235 +                    continue;
   3.236 +            }
   3.237 +        }
   3.238 +        return null;
   3.239 +    }
   3.240 +
   3.241 +    private String getElementName(Token<XMLTokenId> tok) {
   3.242 +        return tok.text().toString().substring(1);
   3.243 +    }
   3.244 +
   3.245 +    private String getAttributeValue(TokenSequence<XMLTokenId> ts, String name) {
   3.246 +        boolean readValue = false;
   3.247 +        while (ts.moveNext()) {
   3.248 +            Token<XMLTokenId> next = ts.token();
   3.249 +
   3.250 +            switch (next.id()) {
   3.251 +                case ARGUMENT:
   3.252 +                    if (name.equals(next.text().toString())) {
   3.253 +                        readValue = true;
   3.254 +                    }
   3.255 +                    continue;
   3.256 +                case VALUE:
   3.257 +                    if (readValue) {
   3.258 +                        CharSequence val = next.text().subSequence(1, next.text().length() - 1);
   3.259 +                        return val.toString();
   3.260 +                    }
   3.261 +                    continue;
   3.262 +                case OPERATOR:
   3.263 +                case EOL:
   3.264 +                case ERROR:
   3.265 +                case WS:
   3.266 +                    continue;
   3.267 +                default:
   3.268 +                    return null;
   3.269 +            }
   3.270 +        }
   3.271 +        return null;
   3.272 +    }
   3.273 +
   3.274 +    private void performGoTo(Document doc, int offset, final String file, final String method, final LinkType type, AtomicBoolean cancel) {
   3.275 +        switch (type) {
   3.276 +            case RESOURCE:
   3.277 +                FileObject fo = NbEditorUtilities.getFileObject(doc);
   3.278 +                File target = new File(file);
   3.279 +                FileObject targetFO;
   3.280 +                if (target.isAbsolute()) {
   3.281 +                    targetFO = FileUtil.toFileObject(FileUtil.normalizeFile(target));
   3.282 +                } else {
   3.283 +                    targetFO = fo.getParent().getFileObject(file);
   3.284 +                }
   3.285 +                if (targetFO.isData() && targetFO.isValid() && !targetFO.isVirtual()) {
   3.286 +                    openInEditor(targetFO);
   3.287 +                } else {
   3.288 +                    String key = "goto_source_source_not_found"; //NOI18N
   3.289 +                    String msg = NbBundle.getMessage(TestNGSuiteHyperlingProvider.class, key);
   3.290 +                    StatusDisplayer.getDefault().setStatusText(MessageFormat.format(msg, new Object[]{file}));
   3.291 +                }
   3.292 +                break;
   3.293 +            case JAVA_SOURCE:
   3.294 +                final ClasspathInfo cp = ClasspathInfo.create(doc);
   3.295 +                JavaSource js = JavaSource.create(cp, Collections.EMPTY_LIST);
   3.296 +                try {
   3.297 +                    js.runUserActionTask(new Task<CompilationController>() {
   3.298 +
   3.299 +                        @Override
   3.300 +                        public void run(CompilationController cc) throws Exception {
   3.301 +                            cc.toPhase(JavaSource.Phase.ELEMENTS_RESOLVED);
   3.302 +                            Element element = cc.getElements().getTypeElement(file.trim());
   3.303 +                            if (element != null) {
   3.304 +                                if (method != null) {
   3.305 +                                    List<? extends Element> enclosedElements = cc.getElements().getAllMembers((TypeElement) element);
   3.306 +                                    for (Element e : enclosedElements) {
   3.307 +                                        if (e.getKind() == ElementKind.METHOD) {
   3.308 +                                            if (e.getSimpleName().toString().equals(method)) {
   3.309 +                                                element = e;
   3.310 +                                                break;
   3.311 +                                            }
   3.312 +                                        }
   3.313 +                                    }
   3.314 +                                }
   3.315 +                                if (!ElementOpen.open(cp, element)) {
   3.316 +                                    String key = "goto_source_source_not_found"; //NOI18N
   3.317 +                                    String msg = NbBundle.getMessage(TestNGSuiteHyperlingProvider.class, key);
   3.318 +                                    StatusDisplayer.getDefault().setStatusText(MessageFormat.format(msg, new Object[]{file}));
   3.319 +                                }
   3.320 +                            }
   3.321 +                        }
   3.322 +                    }, false);
   3.323 +                } catch (IOException ex) {
   3.324 +                    Exceptions.printStackTrace(ex);
   3.325 +                }
   3.326 +                break;
   3.327 +        }
   3.328 +    }
   3.329 +
   3.330 +    private void openInEditor(FileObject fo) {
   3.331 +        DataObject dobj;
   3.332 +        try {
   3.333 +            dobj = DataObject.find(fo);
   3.334 +        } catch (DataObjectNotFoundException e) {
   3.335 +            Exceptions.printStackTrace(e);
   3.336 +            return;
   3.337 +        }
   3.338 +        if (dobj != null) {
   3.339 +            Node.Cookie cookie = dobj.getLookup().lookup(OpenCookie.class);
   3.340 +            if (cookie != null) {
   3.341 +                ((OpenCookie) cookie).open();
   3.342 +            }
   3.343 +        }
   3.344 +    }
   3.345 +}
     4.1 --- a/testng/src/org/netbeans/modules/contrib/testng/resources/layer.xml	Sat Feb 04 14:25:18 2012 +0100
     4.2 +++ b/testng/src/org/netbeans/modules/contrib/testng/resources/layer.xml	Sat Feb 04 17:13:15 2012 +0100
     4.3 @@ -51,6 +51,14 @@
     4.4                      </folder>
     4.5                  </folder>
     4.6              </folder>
     4.7 +            <folder name="x-testng+xml">
     4.8 +                <folder name="HyperlinkProviders">
     4.9 +                    <file name="TestNGSuiteHyperlingProvider.instance">
    4.10 +                        <attr name="instanceClass" stringvalue="org.netbeans.modules.contrib.testng.TestNGSuiteHyperlingProvider"/>
    4.11 +                        <attr name="instanceOf" stringvalue="org.netbeans.lib.editor.hyperlink.spi.HyperlinkProvider"/>
    4.12 +                    </file>
    4.13 +                </folder>
    4.14 +            </folder>
    4.15          </folder>
    4.16      </folder>
    4.17      <folder name="Loaders">