Skip to content

Commit 6f0f788

Browse files
committed
#20 wildcard pattern matching, and new pattern builder
1 parent 1a3f317 commit 6f0f788

6 files changed

Lines changed: 233 additions & 37 deletions

File tree

src/main/java/unquietcode/tools/esm/GenericStateMachine.java

Lines changed: 53 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ this software and associated documentation files (the "Software"), to deal in
2424
package unquietcode.tools.esm;
2525

2626
import unquietcode.tools.esm.routing.StateRouter;
27+
import unquietcode.tools.esm.sequences.Pattern;
28+
import unquietcode.tools.esm.sequences.PatternBuilder;
2729

2830
import java.util.*;
2931
import java.util.concurrent.*;
@@ -46,7 +48,7 @@ public class GenericStateMachine<T extends State> implements StateMachine<T> {
4648
// sequence matching
4749
private int maxRecent = 0;
4850
private final Queue<StateContainer> recentStates = new ArrayDeque<>();
49-
private final Set<PatternMatcher> matchers = new HashSet<>();
51+
private final Set<PatternMatcher<T>> matchers = new HashSet<>();
5052

5153
// global handlers
5254
private final Set<StateHandler<T>> globalOnEntryHandlers = new HashSet<>();
@@ -159,9 +161,11 @@ private void doPatternMatching(StateContainer nextState) {
159161
final List<StateContainer> recent
160162
= Collections.unmodifiableList(new ArrayList<>(recentStates));
161163

162-
for (PatternMatcher matcher : matchers) {
163-
if (matcher.matches(recent)) {
164-
matcher.handler.onMatch(matcher.pattern);
164+
for (PatternMatcher<T> matcher : matchers) {
165+
Optional<List<T>> matches = matcher.matches(recent);
166+
167+
if (matches.isPresent()) {
168+
matcher.handler.onMatch(matches.get());
165169
}
166170
}
167171
}
@@ -400,29 +404,35 @@ public T route(T current, T next) {
400404
}
401405

402406
@Override
403-
public synchronized HandlerRegistration onSequence(List<T> pattern, SequenceHandler<T> handler) {
404-
List<State> _pattern = Collections.<State>unmodifiableList(pattern);
405-
final PatternMatcher matcher = new PatternMatcher(_pattern, handler);
407+
public HandlerRegistration onSequence(Pattern<T> pattern, SequenceHandler<T> handler) {
408+
final PatternMatcher<T> matcher = new PatternMatcher<>(pattern, handler);
406409
matchers.add(matcher);
407-
maxRecent = Math.max(maxRecent, pattern.size());
408410

409-
return new HandlerRegistration() {
410-
public void unregister() {
411-
matchers.remove(matcher);
412-
}
411+
// recalculate the cache size on add
412+
maxRecent = Math.max(maxRecent, pattern.length());
413+
414+
return () -> {
415+
matchers.remove(matcher);
416+
417+
// recalculate the cache size on remove
418+
Optional<Integer> max = matchers.stream()
419+
.map(e -> e.pattern.length())
420+
.max(Integer::compareTo);
421+
422+
maxRecent = max.orElse(0);
413423
};
414424
}
415425

416426
@Override
417427
@SuppressWarnings("unchecked")
418428
public synchronized boolean addTransition(T fromState, T toState) {
419-
return addTransitions(null, true, fromState, Arrays.asList(toState));
429+
return addTransitions(null, true, fromState, Collections.singletonList(toState));
420430
}
421431

422432
@Override
423433
@SuppressWarnings("unchecked")
424434
public synchronized boolean addTransition(T fromState, T toState, TransitionHandler<T> callback) {
425-
return addTransitions(callback, true, fromState, Arrays.asList(toState));
435+
return addTransitions(callback, true, fromState, Collections.singletonList(toState));
426436
}
427437

428438
@Override
@@ -516,14 +526,13 @@ public String toString() {
516526
}
517527

518528
int i = 1;
519-
Map<StateWrapper, StateContainer> sortedStates = new TreeMap<StateWrapper, StateContainer>(states);
529+
Map<StateWrapper, StateContainer> sortedStates = new TreeMap<>(states);
520530

521531
for (Map.Entry<StateWrapper, StateContainer> entry : sortedStates.entrySet()) {
522532
sb.append("\t").append(fullString(entry.getKey().state)).append(" : {");
523533

524534
int j = 1;
525-
Map<StateContainer, Transition> sortedTransitions
526-
= new TreeMap<StateContainer, Transition>(entry.getValue().transitions);
535+
Map<StateContainer, Transition> sortedTransitions = new TreeMap<>(entry.getValue().transitions);
527536

528537
for (Transition t : sortedTransitions.values()) {
529538
sb.append(fullString(t.next.state));
@@ -556,9 +565,9 @@ private StateContainer getState(T token) {
556565

557566
private static class StateContainer implements Comparable<StateContainer> {
558567
final State state;
559-
final Map<StateContainer, Transition> transitions = new HashMap<StateContainer, Transition>();
560-
final Set<StateHandler> entryActions = new HashSet<StateHandler>();
561-
final Set<StateHandler> exitActions = new HashSet<StateHandler>();
568+
final Map<StateContainer, Transition> transitions = new HashMap<>();
569+
final Set<StateHandler> entryActions = new HashSet<>();
570+
final Set<StateHandler> exitActions = new HashSet<>();
562571

563572
StateContainer(State state) {
564573
this.state = state;
@@ -617,7 +626,7 @@ public int hashCode() {
617626

618627
private static class Transition {
619628
final StateContainer next;
620-
final Set<TransitionHandler> callbacks = new HashSet<TransitionHandler>();
629+
final Set<TransitionHandler> callbacks = new HashSet<>();
621630

622631
Transition(StateContainer next) {
623632
this.next = next;
@@ -635,38 +644,48 @@ private static class Transition {
635644
// }
636645
}
637646

638-
private static class PatternMatcher {
639-
private final List<State> pattern;
647+
private static class PatternMatcher<T> {
648+
private final Pattern<T> pattern;
640649
private final SequenceHandler handler;
641650

642-
PatternMatcher(List<State> pattern, SequenceHandler handler) {
651+
PatternMatcher(Pattern<T> pattern, SequenceHandler handler) {
643652
this.pattern = pattern;
644653
this.handler = handler;
645654
}
646655

647-
boolean matches(List<StateContainer> states) {
648-
for (int i = pattern.size() - 1; i >= 0; --i) {
649-
int j = states.size() - (pattern.size() - i);
656+
Optional<List<T>> matches(List<StateContainer> states) {
657+
List<T> matches = new ArrayList<>();
658+
List<Object> patternStates = this.pattern.pattern();
659+
660+
for (int i = patternStates.size() - 1; i >= 0; --i) {
661+
int j = states.size() - (patternStates.size() - i);
650662

651663
if (j < 0) {
652-
return false;
664+
return Optional.empty();
653665
}
654666

655-
State matchState = pattern.get(i);
667+
Object matchState = patternStates.get(i);
656668
State recentState = states.get(j).state;
657669

658-
if (matchState == null) {
670+
if (PatternBuilder.isWildcard(matchState)) {
671+
// nothing, do no state checking
672+
} else if (matchState == null) {
659673
if (recentState != null) {
660-
return false;
674+
return Optional.empty();
661675
}
662676
} else if (recentState == null) {
663-
return false;
677+
return Optional.empty();
664678
} else if (!matchState.equals(recentState)) {
665-
return false;
679+
return Optional.empty();
666680
}
681+
682+
@SuppressWarnings("unchecked")
683+
T recentState_ = (T) recentState;
684+
matches.add(recentState_);
667685
}
668686

669-
return true;
687+
Collections.reverse(matches);
688+
return Optional.of(matches);
670689
}
671690
}
672691

src/main/java/unquietcode/tools/esm/ProgrammableStateMachine.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ this software and associated documentation files (the "Software"), to deal in
2323

2424
package unquietcode.tools.esm;
2525

26+
import unquietcode.tools.esm.sequences.Pattern;
27+
import unquietcode.tools.esm.sequences.PatternBuilder;
28+
2629
import java.util.List;
2730

2831
/**
@@ -82,7 +85,12 @@ public interface ProgrammableStateMachine<T> {
8285
* @param handler to handle the match
8386
* @return registration to assist in removing the handler
8487
*/
85-
HandlerRegistration onSequence(List<T> pattern, SequenceHandler<T> handler);
88+
default HandlerRegistration onSequence(List<T> pattern, SequenceHandler<T> handler) {
89+
Pattern<T> pattern_ = PatternBuilder.createFrom(pattern);
90+
return onSequence(pattern_, handler);
91+
}
92+
93+
HandlerRegistration onSequence(Pattern<T> pattern, SequenceHandler<T> handler);
8694

8795
/*
8896
Adds a transition from one state to another.

src/main/java/unquietcode/tools/esm/WrappedStateMachine.java

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ this software and associated documentation files (the "Software"), to deal in
2424
package unquietcode.tools.esm;
2525

2626
import unquietcode.tools.esm.routing.StateRouter;
27+
import unquietcode.tools.esm.sequences.Pattern;
28+
import unquietcode.tools.esm.sequences.PatternBuilder;
2729

2830
import java.util.*;
2931
import java.util.concurrent.Future;
@@ -167,8 +169,25 @@ public HandlerRegistration routeAfterExiting(_Type from, StateRouter<_Type> rout
167169
}
168170

169171
@Override
170-
public HandlerRegistration onSequence(List<_Type> pattern, SequenceHandler<_Type> handler) {
171-
return proxy.onSequence(wrap(pattern), new SequenceWrapper(handler));
172+
public HandlerRegistration onSequence(Pattern<_Type> pattern, SequenceHandler<_Type> handler) {
173+
PatternBuilder<_Wrapper> patternBuilder = PatternBuilder.create();
174+
175+
for (Object type : pattern.pattern()) {
176+
177+
if (PatternBuilder.isWildcard(type)) {
178+
patternBuilder.addWildcard();
179+
continue;
180+
}
181+
182+
@SuppressWarnings("unchecked")
183+
_Type type_ = (_Type) type;
184+
185+
_Wrapper wrapped = _wrap(type_);
186+
patternBuilder.add(wrapped);
187+
}
188+
189+
Pattern<_Wrapper> pattern_ = patternBuilder.build();
190+
return proxy.onSequence(pattern_, new SequenceWrapper(handler));
172191
}
173192

174193
@Override
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*******************************************************************************
2+
The MIT License (MIT)
3+
4+
Copyright (c) 2018 Benjamin Fagin
5+
6+
Permission is hereby granted, free of charge, to any person obtaining a copy of
7+
this software and associated documentation files (the "Software"), to deal in
8+
the Software without restriction, including without limitation the rights to
9+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10+
the Software, and to permit persons to whom the Software is furnished to do so,
11+
subject to the following conditions:
12+
13+
The above copyright notice and this permission notice shall be included in all
14+
copies or substantial portions of the Software.
15+
16+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22+
******************************************************************************/
23+
24+
package unquietcode.tools.esm.sequences;
25+
26+
import java.util.ArrayList;
27+
import java.util.Collections;
28+
import java.util.List;
29+
30+
/**
31+
* @author Ben Fagin
32+
* @version 2018-07-18
33+
*/
34+
public class Pattern<T> {
35+
private final List<T> pattern = new ArrayList<>();
36+
37+
/* package */ Pattern(List<T> pattern) {
38+
this.pattern.addAll(pattern);
39+
}
40+
41+
public List<Object> pattern() {
42+
return Collections.unmodifiableList(pattern);
43+
}
44+
45+
public int length() {
46+
return pattern.size();
47+
}
48+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*******************************************************************************
2+
The MIT License (MIT)
3+
4+
Copyright (c) 2018 Benjamin Fagin
5+
6+
Permission is hereby granted, free of charge, to any person obtaining a copy of
7+
this software and associated documentation files (the "Software"), to deal in
8+
the Software without restriction, including without limitation the rights to
9+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10+
the Software, and to permit persons to whom the Software is furnished to do so,
11+
subject to the following conditions:
12+
13+
The above copyright notice and this permission notice shall be included in all
14+
copies or substantial portions of the Software.
15+
16+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22+
******************************************************************************/
23+
24+
package unquietcode.tools.esm.sequences;
25+
26+
import unquietcode.tools.esm.State;
27+
28+
import java.util.ArrayList;
29+
import java.util.Arrays;
30+
import java.util.List;
31+
32+
/**
33+
* @author Ben Fagin
34+
* @version 2018-07-18
35+
*/
36+
public class PatternBuilder<T> {
37+
private PatternBuilder() {}
38+
private final List<Object> pattern = new ArrayList<>();
39+
40+
41+
public static <T extends State> PatternBuilder<T> create() {
42+
return new PatternBuilder<>();
43+
}
44+
45+
public static <T> Pattern<T> createFrom(List<T> states) {
46+
return new Pattern<>(states);
47+
}
48+
49+
@SafeVarargs
50+
public final PatternBuilder<T> add(T...states) {
51+
pattern.addAll(Arrays.asList(states));
52+
return this;
53+
}
54+
55+
public Pattern<T> build() {
56+
Pattern<Object> p1 = new Pattern<>(pattern);
57+
58+
@SuppressWarnings("unchecked")
59+
Pattern<T> p2 = (Pattern<T>) p1;
60+
61+
return p2;
62+
}
63+
64+
// wildcard matching state
65+
private static final Object WILDCARD = new Object();
66+
67+
public static boolean isWildcard(Object element) {
68+
return element == WILDCARD;
69+
}
70+
71+
public PatternBuilder<T> addWildcard() {
72+
pattern.add(WILDCARD);
73+
return this;
74+
}
75+
}

0 commit comments

Comments
 (0)