11/*
2- * Copyright (c) 2008, 2016 , Oracle and/or its affiliates. All rights reserved.
2+ * Copyright (c) 2008, 2021 , Oracle and/or its affiliates. All rights reserved.
33 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44 *
55 * This code is free software; you can redistribute it and/or modify it
2929import com .sun .hotspot .igv .data .Properties .RegexpPropertyMatcher ;
3030import com .sun .hotspot .igv .data .services .InputGraphProvider ;
3131import com .sun .hotspot .igv .util .LookupHistory ;
32+ import java .util .Collections ;
3233import java .util .HashSet ;
3334import java .util .List ;
3435import java .util .Set ;
4647 */
4748public class NodeQuickSearch implements SearchProvider {
4849
49- private static final String DEFAULT_PROPERTY = "name " ;
50+ private static final String DEFAULT_PROPERTY = "label " ;
5051
5152 /**
5253 * Method is called by infrastructure when search operation was requested.
@@ -66,18 +67,17 @@ public void evaluate(SearchRequest request, SearchResponse response) {
6667 final String [] parts = query .split ("=" , 2 );
6768
6869 String name ;
70+ String rawValue ;
6971 String value ;
7072
7173 if (parts .length == 1 ) {
7274 name = DEFAULT_PROPERTY ;
73- value = ".*" + Pattern .quote (parts [0 ]) + ".*" ;
75+ rawValue = parts [0 ];
76+ value = ".*" + Pattern .quote (rawValue ) + ".*" ;
7477 } else {
7578 name = parts [0 ];
76- value = parts [1 ];
77- }
78-
79- if (value .isEmpty ()) {
80- value = ".*" ;
79+ rawValue = parts [1 ];
80+ value = (rawValue .isEmpty () ? "" : Pattern .quote (rawValue )) + ".*" ;
8181 }
8282
8383 final InputGraphProvider p = LookupHistory .getLast (InputGraphProvider .class );
@@ -109,25 +109,40 @@ public void evaluate(SearchRequest request, SearchResponse response) {
109109 if (matches != null ) {
110110 final Set <InputNode > set = new HashSet <>(matches );
111111 final InputGraph theGraph = p .getGraph () != matchGraph ? matchGraph : null ;
112- response .addResult (new Runnable () {
113- @ Override
114- public void run () {
115- final EditorTopComponent comp = EditorTopComponent .getActive ();
116- if (comp != null ) {
117- if (theGraph != null ) {
118- comp .getDiagramModel ().selectGraph (theGraph );
112+ // Show "All N matching nodes" entry only if 1) there are
113+ // multiple matches and 2) the query does not only contain
114+ // digits (it is rare to select all nodes whose id contains a
115+ // certain subsequence of digits).
116+ if (matches .size () > 1 && !rawValue .matches ("\\ d+" )) {
117+ if (!response .addResult (new Runnable () {
118+ @ Override
119+ public void run () {
120+ final EditorTopComponent comp = EditorTopComponent .getActive ();
121+ if (comp != null ) {
122+ if (theGraph != null ) {
123+ comp .getDiagramModel ().selectGraph (theGraph );
124+ }
125+ comp .setSelectedNodes (set );
126+ comp .requestActive ();
119127 }
120- comp .setSelectedNodes (set );
121- comp .requestActive ();
122128 }
129+ },
130+ "All " + matches .size () + " matching nodes (" + name + "=" + value + ")" + (theGraph != null ? " in " + theGraph .getName () : "" )
131+ )) {
132+ return ;
123133 }
124- },
125- "All " + matches .size () + " matching nodes (" + name + "=" + value + ")" + (theGraph != null ? " in " + theGraph .getName () : "" )
126- );
134+ }
135+
136+ // Rank the matches.
137+ Collections .sort (matches ,
138+ (InputNode a , InputNode b ) ->
139+ compareByRankThenNumVal (rawValue ,
140+ a .getProperties ().get (name ),
141+ b .getProperties ().get (name )));
127142
128143 // Single matches
129144 for (final InputNode n : matches ) {
130- response .addResult (new Runnable () {
145+ if (! response .addResult (new Runnable () {
131146 @ Override
132147 public void run () {
133148 final EditorTopComponent comp = EditorTopComponent .getActive ();
@@ -143,7 +158,9 @@ public void run() {
143158 }
144159 },
145160 n .getProperties ().get (name ) + " (" + n .getId () + " " + n .getProperties ().get ("name" ) + ")" + (theGraph != null ? " in " + theGraph .getName () : "" )
146- );
161+ )) {
162+ return ;
163+ }
147164 }
148165 }
149166 } else {
@@ -173,4 +190,51 @@ public void run() {
173190 }
174191 return null ;
175192 }
193+
194+ /**
195+ * Compare two matches for a given query, first by rank (see rankMatch()
196+ * below) and then by numeric value, if applicable.
197+ */
198+ private int compareByRankThenNumVal (String qry , String prop1 , String prop2 ) {
199+ int key1 = rankMatch (qry , prop1 );
200+ int key2 = rankMatch (qry , prop2 );
201+ if (key1 == key2 ) {
202+ // If the matches have the same rank, compare the numeric values of
203+ // their first words, if applicable.
204+ try {
205+ key1 = Integer .parseInt (prop1 .split ("\\ W+" )[0 ]);
206+ key2 = Integer .parseInt (prop2 .split ("\\ W+" )[0 ]);
207+ } catch (Exception e ) {
208+ // Not applicable, return equality value.
209+ return 0 ;
210+ }
211+ }
212+ return Integer .compare (key1 , key2 );
213+ }
214+
215+ /**
216+ * Rank a match by splitting the property into words. Full matches of a word
217+ * rank highest, followed by partial matches at the word start, followed by
218+ * the rest of matches in increasing size of the partially matched word, for
219+ * example:
220+ *
221+ * rank("5", "5 AddI") = 1 (full match of first word)
222+ * rank("5", "554 MulI") = 2 (start match of first word)
223+ * rank("5", "25 AddL") = 3 (middle match of first word with excess 1)
224+ * rank("5", "253 AddL") = 4 (middle match of first word with excess 2)
225+ */
226+ private int rankMatch (String qry , String prop ) {
227+ String query = qry .toLowerCase ();
228+ String property = prop .toLowerCase ();
229+ for (String component : property .split ("\\ W+" )) {
230+ if (component .equals (query )) {
231+ return 1 ;
232+ } else if (component .startsWith (query )) {
233+ return 2 ;
234+ } else if (component .contains (query )) {
235+ return component .length () - query .length () + 2 ;
236+ }
237+ }
238+ return Integer .MAX_VALUE ;
239+ }
176240}
0 commit comments