11package com .featureprobe .sdk .server .model ;
22
3- import com .featureprobe .sdk .server .FPUser ;
4- import com .featureprobe .sdk .server .StringMatcher ;
5- import com .featureprobe .sdk .server .SegmentMatcher ;
3+ import com .featureprobe .sdk .server .*;
64import org .apache .commons .lang3 .StringUtils ;
5+ import org .apache .maven .artifact .versioning .ComparableVersion ;
6+ import org .slf4j .Logger ;
77
8- import java .util .HashMap ;
9- import java .util .List ;
10- import java .util .Map ;
11- import java .util .Objects ;
8+ import java .util .*;
129import java .util .regex .Pattern ;
1310
1411public final class Condition {
1512
13+ private static final Logger logger = Loggers .EVALUATOR ;
14+
1615 private ConditionType type ;
1716
1817 private String subject ;
@@ -21,13 +20,20 @@ public final class Condition {
2120
2221 private List <String > objects ;
2322
24- private static final Map <PredicateType , StringMatcher > stringMatchers =
25- new HashMap <>(PredicateType .values ().length );
23+ private static final long MILLISECONDS_IN_ONE_SEC = 1000 ;
24+
25+ private static final Map <PredicateType , StringMatcher > stringMatchers = new EnumMap <>(PredicateType .class );
26+
27+ private static final Map <PredicateType , SegmentMatcher > segmentMatchers = new EnumMap <>(PredicateType .class );
28+
29+ private static final Map <PredicateType , DatetimeMatcher > datetimeMatchers = new EnumMap <>(PredicateType .class );
30+
31+ private static final Map <PredicateType , NumberMatcher > numberMatchers = new EnumMap <>(PredicateType .class );
2632
27- private static final Map <PredicateType , SegmentMatcher > segmentMatchers =
28- new HashMap <>(PredicateType .values ().length );
33+ private static final Map <PredicateType , SemverMatcher > semverMatchers = new EnumMap <>(PredicateType .class );
2934
3035 static {
36+
3137 stringMatchers .put (PredicateType .IS_ONE_OF , (target , objects ) ->
3238 objects .contains (target ));
3339 stringMatchers .put (PredicateType .ENDS_WITH , (target , objects ) ->
@@ -50,43 +56,149 @@ public final class Condition {
5056 objects .stream ().noneMatch (s -> Pattern .compile (s ).matcher (target ).find ()));
5157
5258 segmentMatchers .put (PredicateType .IS_IN , (user , segments , objects ) ->
53- objects .stream ().anyMatch (s -> segments .get (s ).contains (user , segments )));
59+ objects .stream ().anyMatch (s -> segments .get (s ).contains (user , segments )));
5460 segmentMatchers .put (PredicateType .IS_NOT_IN , (user , segments , objects ) ->
5561 objects .stream ().noneMatch (s -> segments .get (s ).contains (user , segments )));
5662
63+ datetimeMatchers .put (PredicateType .AFTER , ((target , objects ) ->
64+ objects .stream ().map (Long ::parseLong ).anyMatch (o -> target >= o )));
65+ datetimeMatchers .put (PredicateType .BEFORE , ((target , objects ) ->
66+ objects .stream ().map (Long ::parseLong ).anyMatch (o -> target < o )));
67+
68+ numberMatchers .put (PredicateType .EQUAL_TO , ((target , objects ) ->
69+ objects .stream ().map (Double ::parseDouble ).anyMatch (o -> target == o )));
70+ numberMatchers .put (PredicateType .NOT_EQUAL_TO , ((target , objects ) ->
71+ objects .stream ().map (Double ::parseDouble ).noneMatch (o -> target == o )));
72+ numberMatchers .put (PredicateType .GREATER_THAN , ((target , objects ) ->
73+ objects .stream ().map (Double ::parseDouble ).anyMatch (o -> target > o )));
74+ numberMatchers .put (PredicateType .GREATER_OR_EQUAL , ((target , objects ) ->
75+ objects .stream ().map (Double ::parseDouble ).anyMatch (o -> target >= o )));
76+ numberMatchers .put (PredicateType .LESS_THAN , ((target , objects ) ->
77+ objects .stream ().map (Double ::parseDouble ).anyMatch (o -> target < o )));
78+ numberMatchers .put (PredicateType .LESS_OR_EQUAL , ((target , objects ) ->
79+ objects .stream ().map (Double ::parseDouble ).anyMatch (o -> target <= o )));
80+
81+ semverMatchers .put (PredicateType .EQUAL_TO , ((target , objects ) ->
82+ objects .stream ().filter (Objects ::nonNull ).map (ComparableVersion ::new ).anyMatch (t -> target .compareTo (t ) == 0 )));
83+ semverMatchers .put (PredicateType .NOT_EQUAL_TO , ((target , objects ) ->
84+ objects .stream ().filter (Objects ::nonNull ).map (ComparableVersion ::new ).noneMatch (t -> target .compareTo (t ) == 0 )));
85+ semverMatchers .put (PredicateType .GREATER_THAN , ((target , objects ) ->
86+ objects .stream ().filter (Objects ::nonNull ).map (ComparableVersion ::new ).anyMatch (t -> target .compareTo (t ) > 0 )));
87+ semverMatchers .put (PredicateType .GREATER_OR_EQUAL , ((target , objects ) ->
88+ objects .stream ().filter (Objects ::nonNull ).map (ComparableVersion ::new ).anyMatch (t -> target .compareTo (t ) >= 0 )));
89+ semverMatchers .put (PredicateType .LESS_THAN , ((target , objects ) ->
90+ objects .stream ().filter (Objects ::nonNull ).map (ComparableVersion ::new ).anyMatch (t -> target .compareTo (t ) < 0 )));
91+ semverMatchers .put (PredicateType .LESS_OR_EQUAL , ((target , objects ) ->
92+ objects .stream ().filter (Objects ::nonNull ).map (ComparableVersion ::new ).anyMatch (t -> target .compareTo (t ) <= 0 )));
93+
5794 }
5895
5996 public boolean matchObjects (FPUser user , Map <String , Segment > segments ) {
6097 switch (type ) {
6198 case STRING :
62- String subjectValue = user .getAttrs ().get (subject );
63- if (StringUtils .isBlank (subjectValue )) {
64- return false ;
65- }
66- return matchStringCondition (subjectValue );
99+ return matchStringCondition (user );
100+
67101 case SEGMENT :
68102 return matchSegmentCondition (user , segments );
69- case DATE :
70- // TODO
103+
104+ case DATETIME :
105+ return matchDatetimeCondition (user );
106+
107+ case NUMBER :
108+ return matchNumberCondition (user );
109+
110+ case SEMVER :
111+ return matchSemverCondition (user );
112+
71113 default :
72114 return false ;
73115 }
74116 }
75117
76- private boolean matchStringCondition (String subjectValue ) {
118+ private boolean matchStringCondition (FPUser user ) {
119+ String subjectValue = user .getAttr (subject );
120+ if (StringUtils .isBlank (subjectValue )) {
121+ return false ;
122+ }
123+
77124 StringMatcher stringMatcher = stringMatchers .get (this .predicate );
78- if (Objects .nonNull (stringMatcher )) {
79- return stringMatcher . match ( subjectValue , this . objects ) ;
125+ if (Objects .isNull (stringMatcher )) {
126+ return false ;
80127 }
81- return false ;
128+
129+ return stringMatcher .match (subjectValue , this .objects );
82130 }
83131
84132 private boolean matchSegmentCondition (FPUser user , Map <String , Segment > segments ) {
85133 SegmentMatcher segmentMatcher = segmentMatchers .get (this .predicate );
86- if (Objects .nonNull (segmentMatcher )) {
87- return segmentMatcher .match (user , segments , this .objects );
134+ if (Objects .isNull (segmentMatcher )) {
135+ return false ;
136+ }
137+
138+ return segmentMatcher .match (user , segments , this .objects );
139+ }
140+
141+ private boolean matchDatetimeCondition (FPUser user ) {
142+ DatetimeMatcher datetimeMatcher = datetimeMatchers .get (this .predicate );
143+ if (Objects .isNull (datetimeMatcher )) {
144+ return false ;
145+ }
146+
147+ String customValue = user .getAttr (this .subject );
148+ long cv ;
149+ try {
150+ cv = StringUtils .isBlank (customValue )
151+ ? System .currentTimeMillis () / MILLISECONDS_IN_ONE_SEC
152+ : Long .parseLong (customValue );
153+ } catch (NumberFormatException e ) {
154+ logger .error ("User attribute type mismatch. attribute value: {}, target type long" , customValue );
155+ return false ;
156+ }
157+ try {
158+ return datetimeMatcher .match (cv , objects );
159+ } catch (NumberFormatException e ) {
160+ logger .error ("Met a string that cannot be parsed to long in Condition.objects: {}" , e .getMessage ());
161+ return false ;
162+ }
163+ }
164+
165+ private boolean matchNumberCondition (FPUser user ) {
166+ NumberMatcher numberMatcher = numberMatchers .get (this .predicate );
167+ if (Objects .isNull (numberMatcher )) {
168+ return false ;
169+ }
170+
171+ String customValue = user .getAttr (this .subject );
172+ if (StringUtils .isBlank (customValue )) {
173+ return false ;
174+ }
175+ double cv ;
176+ try {
177+ cv = Double .parseDouble (customValue );
178+ } catch (NumberFormatException e ) {
179+ logger .error ("User attribute type mismatch. attribute value : {}, target type double" , customValue );
180+ return false ;
181+ }
182+ try {
183+ return numberMatcher .match (cv , this .objects );
184+ } catch (NumberFormatException e ) {
185+ logger .error ("Met a string that cannot be parsed to double in Condition.objects: {}" , e .getMessage ());
186+ return false ;
187+ }
188+ }
189+
190+ private boolean matchSemverCondition (FPUser user ) {
191+ SemverMatcher semverMatcher = semverMatchers .get (this .predicate );
192+ if (Objects .isNull (semverMatcher )) {
193+ return false ;
194+ }
195+
196+ String customValue = user .getAttr (this .subject );
197+ if (StringUtils .isBlank (customValue )) {
198+ return false ;
88199 }
89- return false ;
200+ ComparableVersion cv = new ComparableVersion (customValue );
201+ return semverMatcher .match (cv , this .objects );
90202 }
91203
92204 public ConditionType getType () {
0 commit comments