Support for more advanced UIs (web and non-web).
1.1 --- a/remoting/server/web/web.ui/src/org/netbeans/modules/jackpot30/backend/ui/UI.java Sat Feb 09 21:52:38 2013 +0100
1.2 +++ b/remoting/server/web/web.ui/src/org/netbeans/modules/jackpot30/backend/ui/UI.java Sun Feb 10 14:51:55 2013 +0100
1.3 @@ -199,16 +199,83 @@
1.4 }
1.5
1.6 @GET
1.7 + @Path("/searchSymbol")
1.8 + @Produces("application/javascript")
1.9 + public String searchSymbol(@Context UriInfo uriInfo, @QueryParam("path") List<String> paths, @QueryParam("prefix") String prefix) throws URISyntaxException, IOException, TemplateException {
1.10 + String urlBase = URL_BASE_OVERRIDE != null ? URL_BASE_OVERRIDE : uriInfo.getBaseUri().toString();
1.11 + Map<String, Map<?, ?>> segment2Result = new HashMap<String, Map<?, ?>>();
1.12 +
1.13 + if (paths == null) {
1.14 + Map<String,Map<String, String>> pathsList = list(urlBase);
1.15 + paths = new ArrayList<String>(pathsList.keySet());
1.16 + }
1.17 +
1.18 + for (String path : paths) {
1.19 + URI u = new URI(urlBase + "index/symbol/search?path=" + escapeForQuery(path) + "&prefix=" + escapeForQuery(prefix));
1.20 + @SuppressWarnings("unchecked") //XXX: should not trust something got from the network!
1.21 + Map<String, List<Map<String, Object>>> symbols = Pojson.load(LinkedHashMap.class, u);
1.22 +
1.23 + URI typeSearch = new URI(urlBase + "index/type/search?path=" + escapeForQuery(path) + "&prefix=" + escapeForQuery(prefix));
1.24 + @SuppressWarnings("unchecked") //XXX: should not trust something got from the network!
1.25 + Map<String, List<String>> types = Pojson.load(LinkedHashMap.class, typeSearch);
1.26 +
1.27 + for (Entry<String, List<String>> e : types.entrySet()) {
1.28 + List<Map<String, Object>> thisSourceRootResults = new ArrayList<Map<String, Object>>(e.getValue().size());
1.29 + for (String fqn : e.getValue()) {
1.30 + Map<String, Object> result = new HashMap<String, Object>();
1.31 +
1.32 + result.put("kind", "CLASS");
1.33 + result.put("fqn", fqn);
1.34 +
1.35 + String simpleName = fqn;
1.36 + String enclosingFQN = "";
1.37 +
1.38 + if (simpleName.lastIndexOf('.') > 0) {
1.39 + simpleName = simpleName.substring(simpleName.lastIndexOf('.') + 1);
1.40 + enclosingFQN = fqn.substring(0, fqn.lastIndexOf('.'));
1.41 + }
1.42 +
1.43 + if (simpleName.lastIndexOf('$') > 0) {
1.44 + simpleName = simpleName.substring(simpleName.lastIndexOf('$') + 1);
1.45 + enclosingFQN = fqn.substring(0, fqn.lastIndexOf('$'));
1.46 + }
1.47 +
1.48 + result.put("simpleName", simpleName);
1.49 + result.put("enclosingFQN", enclosingFQN);
1.50 +
1.51 + if (fqn.contains("$")) {
1.52 + fqn = fqn.substring(0, fqn.indexOf("$"));
1.53 + }
1.54 +
1.55 + result.put("file", fqn.replace('.', '/') + ".java");
1.56 +
1.57 + thisSourceRootResults.add(result);
1.58 + }
1.59 +
1.60 + List<Map<String, Object>> putInto = symbols.get(e.getKey());
1.61 +
1.62 + if (putInto == null) symbols.put(e.getKey(), thisSourceRootResults);
1.63 + else putInto.addAll(thisSourceRootResults);
1.64 + }
1.65 +
1.66 + segment2Result.put(path, Collections.singletonMap("found", symbols));
1.67 + }
1.68 +
1.69 + return Pojson.save(segment2Result);
1.70 + }
1.71 +
1.72 + @GET
1.73 @Path("/show")
1.74 @Produces("text/html")
1.75 public String show(@Context UriInfo uriInfo, @QueryParam("path") String segment, @QueryParam("relative") String relative, @QueryParam("highlight") String highlightSpec, @QueryParam("signature") String signature) throws URISyntaxException, IOException, TemplateException, InterruptedException {
1.76 + CategoryStorage category = CategoryStorage.forId(segment);
1.77 String urlBase = URL_BASE_OVERRIDE != null ? URL_BASE_OVERRIDE : uriInfo.getBaseUri().toString();
1.78 URI u = new URI(urlBase + "index/source/cat?path=" + escapeForQuery(segment) + "&relative=" + escapeForQuery(relative));
1.79 String content = WebUtilities.requestStringResponse(u).replace("\r\n", "\n");
1.80 List<Long> highlightSpans = new ArrayList<Long>();
1.81 + CompilationInfo info = ResolveService.parse(segment, relative);
1.82
1.83 if (signature != null) {
1.84 - CompilationInfo info = ResolveService.parse(segment, relative);
1.85 List<long[]> spans = ResolveService.usages(info, signature);
1.86
1.87 for (long[] span : spans) {
1.88 @@ -222,32 +289,39 @@
1.89 }
1.90
1.91 Map<String, Object> configurationData = new HashMap<String, Object>();
1.92 - String[] highlights = colorTokens(segment, relative, highlightSpans);
1.93 + HighlightData highlights = colorTokens(info, segment, relative, highlightSpans);
1.94
1.95 - configurationData.put("spans", highlights[0]);
1.96 - configurationData.put("categories", highlights[1]);
1.97 + configurationData.put("spans", toString(highlights.spans));
1.98 + configurationData.put("categories", toString(highlights.categories));
1.99 configurationData.put("code", translate(content));
1.100 configurationData.put("path", segment);
1.101 + configurationData.put("category", category.getDisplayName());
1.102 configurationData.put("relative", relative);
1.103
1.104 return FreemarkerUtilities.processTemplate("org/netbeans/modules/jackpot30/backend/ui/showCode.html", configurationData);
1.105 }
1.106
1.107 - static String[] colorTokens(String segment, String relative, List<Long> highlight) throws IOException, InterruptedException {
1.108 + @GET
1.109 + @Path("/highlightData")
1.110 + @Produces("application/javascript")
1.111 + public String highlightData(@Context UriInfo uriInfo, @QueryParam("path") String segment, @QueryParam("relative") String relative) throws URISyntaxException, IOException, TemplateException, InterruptedException {
1.112 CompilationInfo info = ResolveService.parse(segment, relative);
1.113 + HighlightData highlights = colorTokens(info, segment, relative, Collections.<Long>emptyList());
1.114 +
1.115 + return Pojson.save(highlights);
1.116 + }
1.117 +
1.118 + static HighlightData colorTokens(CompilationInfo info, String segment, String relative, List<Long> highlight) throws IOException, InterruptedException {
1.119 TokenSequence<?> ts = info.getTokenHierarchy().tokenSequence(JavaTokenId.language());
1.120 Map<Token, Coloring> semanticHighlights = SemanticHighlighter.computeHighlights(info, new TokenList(info, ts, new AtomicBoolean()));
1.121 - StringBuilder spans = new StringBuilder();
1.122 - StringBuilder cats = new StringBuilder();
1.123 + List<Long> spans = new ArrayList<Long>(ts.tokenCount());
1.124 + List<String> cats = new ArrayList<String>(ts.tokenCount());
1.125 long currentOffset = 0;
1.126 boolean cont = false;
1.127
1.128 ts.moveStart();
1.129
1.130 while (cont || ts.moveNext()) {
1.131 - if (spans.length() > 0) spans.append(", ");
1.132 - if (cats.length() > 0) cats.append(", ");
1.133 -
1.134 long endOffset = ts.offset() + ts.token().length();
1.135 boolean foundHighlight = false;
1.136
1.137 @@ -277,13 +351,17 @@
1.138 }
1.139 }
1.140
1.141 - spans.append(endOffset - currentOffset);
1.142 + spans.add(endOffset - currentOffset);
1.143 String category = ts.token().id().primaryCategory();
1.144
1.145 if ("keyword-directive".equals(category)) {
1.146 category = "keyword";
1.147 }
1.148
1.149 + if ("literal".equals(category)) {
1.150 + category = "keyword";
1.151 + }
1.152 +
1.153 if (foundHighlight) {
1.154 if (!category.isEmpty()) category += " ";
1.155 category += "highlight";
1.156 @@ -298,15 +376,32 @@
1.157 }
1.158 }
1.159
1.160 - cats.append(category);
1.161 + cats.add(category);
1.162
1.163 currentOffset = endOffset;
1.164 }
1.165
1.166 - return new String[] {
1.167 - spans.toString(),
1.168 - cats.toString()
1.169 - };
1.170 + return new HighlightData(cats, spans);
1.171 + }
1.172 +
1.173 + private static String toString(List<?> list) {
1.174 + StringBuilder result = new StringBuilder();
1.175 +
1.176 + for (Object o : list) {
1.177 + if (result.length() > 0) result.append(", ");
1.178 + result.append(String.valueOf(o));
1.179 + }
1.180 +
1.181 + return result.toString();
1.182 + }
1.183 +
1.184 + private static final class HighlightData {
1.185 + List<String> categories;
1.186 + List<Long> spans;
1.187 + public HighlightData(List<String> cats, List<Long> spans) {
1.188 + this.categories = cats;
1.189 + this.spans = spans;
1.190 + }
1.191 }
1.192
1.193 //XXX: usages on fields do not work because the field signature in the index contain also the field type
1.194 @@ -434,6 +529,99 @@
1.195 public Object compute(String segment) throws URISyntaxException, TemplateException, IOException;
1.196 }
1.197
1.198 + @GET
1.199 + @Path("/searchUsages")
1.200 + @Produces("application/javascript")
1.201 + public String searchUsages(@Context UriInfo uriInfo, @QueryParam("path") List<String> paths, @QueryParam("signature") final String signature) throws URISyntaxException, IOException, TemplateException {
1.202 + String urlBase = URL_BASE_OVERRIDE != null ? URL_BASE_OVERRIDE : uriInfo.getBaseUri().toString();
1.203 + Map<String, Map<?, ?>> segment2Result = new HashMap<String, Map<?, ?>>();
1.204 +
1.205 + if (paths == null) {
1.206 + Map<String,Map<String, String>> pathsList = list(urlBase);
1.207 + paths = new ArrayList<String>(pathsList.keySet());
1.208 + }
1.209 +
1.210 + for (String path : paths) {
1.211 + Map<String, Map<String, Object>> usageFile2Flags = new HashMap<String, Map<String, Object>>();
1.212 +
1.213 + URI usagesURI = new URI(urlBase + "index/usages/search?path=" + escapeForQuery(path) + "&signatures=" + escapeForQuery(signature));
1.214 + List<String> files = new ArrayList<String>(WebUtilities.requestStringArrayResponse(usagesURI));
1.215 +
1.216 + for (String file : files) {
1.217 + Map<String, Object> flags = usageFile2Flags.get(file);
1.218 +
1.219 + if (flags == null) {
1.220 + usageFile2Flags.put(file, flags = new HashMap<String, Object>());
1.221 + }
1.222 +
1.223 + flags.put("usage", "true");
1.224 + }
1.225 +
1.226 + if (signature.startsWith("CLASS:") || signature.startsWith("INTERFACE:") || signature.startsWith("ENUM:") || signature.startsWith("ANNOTATION_TYPE:")) {
1.227 + final String type = strip(signature, "CLASS:", "INTERFACE:", "ENUM:", "ANNOTATION_TYPE:");
1.228 + URI u = new URI(urlBase + "index/implements/search?path=" + escapeForQuery(path) + "&type=" + escapeForQuery(type));
1.229 + Map<String, List<Map<String, String>>> data = Pojson.load(HashMap.class, u);
1.230 + for (Entry<String, List<Map<String, String>>> relpath2ImplementorsE : data.entrySet()) {
1.231 + for (Map<String, String> implementorData : relpath2ImplementorsE.getValue()) {
1.232 + String file = implementorData.get("file");
1.233 + Map<String, Object> flags = usageFile2Flags.get(file);
1.234 +
1.235 + if (flags == null) {
1.236 + usageFile2Flags.put(file, flags = new HashMap<String, Object>());
1.237 + }
1.238 +
1.239 + List<String> implementors = (List<String>) flags.get("subtypes");
1.240 +
1.241 + if (implementors == null) {
1.242 + flags.put("subtypes", implementors = new ArrayList<String>());
1.243 + }
1.244 +
1.245 + implementors.add(implementorData.get("class"));
1.246 + }
1.247 + }
1.248 + } else if (signature.startsWith("METHOD:")) {
1.249 + URI u = new URI(urlBase + "index/implements/search?path=" + escapeForQuery(path) + "&method=" + escapeForQuery(signature));
1.250 + Map<String, List<Map<String, String>>> data = Pojson.load(HashMap.class, u);
1.251 + for (Entry<String, List<Map<String, String>>> relpath2ImplementorsE : data.entrySet()) {
1.252 + for (Map<String, String> implementorData : relpath2ImplementorsE.getValue()) {
1.253 + String file = implementorData.get("file");
1.254 + Map<String, Object> flags = usageFile2Flags.get(file);
1.255 +
1.256 + if (flags == null) {
1.257 + usageFile2Flags.put(file, flags = new HashMap<String, Object>());
1.258 + }
1.259 +
1.260 + List<String> overridersParents = (List<String>) flags.get("overridersParents");
1.261 +
1.262 + if (overridersParents == null) {
1.263 + flags.put("overridersParents", overridersParents = new ArrayList<String>());
1.264 + }
1.265 +
1.266 + overridersParents.add(implementorData.get("enclosingFQN"));
1.267 + }
1.268 + }
1.269 + }
1.270 +
1.271 + segment2Result.put(path, usageFile2Flags);
1.272 + }
1.273 +
1.274 + return Pojson.save(segment2Result);
1.275 + }
1.276 +
1.277 + @GET
1.278 + @Path("/localUsages")
1.279 + @Produces("application/javascript")
1.280 + public String localUsages(@Context UriInfo uriInfo, @QueryParam("path") String segment, @QueryParam("relative") String relative, @QueryParam("signature") final String signature, @QueryParam("usages") boolean usages) throws URISyntaxException, IOException, TemplateException, InterruptedException {
1.281 + final CompilationInfo info = ResolveService.parse(segment, relative);
1.282 + List<long[]> result = new ArrayList<long[]>();
1.283 +
1.284 + for (long[] span : ResolveService.usages(info, signature)) {
1.285 + result.add(new long[] {span[2], span[3]});
1.286 + }
1.287 +
1.288 + return Pojson.save(result);
1.289 + }
1.290 +
1.291 private static String elementDisplayName(String signatures) {
1.292 StringBuilder elementDisplayName = new StringBuilder();
1.293 String[] splitSignature = signatures.split(":");
1.294 @@ -731,6 +919,7 @@
1.295 break;
1.296 }
1.297 result.put("menu", menu);
1.298 + result.put("signature", signature[0]);
1.299 }
1.300 } else {
1.301 if (targetPosition[0] != (-2)) {