8260581: IGV: enhance node search

Allow users to search by node id or name by default, show partial matches when
searching for a specific property, show 'All N matching nodes' entry only if
relevant, and rank results by level of matching.

Co-authored-by: Christian Hagedorn <chagedorn@openjdk.org>
Reviewed-by: chagedorn, vlivanov, xliu
This commit is contained in:
Roberto Castañeda Lozano 2021-02-03 11:11:57 +00:00 committed by Vladimir Ivanov
parent 90376156be
commit ae2c5f07ce
3 changed files with 95 additions and 24 deletions

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2008, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -274,6 +274,13 @@ public class Figure extends Properties.Entity implements Source.Provider, Vertex
public String[] getLines() { public String[] getLines() {
if (lines == null) { if (lines == null) {
updateLines(); updateLines();
// Set the "label" property of each input node, so that by default
// search is done on the node label (without line breaks). See also
// class NodeQuickSearch in the View module.
for (InputNode n : getSource().getSourceNodes()) {
String label = resolveString(diagram.getNodeText(), n.getProperties());
n.getProperties().setProperty("label", label);
}
} }
return lines; return lines;
} }

View file

@ -1,2 +1,2 @@
javac.source=1.7 javac.source=1.8
javac.compilerargs=-Xlint -Xlint:-serial javac.compilerargs=-Xlint -Xlint:-serial

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2008, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -29,6 +29,7 @@ import com.sun.hotspot.igv.data.Properties;
import com.sun.hotspot.igv.data.Properties.RegexpPropertyMatcher; import com.sun.hotspot.igv.data.Properties.RegexpPropertyMatcher;
import com.sun.hotspot.igv.data.services.InputGraphProvider; import com.sun.hotspot.igv.data.services.InputGraphProvider;
import com.sun.hotspot.igv.util.LookupHistory; import com.sun.hotspot.igv.util.LookupHistory;
import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@ -46,7 +47,7 @@ import org.openide.NotifyDescriptor.Message;
*/ */
public class NodeQuickSearch implements SearchProvider { public class NodeQuickSearch implements SearchProvider {
private static final String DEFAULT_PROPERTY = "name"; private static final String DEFAULT_PROPERTY = "label";
/** /**
* Method is called by infrastructure when search operation was requested. * Method is called by infrastructure when search operation was requested.
@ -66,18 +67,17 @@ public class NodeQuickSearch implements SearchProvider {
final String[] parts = query.split("=", 2); final String[] parts = query.split("=", 2);
String name; String name;
String rawValue;
String value; String value;
if (parts.length == 1) { if (parts.length == 1) {
name = DEFAULT_PROPERTY; name = DEFAULT_PROPERTY;
value = ".*" + Pattern.quote(parts[0]) + ".*"; rawValue = parts[0];
value = ".*" + Pattern.quote(rawValue) + ".*";
} else { } else {
name = parts[0]; name = parts[0];
value = parts[1]; rawValue = parts[1];
} value = (rawValue.isEmpty() ? "" : Pattern.quote(rawValue)) + ".*";
if (value.isEmpty()) {
value = ".*";
} }
final InputGraphProvider p = LookupHistory.getLast(InputGraphProvider.class); final InputGraphProvider p = LookupHistory.getLast(InputGraphProvider.class);
@ -109,7 +109,12 @@ public class NodeQuickSearch implements SearchProvider {
if (matches != null) { if (matches != null) {
final Set<InputNode> set = new HashSet<>(matches); final Set<InputNode> set = new HashSet<>(matches);
final InputGraph theGraph = p.getGraph() != matchGraph ? matchGraph : null; final InputGraph theGraph = p.getGraph() != matchGraph ? matchGraph : null;
response.addResult(new Runnable() { // Show "All N matching nodes" entry only if 1) there are
// multiple matches and 2) the query does not only contain
// digits (it is rare to select all nodes whose id contains a
// certain subsequence of digits).
if (matches.size() > 1 && !rawValue.matches("\\d+")) {
if (!response.addResult(new Runnable() {
@Override @Override
public void run() { public void run() {
final EditorTopComponent comp = EditorTopComponent.getActive(); final EditorTopComponent comp = EditorTopComponent.getActive();
@ -123,11 +128,21 @@ public class NodeQuickSearch implements SearchProvider {
} }
}, },
"All " + matches.size() + " matching nodes (" + name + "=" + value + ")" + (theGraph != null ? " in " + theGraph.getName() : "") "All " + matches.size() + " matching nodes (" + name + "=" + value + ")" + (theGraph != null ? " in " + theGraph.getName() : "")
); )) {
return;
}
}
// Rank the matches.
Collections.sort(matches,
(InputNode a, InputNode b) ->
compareByRankThenNumVal(rawValue,
a.getProperties().get(name),
b.getProperties().get(name)));
// Single matches // Single matches
for (final InputNode n : matches) { for (final InputNode n : matches) {
response.addResult(new Runnable() { if (!response.addResult(new Runnable() {
@Override @Override
public void run() { public void run() {
final EditorTopComponent comp = EditorTopComponent.getActive(); final EditorTopComponent comp = EditorTopComponent.getActive();
@ -143,7 +158,9 @@ public class NodeQuickSearch implements SearchProvider {
} }
}, },
n.getProperties().get(name) + " (" + n.getId() + " " + n.getProperties().get("name") + ")" + (theGraph != null ? " in " + theGraph.getName() : "") n.getProperties().get(name) + " (" + n.getId() + " " + n.getProperties().get("name") + ")" + (theGraph != null ? " in " + theGraph.getName() : "")
); )) {
return;
}
} }
} }
} else { } else {
@ -173,4 +190,51 @@ public class NodeQuickSearch implements SearchProvider {
} }
return null; return null;
} }
/**
* Compare two matches for a given query, first by rank (see rankMatch()
* below) and then by numeric value, if applicable.
*/
private int compareByRankThenNumVal(String qry, String prop1, String prop2) {
int key1 = rankMatch(qry, prop1);
int key2 = rankMatch(qry, prop2);
if (key1 == key2) {
// If the matches have the same rank, compare the numeric values of
// their first words, if applicable.
try {
key1 = Integer.parseInt(prop1.split("\\W+")[0]);
key2 = Integer.parseInt(prop2.split("\\W+")[0]);
} catch (Exception e) {
// Not applicable, return equality value.
return 0;
}
}
return Integer.compare(key1, key2);
}
/**
* Rank a match by splitting the property into words. Full matches of a word
* rank highest, followed by partial matches at the word start, followed by
* the rest of matches in increasing size of the partially matched word, for
* example:
*
* rank("5", "5 AddI") = 1 (full match of first word)
* rank("5", "554 MulI") = 2 (start match of first word)
* rank("5", "25 AddL") = 3 (middle match of first word with excess 1)
* rank("5", "253 AddL") = 4 (middle match of first word with excess 2)
*/
private int rankMatch(String qry, String prop) {
String query = qry.toLowerCase();
String property = prop.toLowerCase();
for (String component : property.split("\\W+")) {
if (component.equals(query)) {
return 1;
} else if (component.startsWith(query)) {
return 2;
} else if (component.contains(query)) {
return component.length() - query.length() + 2;
}
}
return Integer.MAX_VALUE;
}
} }