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}