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.pojotools;
014
015import static org.apache.juneau.internal.StateMachineState.*;
016
017import java.util.*;
018
019import org.apache.juneau.*;
020import org.apache.juneau.internal.*;
021
022/**
023 * TODO
024 *
025 */
026public class NumberMatcherFactory extends MatcherFactory {
027
028   /**
029    * Default reusable matcher.
030    */
031   public static final NumberMatcherFactory DEFAULT = new NumberMatcherFactory();
032
033   @Override
034   public boolean canMatch(ClassMeta<?> cm) {
035      return cm.isNumber();
036   }
037
038   @Override
039   public Matcher create(String pattern) {
040      return new NumberMatcher(pattern);
041   }
042
043   /**
044    * A construct representing a single search pattern.
045    */
046   private static class NumberMatcher extends Matcher {
047      NumberRange[] numberRanges;
048      String pattern;
049      private static final AsciiSet
050         SNUM = AsciiSet.create("-0123456789."),
051         NUM = AsciiSet.create("0123456789."),
052         WS = AsciiSet.create(" \t");
053
054      public NumberMatcher(String s) {
055
056         s = s.trim();
057         pattern = s;
058
059         List<NumberRange> l = new LinkedList<>();
060
061         // Possible patterns:
062         // 123, >123, <123, >=123, <=123, >-123, >=-123, 123-456, -123--456, !123, !123-456, 123 - 456 (one token), 123 -456 (two separate tokens)
063
064         // Possible states:
065         // S01 = Looking for start (WS=S01, [!]=S01, [>]=S02, [<]=S03, SNUM=S06)
066         // S02 = Found [>], looking for [=]/SNUM ([=]=S04, WS=S05, SNUM=S06)
067         // S03 = Found [<], looking for [=]/SNUM ([=]=S05, WS=S05, SNUM=S06)
068         // S04 = Found [=], looking for SNUN (WS=S05, SNUM=S06)
069         // S05 = Found [... ], looking for SNUM (SNUM=S06)
070         // S06 = Found [1], looking for [-]/WS (WS=S07, [-]=S08)
071         // S07 = Found [123 ], looking for [-]/SNUM (if -, could be 123 - 456 or 123 -456) ([-]=S09, SNUM=S07)
072         // S08 = Found [123-], looking for SNUM (Could be 123- 456 or 123-456) (SNUM=S11)
073         // S09 = Found [123 -], looking for WS/SNUM (If WS, then it's 123 - 456, otherwise 123 -456) (WS=S10, SNUM=S06)
074         // S10 = Found [123 - ], looking for SNUM (SNUM=S12)
075         // S11 = Found [123 - 4], looking for WS (WS=S01)
076
077         StateMachineState state = S01;
078         int mark = 0;
079         boolean isNot = false;
080         Equality eq = Equality.NONE;
081         Integer n1 = null, n2 = null;
082
083         int i;
084         for (i = 0; i < s.length(); i++) {
085            char c = s.charAt(i);
086            if (state == S01) {
087               if (c == '!') {
088                  isNot = true;
089               } else if (WS.contains(c)) {
090                  state = S01;
091               } else if (c == '>') {
092                  state = S02;
093                  eq = Equality.GT;
094               } else if (c == '<') {
095                  state = S03;
096                  eq = Equality.LT;
097               } else if (SNUM.contains(c)) {
098                  state = S06;
099                  mark = i;
100               } else {
101                  break;
102               }
103            } else if (state == S02) {
104               if (c == '=') {
105                  state = S04;
106                  eq = Equality.GTE;
107               } else if (WS.contains(c)) {
108                  state = S05;
109               } else if (SNUM.contains(c)) {
110                  state = S06;
111                  mark = i;
112               } else {
113                  break;
114               }
115            } else if (state == S03) {
116               if (c == '=') {
117                  state = S04;
118                  eq = Equality.LTE;
119               } else if (WS.contains(c)) {
120                  state = S05;
121               } else if (SNUM.contains(c)) {
122                  state = S06;
123                  mark = i;
124               } else {
125                  break;
126               }
127            } else if (state == S04) {
128               if (WS.contains(c)) {
129                  state = S05;
130               } else if (SNUM.contains(c)) {
131                  mark = i;
132                  state = S06;
133               } else {
134                  break;
135               }
136            } else if (state == S05) {
137               if (WS.contains(c)) {
138                  state = S05;
139               } else if (SNUM.contains(c)) {
140                  state = S06;
141                  mark = i;
142               } else {
143                  break;
144               }
145            } else if (state == S06) {
146               if (NUM.contains(c)) {
147                  state = S06;
148               } else if (WS.contains(c)) {
149                  state = S07;
150                  n1 = Integer.parseInt(s.substring(mark, i));
151               } else if (c == '-') {
152                  state = S08;
153                  n1 = Integer.parseInt(s.substring(mark, i));
154               } else {
155                  break;
156               }
157            } else if (state == S07) {
158               if (WS.contains(c)) {
159                  state = S07;
160               } else if (c == '-') {
161                  state = S09;
162               } else if (SNUM.contains(c)) {
163                  state = S06;
164                  l.add(new NumberRange(eq, n1, isNot));
165                  eq = Equality.NONE;
166                  n1 = null;
167                  isNot = false;
168                  mark = i;
169               } else {
170                  break;
171               }
172            } else if (state == S08) {
173               if (WS.contains(c)) {
174                  state = S08;
175               } else if (SNUM.contains(c)) {
176                  state = S11;
177                  mark = i;
178               } else {
179                  break;
180               }
181            } else if (state == S09)  {
182               if (WS.contains(c)) {
183                  state = S10;
184               } else if (NUM.contains(c)) {
185                  state = S06;
186                  l.add(new NumberRange(eq, n1, isNot));
187                  eq = Equality.NONE;
188                  n1 = null;
189                  isNot = false;
190                  mark = i-1;
191               } else {
192                  break;
193               }
194            } else if (state == S10) {
195               if (WS.contains(c)) {
196                  state = S10;
197               } else if (SNUM.contains(c)) {
198                  state = S11;
199                  mark = i;
200               } else {
201                  break;
202               }
203            } else /* (state == S11) */ {
204               if (SNUM.contains(c)) {
205                  state = S11;
206               } else if (WS.contains(c)) {
207                  state = S01;
208                  n2 = Integer.parseInt(s.substring(mark, i));
209                  l.add(new NumberRange(eq, n1, n2, isNot));
210                  eq = Equality.NONE;
211                  n1 = n2 = null;
212                  isNot = false;
213               } else {
214                  break;
215               }
216            }
217         }
218
219         if (i != s.length())
220            throw new PatternException("Invalid range pattern ({0}): {1}", state, s);
221
222         if (state == S01) {
223            // No tokens found.
224         } else if (state == S02 || state == S03 || state == S04 || state == S08 || state == S09) {
225            throw new PatternException("Invalid range pattern (E{0}): {1}", state, s);
226         } else if (state == S06) {
227            n1 = Integer.parseInt(s.substring(mark).trim());
228            l.add(new NumberRange(eq, n1, isNot));
229         } else /* (state == S11) */ {
230            n2 = Integer.parseInt(s.substring(mark).trim());
231            l.add(new NumberRange(eq, n1, n2, isNot));
232         }
233
234         numberRanges = l.toArray(new NumberRange[l.size()]);
235      }
236
237      @Override /* Matcher */
238      public boolean matches(ClassMeta<?> cm, Object o) {
239         Number n = (Number)o;
240         if (numberRanges.length == 0) return true;
241         for (int i = 0; i < numberRanges.length; i++)
242            if (numberRanges[i].matches(n))
243               return true;
244         return false;
245      }
246
247      @Override /* Object */
248      public String toString() {
249         return pattern;
250      }
251   }
252
253   /**
254    * A construct representing a single search range in a single search pattern.
255    * All possible forms of search patterns are boiled down to these number ranges.
256    */
257   private static class NumberRange {
258      int start;
259      int end;
260      boolean isNot;
261
262      public NumberRange(Equality eq, Integer num, boolean isNot) {
263         this(eq, num, null, isNot);
264      }
265
266      public NumberRange(Equality eq, Integer start, Integer end, boolean isNot) {
267         this.isNot = isNot;
268
269         // 123, >123, <123, >=123, <=123, >-123, >=-123, 123-456, -123--456
270         if (eq == Equality.NONE && end == null) { // 123
271            this.start = start;
272            this.end = this.start;
273         } else if (eq == Equality.GT) {
274            this.start = start+1;
275            this.end = Integer.MAX_VALUE;
276         } else if (eq == Equality.GTE) {
277            this.start = start;
278            this.end = Integer.MAX_VALUE;
279         } else if (eq == Equality.LT) {
280            this.start = Integer.MIN_VALUE;
281            this.end = start-1;
282         } else if (eq == Equality.LTE) {
283            this.start = Integer.MIN_VALUE;
284            this.end = start;
285         } else {
286            this.start = start;
287            this.end = end;
288         }
289      }
290
291      public boolean matches(Number n) {
292         long i = n.longValue();
293         boolean b = (i>=start && i<=end);
294         if (isNot) b = !b;
295         return b;
296      }
297   }
298}