1/*
2 * Copyright (C) 2013 DroidDriver committers
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
17package io.appium.droiddriver.finders;
18
19import android.text.TextUtils;
20
21import java.util.Arrays;
22
23import io.appium.droiddriver.UiElement;
24
25/**
26 * Static utility methods pertaining to {@code Predicate} instances.
27 */
28public final class Predicates {
29
30  private Predicates() {
31  }
32
33  private static final Predicate<Object> ANY = new Predicate<Object>() {
34    @Override
35    public boolean apply(Object o) {
36      return true;
37    }
38
39    @Override
40    public String toString() {
41      return "any";
42    }
43  };
44
45  /**
46   * Returns a predicate that always evaluates to {@code true}.
47   */
48  @SuppressWarnings("unchecked")
49  public static <T> Predicate<T> any() {
50    return (Predicate<T>) ANY;
51  }
52
53  /**
54   * Returns a predicate that is the negation of the provided {@code predicate}.
55   */
56  public static <T> Predicate<T> not(final Predicate<T> predicate) {
57    return new Predicate<T>() {
58      @Override
59      public boolean apply(T input) {
60        return !predicate.apply(input);
61      }
62
63      @Override
64      public String toString() {
65        return "not(" + predicate + ")";
66      }
67    };
68  }
69
70  /**
71   * Returns a predicate that evaluates to {@code true} if both arguments evaluate to {@code true}.
72   * The arguments are evaluated in order, and evaluation will be "short-circuited" as soon as a
73   * false predicate is found.
74   */
75  @SuppressWarnings("unchecked")
76  public static <T> Predicate<T> allOf(final Predicate<? super T> first,
77      final Predicate<? super T> second) {
78    if (first == null || first == ANY) {
79      return (Predicate<T>) second;
80    }
81    if (second == null || second == ANY) {
82      return (Predicate<T>) first;
83    }
84
85    return new Predicate<T>() {
86      @Override
87      public boolean apply(T input) {
88        return first.apply(input) && second.apply(input);
89      }
90
91      @Override
92      public String toString() {
93        return "allOf(" + first + ", " + second + ")";
94      }
95    };
96  }
97
98  /**
99   * Returns a predicate that evaluates to {@code true} if each of its components evaluates to
100   * {@code true}. The components are evaluated in order, and evaluation will be "short-circuited"
101   * as soon as a false predicate is found.
102   */
103  public static <T> Predicate<T> allOf(final Iterable<Predicate<? super T>> components) {
104    return new Predicate<T>() {
105      @Override
106      public boolean apply(T input) {
107        for (Predicate<? super T> each : components) {
108          if (!each.apply(input)) {
109            return false;
110          }
111        }
112        return true;
113      }
114
115      @Override
116      public String toString() {
117        return "allOf(" + TextUtils.join(", ", components) + ")";
118      }
119    };
120  }
121
122  /**
123   * Returns a predicate that evaluates to {@code true} if each of its components evaluates to
124   * {@code true}. The components are evaluated in order, and evaluation will be "short-circuited"
125   * as soon as a false predicate is found.
126   */
127  @SuppressWarnings("RedundantTypeArguments") // Some compilers cannot infer <T>
128  @SafeVarargs
129  public static <T> Predicate<T> allOf(final Predicate<? super T>... components) {
130    return Predicates.<T>allOf(Arrays.asList(components));
131  }
132
133  /**
134   * Returns a predicate that evaluates to {@code true} if any one of its components evaluates to
135   * {@code true}. The components are evaluated in order, and evaluation will be "short-circuited"
136   * as soon as a true predicate is found.
137   */
138  public static <T> Predicate<T> anyOf(final Iterable<Predicate<? super T>> components) {
139    return new Predicate<T>() {
140      @Override
141      public boolean apply(T input) {
142        for (Predicate<? super T> each : components) {
143          if (each.apply(input)) {
144            return true;
145          }
146        }
147        return false;
148      }
149
150      @Override
151      public String toString() {
152        return "anyOf(" + TextUtils.join(", ", components) + ")";
153      }
154    };
155  }
156
157  /**
158   * Returns a predicate that evaluates to {@code true} if any one of its components evaluates to
159   * {@code true}. The components are evaluated in order, and evaluation will be "short-circuited"
160   * as soon as a true predicate is found.
161   */
162  @SuppressWarnings("RedundantTypeArguments") // Some compilers cannot infer <T>
163  @SafeVarargs
164  public static <T> Predicate<T> anyOf(final Predicate<? super T>... components) {
165    return Predicates.<T>anyOf(Arrays.asList(components));
166  }
167
168  /**
169   * Returns a predicate that evaluates to {@code true} on a {@link UiElement} if its {@code
170   * attribute} is {@code true}.
171   */
172  public static Predicate<UiElement> attributeTrue(final Attribute attribute) {
173    return new Predicate<UiElement>() {
174      @Override
175      public boolean apply(UiElement element) {
176        Boolean actual = element.get(attribute);
177        return actual != null && actual;
178      }
179
180      @Override
181      public String toString() {
182        return String.format("{%s}", attribute);
183      }
184    };
185  }
186
187  /**
188   * Returns a predicate that evaluates to {@code true} on a {@link UiElement} if its {@code
189   * attribute} is {@code false}.
190   */
191  public static Predicate<UiElement> attributeFalse(final Attribute attribute) {
192    return new Predicate<UiElement>() {
193      @Override
194      public boolean apply(UiElement element) {
195        Boolean actual = element.get(attribute);
196        return actual == null || !actual;
197      }
198
199      @Override
200      public String toString() {
201        return String.format("{not %s}", attribute);
202      }
203    };
204  }
205
206  /**
207   * Returns a predicate that evaluates to {@code true} on a {@link UiElement} if its {@code
208   * attribute} equals {@code expected}.
209   */
210  public static Predicate<UiElement> attributeEquals(final Attribute attribute,
211      final Object expected) {
212    return new Predicate<UiElement>() {
213      @Override
214      public boolean apply(UiElement element) {
215        Object actual = element.get(attribute);
216        return actual == expected || (actual != null && actual.equals(expected));
217      }
218
219      @Override
220      public String toString() {
221        return String.format("{%s=%s}", attribute, expected);
222      }
223    };
224  }
225
226  /**
227   * Returns a predicate that evaluates to {@code true} on a {@link UiElement} if its {@code
228   * attribute} matches {@code regex}.
229   */
230  public static Predicate<UiElement> attributeMatches(final Attribute attribute,
231      final String regex) {
232    return new Predicate<UiElement>() {
233      @Override
234      public boolean apply(UiElement element) {
235        String actual = element.get(attribute);
236        return actual != null && actual.matches(regex);
237      }
238
239      @Override
240      public String toString() {
241        return String.format("{%s matches %s}", attribute, regex);
242      }
243    };
244  }
245
246  /**
247   * Returns a predicate that evaluates to {@code true} on a {@link UiElement} if its {@code
248   * attribute} contains {@code substring}.
249   */
250  public static Predicate<UiElement> attributeContains(final Attribute attribute,
251      final String substring) {
252    return new Predicate<UiElement>() {
253      @Override
254      public boolean apply(UiElement element) {
255        String actual = element.get(attribute);
256        return actual != null && actual.contains(substring);
257      }
258
259      @Override
260      public String toString() {
261        return String.format("{%s contains %s}", attribute, substring);
262      }
263    };
264  }
265
266  public static Predicate<UiElement> withParent(
267      final Predicate<? super UiElement> parentPredicate) {
268    return new Predicate<UiElement>() {
269      @Override
270      public boolean apply(UiElement element) {
271        UiElement parent = element.getParent();
272        return parent != null && parentPredicate.apply(parent);
273      }
274
275      @Override
276      public String toString() {
277        return "withParent(" + parentPredicate + ")";
278      }
279    };
280  }
281
282  public static Predicate<UiElement> withAncestor(
283      final Predicate<? super UiElement> ancestorPredicate) {
284    return new Predicate<UiElement>() {
285      @Override
286      public boolean apply(UiElement element) {
287        UiElement parent = element.getParent();
288        while (parent != null) {
289          if (ancestorPredicate.apply(parent)) {
290            return true;
291          }
292          parent = parent.getParent();
293        }
294        return false;
295      }
296
297      @Override
298      public String toString() {
299        return "withAncestor(" + ancestorPredicate + ")";
300      }
301    };
302  }
303
304  public static Predicate<UiElement> withSibling(
305      final Predicate<? super UiElement> siblingPredicate) {
306    return new Predicate<UiElement>() {
307      @Override
308      public boolean apply(UiElement element) {
309        UiElement parent = element.getParent();
310        if (parent == null) {
311          return false;
312        }
313        for (UiElement sibling : parent.getChildren(UiElement.VISIBLE)) {
314          if (sibling != element && siblingPredicate.apply(sibling)) {
315            return true;
316          }
317        }
318        return false;
319      }
320
321      @Override
322      public String toString() {
323        return "withSibling(" + siblingPredicate + ")";
324      }
325    };
326  }
327
328  public static Predicate<UiElement> withChild(final Predicate<? super UiElement> childPredicate) {
329    return new Predicate<UiElement>() {
330      @Override
331      public boolean apply(UiElement element) {
332        for (UiElement child : element.getChildren(UiElement.VISIBLE)) {
333          if (childPredicate.apply(child)) {
334            return true;
335          }
336        }
337        return false;
338      }
339
340      @Override
341      public String toString() {
342        return "withChild(" + childPredicate + ")";
343      }
344    };
345  }
346
347
348}
349