Commits

Jan Lahoda committed d28ce9e

Improving the web.ui: ability to perform find usages

Comments (0)

Files changed (8)

         <copy file="${nbplatform.active.dir}/ide/modules/org-netbeans-modules-parsing-api.jar" todir="." />
         <copy file="${nbplatform.active.dir}/ide/modules/org-netbeans-modules-projectapi.jar" todir="." />
         <copy file="${nbplatform.active.dir}/java/modules/org-netbeans-modules-java-project.jar" todir="." />
+        <copy file="${nbplatform.active.dir}/ide/modules/org-netbeans-modules-lexer.jar" todir="." />
+        <copy file="${nbplatform.active.dir}/java/modules/org-netbeans-modules-java-lexer.jar" todir="." />
+        <copy file="${nbplatform.active.dir}/ide/modules/org-netbeans-modules-editor-util.jar" todir="." />
     </target>
 </project>

remoting/server/web/web.ui/nbproject/project.properties

 endorsed.classpath=
 excludes=
 file.reference.org-netbeans-api-java-classpath.jar=../../../../lib/org-netbeans-api-java-classpath.jar
+file.reference.org-netbeans-modules-java-lexer.jar=../../../../lib/org-netbeans-modules-java-lexer.jar
 file.reference.org-netbeans-modules-java-source.jar=../../../../lib/org-netbeans-modules-java-source.jar
 file.reference.org-netbeans-modules-jumpto.jar=../../../../lib/org-netbeans-modules-jumpto.jar
+file.reference.org-netbeans-modules-lexer.jar=../../../../lib/org-netbeans-modules-lexer.jar
 file.reference.org-netbeans-modules-parsing-lucene.jar=../../../../lib/org-netbeans-modules-parsing-lucene.jar
 file.reference.org-openide-filesystems.jar=../../../../lib/org-openide-filesystems.jar
 file.reference.util-commons.jar-1=../../../ide/api/external/util-commons.jar
     ${file.reference.org-netbeans-modules-parsing-lucene.jar}:\
     ${file.reference.org-openide-filesystems.jar}:\
     ${file.reference.util-commons.jar-1}:\
-    ${file.reference.util-pojson.jar-1}
+    ${file.reference.util-pojson.jar-1}:\
+    ${file.reference.org-netbeans-modules-java-lexer.jar}:\
+    ${file.reference.org-netbeans-modules-lexer.jar}
 # Space-separated list of extra javac options
 javac.compilerargs=
 javac.deprecation=false

remoting/server/web/web.ui/src/icons/javaFile.png

Added
New image

remoting/server/web/web.ui/src/org/netbeans/modules/jackpot30/backend/ui/UI.java

 
 package org.netbeans.modules.jackpot30.backend.ui;
 
-import java.util.Comparator;
-import java.util.LinkedHashMap;
-import java.util.Map.Entry;
 import freemarker.template.TemplateException;
 import java.io.IOException;
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
-import javax.lang.model.element.TypeElement;
-import javax.lang.model.type.ArrayType;
-import javax.lang.model.type.DeclaredType;
-import javax.lang.model.type.TypeKind;
-import javax.lang.model.type.TypeMirror;
-import javax.lang.model.type.TypeVariable;
+import java.util.Map.Entry;
 import javax.ws.rs.GET;
 import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
 import javax.ws.rs.QueryParam;
 import org.codeviation.pojson.Pojson;
+import org.netbeans.api.java.lexer.JavaTokenId;
+import org.netbeans.api.lexer.TokenHierarchy;
+import org.netbeans.api.lexer.TokenSequence;
 import org.netbeans.modules.jackpot30.backend.base.FreemarkerUtilities;
 import org.netbeans.modules.jackpot30.backend.base.WebUtilities;
 import static org.netbeans.modules.jackpot30.backend.base.WebUtilities.escapeForQuery;
 @Path("/index/ui")
 public class UI {
 
+    private static final String URL_BASE = "http://localhost:9998/index";
+//    private static final String URL_BASE = "http://lahoda.info/index";
+
     @GET
     @Path("/search")
     @Produces("text/html")
         configurationData.put("prefix", prefix);
 
         if (prefix != null && path != null) {
-            URI u = new URI("http://localhost:9998/index/symbol/search?path=" + escapeForQuery(path) + "&prefix=" + escapeForQuery(prefix));
-            long queryTime = System.currentTimeMillis();
+            URI u = new URI(URL_BASE + "/symbol/search?path=" + escapeForQuery(path) + "&prefix=" + escapeForQuery(prefix));
             @SuppressWarnings("unchecked") //XXX: should not trust something got from the network!
             Map<String, List<Map<String, Object>>> symbols = Pojson.load(LinkedHashMap.class, u);
             List<Map<String, Object>> results = new LinkedList<Map<String, Object>>();
 
-            queryTime = System.currentTimeMillis() - queryTime;
-
             for (Entry<String, List<Map<String, Object>>> e : symbols.entrySet()) {
                 for (Map<String, Object> found : e.getValue()) {
                     found.put("icon", getElementIcon((String) found.get("kind"), (Collection<String>) found.get("modifiers")));
                 }
             }
 
+            URI typeSearch = new URI(URL_BASE + "/type/search?path=" + escapeForQuery(path) + "&prefix=" + escapeForQuery(prefix));
+            @SuppressWarnings("unchecked") //XXX: should not trust something got from the network!
+            Map<String, List<String>> types = Pojson.load(LinkedHashMap.class, typeSearch);
+
+            for (Entry<String, List<String>> e : types.entrySet()) {
+                for (String fqn : e.getValue()) {
+                    Map<String, Object> result = new HashMap<String, Object>();
+
+                    result.put("icon", getElementIcon("CLASS", Collections.<String>emptyList()));
+                    result.put("kind", "CLASS");
+                    result.put("fqn", fqn);
+
+                    String displayName = fqn;
+                    String enclosingFQN = "";
+
+                    if (displayName.lastIndexOf('.') > 0) {
+                        displayName = displayName.substring(displayName.lastIndexOf('.') + 1);
+                        enclosingFQN = fqn.substring(0, fqn.lastIndexOf('.'));
+                    }
+
+                    if (displayName.lastIndexOf('$') > 0) {
+                        displayName = displayName.substring(displayName.lastIndexOf('$') + 1);
+                        enclosingFQN = fqn.substring(0, fqn.lastIndexOf('$'));
+                    }
+
+                    result.put("displayName", displayName);
+                    result.put("enclosingFQN", enclosingFQN);
+
+                    if (fqn.contains("$")) {
+                        fqn = fqn.substring(0, fqn.indexOf("$"));
+                    }
+
+                    result.put("file", e.getKey() + fqn.replace('.', '/') + ".java");
+
+                    results.add(result);
+                }
+            }
+
             Collections.sort(results, new Comparator<Map<String, Object>>() {
                 @Override public int compare(Map<String, Object> o1, Map<String, Object> o2) {
-                    return ((String) o1.get("simpleName")).compareTo((String) o2.get("simpleName"));
+                    int r = ((String) o1.get("displayName")).compareTo((String) o2.get("displayName"));
+
+                    if (r == 0) {
+                        r = ((String) o1.get("enclosingFQN")).compareTo((String) o2.get("enclosingFQN"));
+                    }
+                    return r;
                 }
             });
 
             configurationData.put("results", results);
-
-            Map<String, Object> statistics = new HashMap<String, Object>();
-
-            statistics.put("queryTime", queryTime);
-
-            configurationData.put("statistics", statistics);
         }
 
         return FreemarkerUtilities.processTemplate("org/netbeans/modules/jackpot30/backend/ui/ui-findType.html", configurationData);
     }
 
+    @GET
+    @Path("/show")
+    @Produces("text/html")
+    public String show(@QueryParam("path") String segment, @QueryParam("relative") String relative) throws URISyntaxException, IOException, TemplateException {
+        URI u = new URI(URL_BASE + "/source/cat?path=" + escapeForQuery(segment) + "&relative=" + escapeForQuery(relative));
+        String content = WebUtilities.requestStringResponse(u);
+        TokenSequence<?> ts = TokenHierarchy.create(content, JavaTokenId.language()).tokenSequence();
+        StringBuilder spans = new StringBuilder();
+        StringBuilder cats  = new StringBuilder();
+        int currentOffset = 0;
+
+        while (ts.moveNext()) {
+            if (spans.length() > 0) spans.append(", ");
+            int endOffset = ts.offset() + ts.token().length();
+            spans.append(endOffset - currentOffset);
+            currentOffset = endOffset;
+            String category = ts.token().id().primaryCategory();
+
+            if ("keyword".equals(category)) {
+                cats.append("K");
+            } else if ("keyword-directive".equals(category)) {
+                cats.append("K");
+            } else if ("literal".equals(category)) {
+                cats.append("K");
+            } else if ("whitespace".equals(category)) {
+                cats.append("W");
+            } else if ("comment".equals(category)) {
+                cats.append("C");
+            } else if ("character".equals(category)) {
+                cats.append("H");
+            } else if ("number".equals(category)) {
+                cats.append("N");
+            } else if ("string".equals(category)) {
+                cats.append("S");
+            } else {
+                cats.append("E");
+            }
+        }
+
+        Map<String, Object> configurationData = new HashMap<String, Object>();
+
+        configurationData.put("spans", spans.toString());
+        configurationData.put("categories", cats.toString());
+        configurationData.put("code", translate(content));
+
+        return FreemarkerUtilities.processTemplate("org/netbeans/modules/jackpot30/backend/ui/showCode.html", configurationData);
+    }
+
+    //XXX: usages on fields do not work because the field signature in the index contain also the field type
+    @GET
+    @Path("/usages")
+    @Produces("text/html")
+    public String usages(@QueryParam("path") String segment, @QueryParam("signatures") String signatures) throws URISyntaxException, IOException, TemplateException {
+        List<Map<String, String>> segments2Process = new ArrayList<Map<String, String>>();
+
+        for (Map<String, String> m : list()) {
+            if (segment != null) {
+                if (segment.equals(m.get("segment"))) {
+                    segments2Process.add(m);
+                }
+            } else {
+                segments2Process.add(m);
+            }
+        }
+
+        Map<String, Object> configurationData = new HashMap<String, Object>();
+        StringBuilder elementDisplayName = new StringBuilder();
+        String[] splitSignature = signatures.split(":");
+
+        elementDisplayName.append(splitSignature[2]);
+
+        if ("METHOD".equals(splitSignature[0])) {
+            elementDisplayName.append(decodeMethodSignature(splitSignature[3]));
+        }
+
+        elementDisplayName.append(" in ");
+        elementDisplayName.append(splitSignature[1]);
+
+        configurationData.put("elementDisplayName", elementDisplayName.toString()); //TODO
+
+        List<Map<String, Object>> results = new ArrayList<Map<String, Object>>(segments2Process.size());
+
+        for (Map<String, String> m : segments2Process) {
+            Map<String, Object> rootResults = new HashMap<String, Object>();
+            String currentSegment = m.get("segment");
+
+            rootResults.put("rootDisplayName", m.get("displayName"));
+            rootResults.put("rootPath", currentSegment);
+            URI u = new URI(URL_BASE + "/usages/search?path=" + escapeForQuery(currentSegment) + "&signatures=" + escapeForQuery(simplify(signatures)));
+            List<String> files = new ArrayList<String>(WebUtilities.requestStringArrayResponse(u));
+            Collections.sort(files);
+            rootResults.put("files", files);
+
+            results.add(rootResults);
+        }
+
+        configurationData.put("results", results);
+
+        return FreemarkerUtilities.processTemplate("org/netbeans/modules/jackpot30/backend/ui/usages.html", configurationData);
+    }
+
     private static List<Map<String, String>> list() throws URISyntaxException {
         List<Map<String, String>> result = new LinkedList<Map<String, String>>();
 
-        for (String enc : WebUtilities.requestStringArrayResponse(new URI("http://localhost:9998/index/list"))) {
+        for (String enc : WebUtilities.requestStringArrayResponse(new URI(URL_BASE + "/list"))) {
             Map<String, String> rootDesc = new HashMap<String, String>();
             String[] col = enc.split(":", 2);
 
             default: return "unknown";
         }
     }
+
+    private static String[] c = new String[] {"&", "<"}; // NOI18N
+    private static String[] tags = new String[] {"&amp;", "&lt;"}; // NOI18N
+
+    private String translate(String input) {
+        for (int cntr = 0; cntr < c.length; cntr++) {
+            input = input.replaceAll(c[cntr], tags[cntr]);
+        }
+
+        return input;
+    }
+
+    static String simplify(String originalSignature) {
+        if (   !originalSignature.startsWith("METHOD:")
+            && !originalSignature.startsWith("CONSTRUCTOR:")) return originalSignature;
+        StringBuilder target = new StringBuilder(originalSignature.length());
+        int b = 0;
+
+        for (char c : originalSignature.toCharArray()) {
+            if (c == '<') {
+                b++;
+            } else if (c == '>') {
+                b--;
+            } else if (b == '^') {
+                return target.toString();
+            } else if (b == 0) {
+                target.append(c);
+            }
+        }
+
+        return target.delete(target.length() - 1, target.length()).toString();
+    }
 }

remoting/server/web/web.ui/src/org/netbeans/modules/jackpot30/backend/ui/showCode.html

+<html>
+<head>
+    <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
+    <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.18/jquery-ui.min.js"></script>
+    <link href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.18/themes/base/jquery-ui.css" rel="stylesheet" type="text/css">
+    <script type="text/javascript">
+        $(document).ready(function() {
+            var $codeEl = $("#code");
+            var $code = $codeEl.text();
+
+            $codeEl.empty();
+            
+            var $highlights = "${categories}";
+            var $spans = [${spans}];
+            var $current = 0;
+            for (var i = 0; i < $highlights.length; i++ ) {
+                $codeEl.append($('<span class=' + $highlights.charAt(i) + '>' + $code.slice($current, $current+$spans[i]).replace('&', '&amp;').replace('<', '&lt;') + "</span>"));
+                $current += $spans[i];
+            }
+        });
+    </script>
+    <style type="text/css">
+        .K {color: #0000FF; font-weight: bold;}
+        .C {color: #737373;}
+        .H {color: #006F00;}
+        .N {color: #780000;}
+        .S {color: #99009D;}
+    </style>
+</head>
+<body>
+<pre id="code">
+${code}
+</pre>
+</body>
+</html>

remoting/server/web/web.ui/src/org/netbeans/modules/jackpot30/backend/ui/ui-findType.html

 <html>
 <head>
-    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js"></script>
+    <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
+    <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.18/jquery-ui.min.js"></script>
+    <link href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.18/themes/base/jquery-ui.css" rel="stylesheet" type="text/css">
+    <script type="text/javascript">
+        function methodPopup(relativePath, resultFile, signature) {
+            $('#popup').html('<a href="/index/ui/show?path=' + relativePath + '&relative=' + resultFile + '">Source Code</a><br>' +
+                             '<a href="/index/ui/usages?path=' + relativePath + '&signatures=' + signature + '">Usages in the current project</a><br>' +
+                             '<a href="/index/ui/usages?signatures=' + signature + '">Usages in all known projects</a><br>')
+                            .dialog({
+                                title: 'Show'
+                            });
+        }
+        function classPopup(relativePath, resultFile, signature) {
+            $('#popup').html('<a href="/index/ui/show?path=' + relativePath + '&relative=' + resultFile + '">Source Code</a><br>' +
+                             '<a href="/index/ui/usages?path=' + relativePath + '&signatures=' + signature + '">Usages in the current project</a><br>' +
+                             '<a href="/index/ui/usages?signatures=' + signature + '">Usages in all known projects</a><br>')
+                            .dialog({
+                                title: 'Show'
+                            });
+        }
+        function otherPopup(relativePath, resultFile, signature) {
+            $('#popup').html('<a href="/index/ui/show?path=' + relativePath + '&relative=' + resultFile + '">Source Code</a><br>' +
+                             '<a href="/index/ui/usages?path=' + relativePath + '&signatures=' + signature + '">Usages in the current project</a><br>' +
+                             '<a href="/index/ui/usages?signatures=' + signature + '">Usages in all known projects</a><br>')
+                            .dialog({
+                                title: 'Show'
+                            });
+        }
+    </script>
 </head>
 <body>
 <form method="get">
 
 <#if results??>
     Found symbols:<br>
-<!--    <table>-->
         <#list results as result>
-<!--            <tr>-->
-<!--                <td>Icon</td>-->
-<!--                <td><a href="/index/source/cat?path=${selectedPath}&relative=${result.file}">${result.simpleName} in ${result.enclosingFQN}</a></td>-->
                 <img src="/index/icons/${result.icon}" alt="${result.kind}"/>
-                <a href="/index/source/cat?path=${selectedPath}&relative=${result.file}">${result.displayName}</a> in ${result.enclosingFQN}
+                <#if result.kind == "METHOD">
+                    <a href="javascript: methodPopup('${selectedPath}', '${result.file}', '${result.kind}:${result.enclosingFQN}:${result.simpleName}:${result.signature}')">
+                <#elseif result.kind == "CLASS" || result.kind == "INTERFACE" || result.kind == "ENUM" || result.kind == "ANNOTATION_TYPE">
+                    <a href="javascript: classPopup('${selectedPath}', '${result.file}', '${result.kind}:${result.fqn}')">
+                <#else>
+                    <a href="javascript: otherPopup('${selectedPath}', '${result.file}', '${result.kind}:${result.enclosingFQN}:${result.simpleName}')">
+                </#if>
+                ${result.displayName?replace("&", "&amp;")?replace("<", "&lt;")}</a> in ${result.enclosingFQN?replace("&", "&amp;")?replace("<", "&lt;")}
                 <br>
-<!--            </tr>-->
         </#list>
-<!--    </table>-->
 </#if>
 
+<div id="popup"></div>
 </body>
 </html>

remoting/server/web/web.ui/src/org/netbeans/modules/jackpot30/backend/ui/usages.html

+<html>
+<head>
+    <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
+    <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.18/jquery-ui.min.js"></script>
+    <link href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.18/themes/base/jquery-ui.css" rel="stylesheet" type="text/css">
+</head>
+<body>
+    Occurrences of ${elementDisplayName?replace("&", "&amp;")?replace("<", "&lt;")} found in the following files:<br>
+        <ul>
+            <#list results as rootResults>
+                <li>${rootResults.rootDisplayName?replace("&", "&amp;")?replace("<", "&lt;")}<br>
+                    <#list rootResults.files as file>
+                        <img src="/index/icons/javaFile.png" alt="Java File"/>
+                        <a href='/index/ui/show?path=${rootResults.rootPath}&relative=${file}'>${file}</a><br>
+                    </#list>
+                </li>
+            </#list>
+        </ul>
+</body>
+</html>

remoting/server/web/web.ui/test/org/netbeans/modules/jackpot30/backend/ui/UITest.java

         assertEquals("(Function<P, R>, P)", UI.decodeMethodSignature("<P:Lorg/netbeans/modules/java/source/queries/api/Queries;R:Ljava/lang/Object;>(Lorg/netbeans/modules/java/source/queries/api/Function<TP;TR;>;TP;)Lorg/netbeans/modules/java/source/queries/spi/QueriesController$Context<TR;>;;"));
     }
 
+    public void testSimplifySignature() {
+        assertEquals("METHOD:org.netbeans.spi.java.hints.JavaFixUtilities:rewriteFix:(Lorg/netbeans/api/java/source/CompilationInfo;Ljava/lang/String;Lcom/sun/source/util/TreePath;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;[Ljava/lang/String;)Lorg/netbeans/spi/editor/hints/Fix;",
+                     UI.simplify("METHOD:org.netbeans.spi.java.hints.JavaFixUtilities:rewriteFix:(Lorg/netbeans/api/java/source/CompilationInfo;Ljava/lang/String;Lcom/sun/source/util/TreePath;Ljava/lang/String;Ljava/util/Map<Ljava/lang/String;Lcom/sun/source/util/TreePath;>;Ljava/util/Map<Ljava/lang/String;Ljava/util/Collection<Lcom/sun/source/util/TreePath;>;>;Ljava/util/Map<Ljava/lang/String;Ljava/lang/String;>;Ljava/util/Map<Ljava/lang/String;Ljavax/lang/model/type/TypeMirror;>;Ljava/util/Map<Ljava/lang/String;Ljava/lang/String;>;[Ljava/lang/String;)Lorg/netbeans/spi/editor/hints/Fix;;"));
+        assertEquals("METHOD:org.netbeans.modules.java.hints.spiimpl.batch.BatchUtilities:fixDependencies:(Lorg/openide/filesystems/FileObject;Ljava/util/List;Ljava/util/Map;)Z",
+                     UI.simplify("METHOD:org.netbeans.modules.java.hints.spiimpl.batch.BatchUtilities:fixDependencies:(Lorg/openide/filesystems/FileObject;Ljava/util/List<Lorg/netbeans/spi/java/hints/JavaFix;>;Ljava/util/Map<Lorg/netbeans/api/project/Project;Ljava/util/Set<Ljava/lang/String;>;>;)Z;"));
+    }
 }