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