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