1package com.google.android.libraries.backup;
2
3import android.content.Context;
4import java.lang.annotation.Annotation;
5import java.lang.reflect.Field;
6import java.util.ArrayList;
7import java.util.Arrays;
8import java.util.Collection;
9import java.util.HashSet;
10import java.util.List;
11import java.util.Set;
12import java.util.regex.Matcher;
13import java.util.regex.Pattern;
14
15/** Static utility methods returning {@link BackupKeyPredicate} instances. */
16public class BackupKeyPredicates {
17
18  /**
19   * Returns a predicate that determines whether a key was defined as a field with the given
20   * annotation in one of the given classes. Assumes that the given annotation and classes are
21   * valid. You must ensure that proguard does not remove your annotation or any fields annotated
22   * with it.
23   *
24   * @see Backup
25   */
26  public static BackupKeyPredicate buildPredicateFromAnnotatedFieldsIn(
27      Class<? extends Annotation> annotation, Class<?>... klasses) {
28    return in(getAnnotatedFieldValues(annotation, klasses));
29  }
30
31  /**
32   * Returns a predicate that determines whether a key matches a regex that was defined as a field
33   * with the given annotation in one of the given classes. The test used is equivalent to
34   * {@link #containsPattern(String)} for each annotated field value. Assumes that the given
35   * annotation and classes are valid. You must ensure that proguard does not remove your annotation
36   * or any fields annotated with it.
37   *
38   * @see Backup
39   */
40  public static BackupKeyPredicate buildPredicateFromAnnotatedRegexFieldsIn(
41      Class<? extends Annotation> annotation, Class<?>... klasses) {
42    Set<String> patterns = getAnnotatedFieldValues(annotation, klasses);
43    Set<BackupKeyPredicate> patternPredicates = new HashSet<>();
44    for (String pattern : patterns) {
45      patternPredicates.add(containsPattern(pattern));
46    }
47    return or(patternPredicates);
48  }
49
50  private static Set<String> getAnnotatedFieldValues(
51      Class<? extends Annotation> annotation, Class<?>... klasses) {
52    Set<String> values = new HashSet<>();
53    for (Class<?> klass : klasses) {
54      addAnnotatedFieldValues(annotation, klass, values);
55    }
56    return values;
57  }
58
59  private static void addAnnotatedFieldValues(
60      Class<? extends Annotation> annotation, Class<?> klass, Set<String> values) {
61    for (Field field : klass.getDeclaredFields()) {
62      addFieldValueIfAnnotated(annotation, field, values);
63    }
64  }
65
66  private static void addFieldValueIfAnnotated(
67      Class<? extends Annotation> annotation, Field field, Set<String> values) {
68    if (field.isAnnotationPresent(annotation) && field.getType().equals(String.class)) {
69      try {
70        values.add((String) field.get(null));
71      } catch (IllegalAccessException e) {
72        throw new IllegalArgumentException(e);
73      }
74    }
75  }
76
77  /**
78   * Returns a predicate that determines whether a key is a member of the given collection. Changes
79   * to the given collection will change the returned predicate.
80   */
81  public static BackupKeyPredicate in(final Collection<? extends String> collection) {
82    if (collection == null) {
83      throw new NullPointerException("Null collection given.");
84    }
85    return new BackupKeyPredicate() {
86      @Override
87      public boolean shouldBeBackedUp(String key) {
88        return collection.contains(key);
89      }
90    };
91  }
92
93  /**
94   * Returns a predicate that determines whether a key contains any match for the given regular
95   * expression pattern. The test used is equivalent to {@link Matcher#find()}.
96   */
97  public static BackupKeyPredicate containsPattern(String pattern) {
98    final Pattern compiledPattern = Pattern.compile(pattern);
99    return new BackupKeyPredicate() {
100      @Override
101      public boolean shouldBeBackedUp(String key) {
102        return compiledPattern.matcher(key).find();
103      }
104    };
105  }
106
107  /**
108   * Returns a predicate that determines whether a key passes any of the given predicates. Each
109   * predicate is evaluated in the order given, and the evaluation process stops as soon as an
110   * accepting predicate is found. Changes to the given iterable will not change the returned
111   * predicate. The returned predicate returns {@code false} for any key if the given iterable is
112   * empty.
113   */
114  public static BackupKeyPredicate or(Iterable<BackupKeyPredicate> predicates) {
115    final List<BackupKeyPredicate> copiedPredicates = new ArrayList<>();
116    for (BackupKeyPredicate predicate : predicates) {
117      copiedPredicates.add(predicate);
118    }
119    return orDefensivelyCopied(new ArrayList<>(copiedPredicates));
120  }
121
122  /**
123   * Returns a predicate that determines whether a key passes any of the given predicates. Each
124   * predicate is evaluated in the order given, and the evaluation process stops as soon as an
125   * accepting predicate is found. The returned predicate returns {@code false} for any key if no
126   * there are no given predicates.
127   */
128  public static BackupKeyPredicate or(BackupKeyPredicate... predicates) {
129    return orDefensivelyCopied(Arrays.asList(predicates));
130  }
131
132  private static BackupKeyPredicate orDefensivelyCopied(
133      final Iterable<BackupKeyPredicate> predicates) {
134    return new BackupKeyPredicate() {
135      @Override
136      public boolean shouldBeBackedUp(String key) {
137        for (BackupKeyPredicate predicate : predicates) {
138          if (predicate.shouldBeBackedUp(key)) {
139            return true;
140          }
141        }
142        return false;
143      }
144    };
145  }
146
147  /**
148   * Returns a predicate that determines whether a key is one of the resources from the provided
149   * resource IDs. Assumes that all of the given resource IDs are valid.
150   */
151  public static BackupKeyPredicate buildPredicateFromResourceIds(
152      Context context, Collection<Integer> ids) {
153    Set<String> keys = new HashSet<>();
154    for (Integer id : ids) {
155      keys.add(context.getString(id));
156    }
157    return in(keys);
158  }
159
160  /** Returns a predicate that returns true for any key. */
161  public static BackupKeyPredicate alwaysTrue() {
162    return new BackupKeyPredicate() {
163      @Override
164      public boolean shouldBeBackedUp(String key) {
165        return true;
166      }
167    };
168  }
169}
170