1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.inputmethodservice.cts.devicetest;
18 
19 import java.util.ArrayList;
20 import java.util.Arrays;
21 import java.util.List;
22 import java.util.function.IntFunction;
23 import java.util.function.Predicate;
24 import java.util.stream.Collector;
25 import java.util.stream.Stream;
26 
27 /**
28  * Sequence matcher on {@link Stream}.
29  */
30 final class SequenceMatcher {
31 
32     /**
33      * Return type of this matcher.
34      * @param <E> type of stream element.
35      */
36     static final class MatchResult<E> {
37 
38         private final boolean mMatched;
39         private final List<E> mMatchedSequence;
40 
MatchResult(boolean matched, List<E> matchedSequence)41         MatchResult(boolean matched, List<E> matchedSequence) {
42             mMatched = matched;
43             mMatchedSequence = matchedSequence;
44         }
45 
matched()46         boolean matched() {
47             return mMatched;
48         }
49 
getMatchedSequence()50         List<E> getMatchedSequence() {
51             return mMatchedSequence;
52         }
53     }
54 
55     /**
56      * Accumulate continuous sequence of elements that satisfy specified {@link Predicate}s.
57      * @param <E> type of stream element.
58      */
59     private static final class SequenceAccumulator<E> {
60 
61         private final Predicate<E>[] mPredicates;
62         private final List<E> mSequence = new ArrayList<>();
63 
SequenceAccumulator(Predicate<E>.... predicates)64         SequenceAccumulator(Predicate<E>... predicates) {
65             mPredicates = predicates;
66         }
67 
accumulate(E element)68         void accumulate(E element) {
69             if (mSequence.isEmpty()) {
70                 // Search for the first element of sequence.
71                 if (mPredicates[0].test(element)) {
72                     mSequence.add(element);
73                 }
74                 return;
75             }
76             final int currentIndex = mSequence.size();
77             if (currentIndex >= mPredicates.length) {
78                 // Already found sequence.
79                 return;
80             }
81             if (mPredicates[currentIndex].test(element)) {
82                 mSequence.add(element);
83             } else {
84                 // Not continuous, restart searching from the first.
85                 mSequence.clear();
86             }
87         }
88 
getResult()89         MatchResult<E> getResult() {
90             return new MatchResult<>(mSequence.size() == mPredicates.length, mSequence);
91         }
92     }
93 
94 
95     /**
96      * Create a {@link Collector} that collects continuous sequence of elements that equal to
97      * specified {@code elements}. It returns {@link MatchResult<E>} of found such sequence.
98      * <pre>
99      * Stream.of(1, 2, 3, 4, 5).collect(
100      *      SequenceMatcher.of(1)).matched();  // true
101      * Stream.of(1, 2, 3, 4, 5).collect(
102      *      SequenceMatcher.of(2)).matched();  // true
103      * Stream.of(1, 2, 3, 4, 5).collect(
104      *      SequenceMatcher.of(0)).matched();  // false
105      * Stream.of(1, 2, 3, 4, 5).collect(
106      *      SequenceMatcher.of(2, 3, 4)).matched();  // true
107      * Stream.of(1, 2, 3, 4, 5).collect(
108      *      SequenceMatcher.of(2, 3, 5)).matched();  // false, not continuous.
109      * Stream.of(1, 2, 3, 4, 5).collect(
110      *      SequenceMatcher.of(2, 1)).matched();  // false
111      * Stream.of(1, 2, 3, 4, 5).collect(
112      *      SequenceMatcher.of(1, 2, 3, 4, 5, 6)).matched();  // false
113      * Stream.of(1, 1, 1, 1, 1).collect(
114      *      SequenceMatcher.of(1, 1)).matched();  // true
115      * Stream.of(1, 1, 1, 1, 1).collect(
116      *      SequenceMatcher.of(1, 1, 1)).matched();  // true
117      * Stream.of(1, 1, 1, 1, 1).collect(
118      *      SequenceMatcher.of(1, 1, 1, 1, 1, 1)).matched();  // false
119      * Stream.of(1, 1, 0, 1, 1).collect(
120      *      SequenceMatcher.of(1, 1, 1)).matched();  // false, not continuous.
121      * </pre>
122      *
123      * @param elements elements of matching sequence.
124      * @param <E> type of stream element
125      * @return {@link MatchResult<E>} of matcher sequence.
126      */
of(E... elements)127     static <E> Collector<E, ?, MatchResult<E>> of(E... elements) {
128         if (elements == null || elements.length == 0) {
129             throw new IllegalArgumentException("At least one element.");
130         }
131         final IntFunction<Predicate<E>[]> arraySupplier = Predicate[]::new;
132         return of(Arrays.stream(elements).map(Predicate::isEqual).toArray(arraySupplier));
133     }
134 
135     /**
136      * Create a {@link Collector} that collects continuous sequence of elements that satisfied
137      * specified {@code predicates}. It returns {@link MatchResult<E>} of found such sequence.
138      * <p>Please see examples in {@link #of(Object...)}.
139      *
140      * @param predicates array of {@link Predicate<E>} that each of sequence element should satisfy.
141      * @param <E> type of stream element.
142      * @return {@link MatchResult<E>} of matched sequence.
143      */
of(Predicate<E>.... predicates)144     static <E> Collector<E, ?, MatchResult<E>> of(Predicate<E>... predicates) {
145         if (predicates == null || predicates.length == 0) {
146             throw new IllegalArgumentException("At least one Predicate.");
147         }
148         return Collector.of(
149                 () -> new SequenceAccumulator<>(predicates),
150                 SequenceAccumulator::accumulate,
151                 (accumulate, accumulate2) -> {
152                     throw new UnsupportedOperationException("Do not use on parallel stream.");
153                 },
154                 SequenceAccumulator::getResult);
155     }
156 }
157