@@ -18,8 +18,13 @@ public class WebQueryParser
1818 private static final String WHERE = "WHERE" ;
1919
2020 private static final String OPEN_BRACKET = "(" ;
21+ private static final char OPEN_BRACKET_C = '(' ;
2122 private static final String CLOSE_BRACKET = ")" ;
23+ private static final char CLOSE_BRACKET_C = ')' ;
2224 private static final String COMMA = "," ;
25+ private static final char COMMA_C = ',' ;
26+
27+ private static final char COLON_C = ':' ;
2328
2429 private static final String AND = "AND" ;
2530 private static final String OR = "OR" ;
@@ -36,6 +41,9 @@ public class WebQueryParser
3641 private static final String STARTS = "STARTS" ;
3742 private static final String CONTAINS = "CONTAINS" ;
3843 private static final String CONTAINS2 = "~=" ;
44+
45+ // operator token is a run of any of the following:
46+ private static final char [] OPERATORS = new char []{'=' , '<' , '>' , '~' , '!' };
3947
4048 public static WebQuery parse (String search , WebQuery query )
4149 {
@@ -241,7 +249,7 @@ else if (start.equalsIgnoreCase(SELECT))
241249 }
242250 while (StringUtils .equals (peek (t ), COMMA ));
243251
244- query .fetch (selects . stream (). collect ( Collectors . joining ( "," ) ));
252+ query .fetch (String . join ( "," , selects ));
245253
246254 // Allow EOF, EXPAND, WHERE or ORDER
247255 final String token = peek (t );
@@ -279,6 +287,9 @@ else if (token.equalsIgnoreCase(ORDER))
279287
280288 if (operator .equalsIgnoreCase (IS ))
281289 { // Unary expression
290+ if (notted )
291+ throw new IllegalArgumentException ("Unexpected symbol NOT before " + operator );
292+
282293 final boolean isNotNullExpr = takeIf (t , NOT );
283294
284295 expect (t , NULL );
@@ -349,54 +360,66 @@ else if (operator.equalsIgnoreCase(CONTAINS) || operator.equals(CONTAINS2))
349360 else
350361 group .notContains (start , val );
351362 }
352- else if (operator .equalsIgnoreCase ("eqref" ))
353- {
354- if (!notted )
355- group .eqRef (start , val );
356- else
357- group .neqRef (start , val );
358- }
359- else if (operator .equalsIgnoreCase ("neqref" ))
360- {
361- if (!notted )
362- group .neqRef (start , val );
363- else
364- group .eqRef (start , val );
365- }
366- else if (operator .equalsIgnoreCase ("leref" ))
363+ else if (operator .length () >= 5 && StringUtils .endsWithIgnoreCase (operator , "ref" ))
367364 {
368- if (notted )
369- throw new IllegalArgumentException ("Unexpected symbol NOT before " + operator );
365+ if (operator .equalsIgnoreCase ("eqref" ))
366+ {
367+ if (!notted )
368+ group .eqRef (start , val );
369+ else
370+ group .neqRef (start , val );
371+ }
372+ else if (operator .equalsIgnoreCase ("neqref" ))
373+ {
374+ if (!notted )
375+ group .neqRef (start , val );
376+ else
377+ group .eqRef (start , val );
378+ }
379+ else if (operator .equalsIgnoreCase ("leref" ))
380+ {
381+ if (notted )
382+ throw new IllegalArgumentException ("Unexpected symbol NOT before " + operator );
370383
371- group .leRef (start , val );
372- }
373- else if (operator .equalsIgnoreCase ("geref" ))
374- {
375- if (notted )
376- throw new IllegalArgumentException ("Unexpected symbol NOT before " + operator );
384+ group .leRef (start , val );
385+ }
386+ else if (operator .equalsIgnoreCase ("geref" ))
387+ {
388+ if (notted )
389+ throw new IllegalArgumentException ("Unexpected symbol NOT before " + operator );
377390
378- group .geRef (start , val );
379- }
380- else if (operator .equalsIgnoreCase ("ltref" ))
381- {
382- if (notted )
383- throw new IllegalArgumentException ("Unexpected symbol NOT before " + operator );
391+ group .geRef (start , val );
392+ }
393+ else if (operator .equalsIgnoreCase ("ltref" ))
394+ {
395+ if (notted )
396+ throw new IllegalArgumentException ("Unexpected symbol NOT before " + operator );
384397
385- group .ltRef (start , val );
386- }
387- else if (operator .equalsIgnoreCase ("gtref" ))
388- {
389- if (notted )
390- throw new IllegalArgumentException ("Unexpected symbol NOT before " + operator );
398+ group .ltRef (start , val );
399+ }
400+ else if (operator .equalsIgnoreCase ("gtref" ))
401+ {
402+ if (notted )
403+ throw new IllegalArgumentException ("Unexpected symbol NOT before " + operator );
391404
392- group .gtRef (start , val );
405+ group .gtRef (start , val );
406+ }
407+ else
408+ {
409+ throw new IllegalArgumentException ("Unknown operator: " + operator );
410+ }
393411 }
394412 else
395413 {
396414 if (notted )
397415 throw new IllegalArgumentException ("Unexpected symbol NOT before " + operator );
398416
399- switch (operator .toLowerCase (Locale .ROOT ))
417+ // Allow case-insensitive matching for "eq", but leave "=" alone
418+ final String lcop = operator .length () == 1 || !Character .isAlphabetic (operator .charAt (0 )) ?
419+ operator :
420+ operator .toLowerCase (Locale .ROOT );
421+
422+ switch (lcop )
400423 {
401424 case "=" :
402425 case "eq" :
@@ -515,8 +538,6 @@ private static List<String> tokenise(final String search)
515538 {
516539 List <String > tokens = new ArrayList <>();
517540
518- char [] operators = new char []{'=' , '<' , '>' , '~' , '!' };
519-
520541 for (int i = 0 ; i < search .length (); i ++)
521542 {
522543 try
@@ -538,11 +559,11 @@ private static List<String> tokenise(final String search)
538559
539560 tokens .add (str );
540561 }
541- else if (c == OPEN_BRACKET . charAt ( 0 ) || c == CLOSE_BRACKET . charAt ( 0 ) || c == COMMA . charAt ( 0 ) )
562+ else if (c == OPEN_BRACKET_C || c == CLOSE_BRACKET_C || c == COMMA_C )
542563 {
543564 tokens .add (Character .toString (c ));
544565 }
545- else if (Character .isJavaIdentifierPart (c ))
566+ else if (Character .isJavaIdentifierPart (c ) || c == COLON_C )
546567 {
547568 final int start = i ;
548569 // Search for: EOF, next char that is not isJavaIdentifierPart, a dot colon or square brackets
@@ -563,7 +584,7 @@ else if (Character.isJavaIdentifierPart(c))
563584 tokens .add (search .substring (start , i + 1 ));
564585 }
565586 }
566- else if (( c == '-' && tokenPeekIs ( search , i , '-' )) || ( c == '/' && tokenPeekIs ( search , i , '/' ) ))
587+ else if (tokenTupleMatch ( search , i , c , '-' , '-' ) || tokenTupleMatch ( search , i , c , '/' , '/' ))
567588 {
568589 i ++;
569590
@@ -576,7 +597,7 @@ else if ((c == '-' && tokenPeekIs(search, i, '-')) || (c == '/' && tokenPeekIs(s
576597 // Skip over all data
577598 i = endPos ;
578599 }
579- else if (c == '/' && tokenPeekIs (search , i , '*' ))
600+ else if (tokenTupleMatch (search ,i , c , '/' , '*' ))
580601 {
581602 i ++;
582603
@@ -591,11 +612,11 @@ else if (c == '/' && tokenPeekIs(search, i, '*'))
591612
592613 i = endPos +1 ;
593614 }
594- else if (ArrayUtils .indexOf (operators , c ) != -1 )
615+ else if (ArrayUtils .indexOf (OPERATORS , c ) != -1 )
595616 {
596617 final int start = i ;
597- // Search for: EOF, next char that is not isJavaIdentifierPart / ":"
598- while (i < search .length () && ArrayUtils .indexOf (operators , search .charAt (i )) != -1 )
618+ // Consume a run of operators, stopping if we hit EOF
619+ while (i < search .length () && ArrayUtils .indexOf (OPERATORS , search .charAt (i )) != -1 )
599620 {
600621 i ++;
601622 }
@@ -628,12 +649,25 @@ else if (ArrayUtils.indexOf(operators, c) != -1)
628649 }
629650
630651
631- public static boolean tokenPeekIs (final String s , final int i , final char c )
652+ /**
653+ * Tests if the current+next token matches (a,b)
654+ *
655+ * @param s source string
656+ * @param i current token position
657+ * @param curr the current token
658+ * @param a desired first token
659+ * @param b desired second token
660+ * @return
661+ */
662+ private static boolean tokenTupleMatch (final String s , final int i , final char curr , final char a , final char b )
632663 {
633- if (s .length () > i )
634- return c == s .charAt (i + 1 );
635- else
636- return false ;
664+ return
665+ // current token matches
666+ curr == a &&
667+ // we are not at EOF
668+ s .length () > i &&
669+ // the next token matches
670+ b == s .charAt (i + 1 );
637671 }
638672
639673 public static boolean isBareWordPart (final char c )
0 commit comments