001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.objecttools;
014
015import static org.apache.juneau.internal.StateMachineState.*;
016
017import java.util.*;
018
019import org.apache.juneau.*;
020import org.apache.juneau.common.internal.*;
021import org.apache.juneau.internal.*;
022
023/**
024 * Number matcher factory for the {@link ObjectSearcher} class.
025 *
026 * <p>
027 *    The class provides searching based on the following patterns:
028 * </p>
029 * <ul>
030 *    <li><js>"property=1"</js> - A single number
031 *    <li><js>"property=1 2"</js> - Multiple OR'ed numbers
032 *    <li><js>"property=-1 -2"</js> - Multiple OR'ed negative numbers
033 *    <li><js>"property=1-2"</js>,<js>"property=-2--1"</js>  - A range of numbers (whitespace ignored)
034 *    <li><js>"property=1-2 4-5"</js> - Multiple OR'ed ranges
035 *    <li><js>"property=&lt;1"</js>,<js>"property=&lt;=1"</js>,<js>"property=&gt;1"</js>,<js>"property=&gt;=1"</js> - Open-ended ranges
036 *    <li><js>"property=!1"</js>,<js>"property=!1-2"</js> - Negation
037 * </ul>
038 *
039 * <h5 class='section'>See Also:</h5><ul>
040 *    <li class='link'><a class="doclink" href="../../../../index.html#jm.ObjectTools">Overview &gt; juneau-marshall &gt; Object Tools</a>
041 * </ul>
042 */
043public class NumberMatcherFactory extends MatcherFactory {
044
045   /**
046    * Default reusable matcher.
047    */
048   public static final NumberMatcherFactory DEFAULT = new NumberMatcherFactory();
049
050   @Override
051   public boolean canMatch(ClassMeta<?> cm) {
052      return cm.isNumber();
053   }
054
055   @Override
056   public AbstractMatcher create(String pattern) {
057      return new NumberMatcher(pattern);
058   }
059
060   /**
061    * A construct representing a single search pattern.
062    */
063   private static class NumberMatcher extends AbstractMatcher {
064      NumberRange[] numberRanges;
065      String pattern;
066      private static final AsciiSet
067         SNUM = AsciiSet.create("-0123456789."),
068         NUM = AsciiSet.create("0123456789."),
069         WS = AsciiSet.create(" \t");
070
071      public NumberMatcher(String s) {
072
073         s = s.trim();
074         pattern = s;
075
076         List<NumberRange> l = new LinkedList<>();
077
078         // Possible patterns:
079         // 123, >123, <123, >=123, <=123, >-123, >=-123, 123-456, -123--456, !123, !123-456, 123 - 456 (one token), 123 -456 (two separate tokens)
080
081         // Possible states:
082         // S01 = Looking for start (WS=S01, [!]=S01, [>]=S02, [<]=S03, SNUM=S06)
083         // S02 = Found [>], looking for [=]/SNUM ([=]=S04, WS=S05, SNUM=S06)
084         // S03 = Found [<], looking for [=]/SNUM ([=]=S05, WS=S05, SNUM=S06)
085         // S04 = Found [=], looking for SNUN (WS=S05, SNUM=S06)
086         // S05 = Found [... ], looking for SNUM (SNUM=S06)
087         // S06 = Found [1], looking for [-]/WS (WS=S07, [-]=S08)
088         // S07 = Found [123 ], looking for [-]/SNUM (if -, could be 123 - 456 or 123 -456) ([-]=S09, SNUM=S07)
089         // S08 = Found [123-], looking for SNUM (Could be 123- 456 or 123-456) (SNUM=S11)
090         // S09 = Found [123 -], looking for WS/SNUM (If WS, then it's 123 - 456, otherwise 123 -456) (WS=S10, SNUM=S06)
091         // S10 = Found [123 - ], looking for SNUM (SNUM=S12)
092         // S11 = Found [123 - 4], looking for WS (WS=S01)
093
094         StateMachineState state = S01;
095         int mark = 0;
096         boolean isNot = false;
097         Equality eq = Equality.NONE;
098         Integer n1 = null, n2 = null;
099
100         int i;
101         for (i = 0; i < s.length(); i++) {
102            char c = s.charAt(i);
103            if (state == S01) {
104               if (c == '!') {
105                  isNot = true;
106               } else if (WS.contains(c)) {
107                  state = S01;
108               } else if (c == '>') {
109                  state = S02;
110                  eq = Equality.GT;
111               } else if (c == '<') {
112                  state = S03;
113                  eq = Equality.LT;
114               } else if (SNUM.contains(c)) {
115                  state = S06;
116                  mark = i;
117               } else {
118                  break;
119               }
120            } else if (state == S02) {
121               if (c == '=') {
122                  state = S04;
123                  eq = Equality.GTE;
124               } else if (WS.contains(c)) {
125                  state = S05;
126               } else if (SNUM.contains(c)) {
127                  state = S06;
128                  mark = i;
129               } else {
130                  break;
131               }
132            } else if (state == S03) {
133               if (c == '=') {
134                  state = S04;
135                  eq = Equality.LTE;
136               } else if (WS.contains(c)) {
137                  state = S05;
138               } else if (SNUM.contains(c)) {
139                  state = S06;
140                  mark = i;
141               } else {
142                  break;
143               }
144            } else if (state == S04) {
145               if (WS.contains(c)) {
146                  state = S05;
147               } else if (SNUM.contains(c)) {
148                  mark = i;
149                  state = S06;
150               } else {
151                  break;
152               }
153            } else if (state == S05) {
154               if (WS.contains(c)) {
155                  state = S05;
156               } else if (SNUM.contains(c)) {
157                  state = S06;
158                  mark = i;
159               } else {
160                  break;
161               }
162            } else if (state == S06) {
163               if (NUM.contains(c)) {
164                  state = S06;
165               } else if (WS.contains(c)) {
166                  state = S07;
167                  n1 = Integer.parseInt(s.substring(mark, i));
168               } else if (c == '-') {
169                  state = S08;
170                  n1 = Integer.parseInt(s.substring(mark, i));
171               } else {
172                  break;
173               }
174            } else if (state == S07) {
175               if (WS.contains(c)) {
176                  state = S07;
177               } else if (c == '-') {
178                  state = S09;
179               } else if (SNUM.contains(c)) {
180                  state = S06;
181                  l.add(new NumberRange(eq, n1, isNot));
182                  eq = Equality.NONE;
183                  n1 = null;
184                  isNot = false;
185                  mark = i;
186               } else {
187                  break;
188               }
189            } else if (state == S08) {
190               if (WS.contains(c)) {
191                  state = S08;
192               } else if (SNUM.contains(c)) {
193                  state = S11;
194                  mark = i;
195               } else {
196                  break;
197               }
198            } else if (state == S09)  {
199               if (WS.contains(c)) {
200                  state = S10;
201               } else if (NUM.contains(c)) {
202                  state = S06;
203                  l.add(new NumberRange(eq, n1, isNot));
204                  eq = Equality.NONE;
205                  n1 = null;
206                  isNot = false;
207                  mark = i-1;
208               } else {
209                  break;
210               }
211            } else if (state == S10) {
212               if (WS.contains(c)) {
213                  state = S10;
214               } else if (SNUM.contains(c)) {
215                  state = S11;
216                  mark = i;
217               } else {
218                  break;
219               }
220            } else /* (state == S11) */ {
221               if (SNUM.contains(c)) {
222                  state = S11;
223               } else if (WS.contains(c)) {
224                  state = S01;
225                  n2 = Integer.parseInt(s.substring(mark, i));
226                  l.add(new NumberRange(eq, n1, n2, isNot));
227                  eq = Equality.NONE;
228                  n1 = n2 = null;
229                  isNot = false;
230               } else {
231                  break;
232               }
233            }
234         }
235
236         if (i != s.length())
237            throw new PatternException("Invalid range pattern ({0}): {1}", state, s);
238
239         if (state == S01) {
240            // No tokens found.
241         } else if (state == S02 || state == S03 || state == S04 || state == S08 || state == S09) {
242            throw new PatternException("Invalid range pattern (E{0}): {1}", state, s);
243         } else if (state == S06) {
244            n1 = Integer.parseInt(s.substring(mark).trim());
245            l.add(new NumberRange(eq, n1, isNot));
246         } else /* (state == S11) */ {
247            n2 = Integer.parseInt(s.substring(mark).trim());
248            l.add(new NumberRange(eq, n1, n2, isNot));
249         }
250
251         numberRanges = l.toArray(new NumberRange[l.size()]);
252      }
253
254      @Override /* Matcher */
255      public boolean matches(ClassMeta<?> cm, Object o) {
256         Number n = (Number)o;
257         if (numberRanges.length == 0)
258            return true;
259         for (int i = 0; i < numberRanges.length; i++)
260            if (numberRanges[i].matches(n))
261               return true;
262         return false;
263      }
264
265      @Override /* Object */
266      public String toString() {
267         return pattern;
268      }
269   }
270
271   /**
272    * A construct representing a single search range in a single search pattern.
273    * All possible forms of search patterns are boiled down to these number ranges.
274    */
275   private static class NumberRange {
276      int start;
277      int end;
278      boolean isNot;
279
280      public NumberRange(Equality eq, Integer num, boolean isNot) {
281         this(eq, num, null, isNot);
282      }
283
284      public NumberRange(Equality eq, Integer start, Integer end, boolean isNot) {
285         this.isNot = isNot;
286
287         // 123, >123, <123, >=123, <=123, >-123, >=-123, 123-456, -123--456
288         if (eq == Equality.NONE && end == null) { // 123
289            this.start = start;
290            this.end = this.start;
291         } else if (eq == Equality.GT) {
292            this.start = start+1;
293            this.end = Integer.MAX_VALUE;
294         } else if (eq == Equality.GTE) {
295            this.start = start;
296            this.end = Integer.MAX_VALUE;
297         } else if (eq == Equality.LT) {
298            this.start = Integer.MIN_VALUE;
299            this.end = start-1;
300         } else if (eq == Equality.LTE) {
301            this.start = Integer.MIN_VALUE;
302            this.end = start;
303         } else {
304            this.start = start;
305            this.end = end;
306         }
307      }
308
309      public boolean matches(Number n) {
310         long i = n.longValue();
311         boolean b = (i>=start && i<=end);
312         if (isNot)
313            b = !b;
314         return b;
315      }
316   }
317}