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=<1"</js>,<js>"property=<=1"</js>,<js>"property=>1"</js>,<js>"property=>=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 > juneau-marshall > 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}