1 package com.google.android.libraries.backup; 2 3 import android.content.Context; 4 import java.lang.annotation.Annotation; 5 import java.lang.reflect.Field; 6 import java.util.ArrayList; 7 import java.util.Arrays; 8 import java.util.Collection; 9 import java.util.HashSet; 10 import java.util.List; 11 import java.util.Set; 12 import java.util.regex.Matcher; 13 import java.util.regex.Pattern; 14 15 /** Static utility methods returning {@link BackupKeyPredicate} instances. */ 16 public 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 */ buildPredicateFromAnnotatedFieldsIn( Class<? extends Annotation> annotation, Class<?>... klasses)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 */ buildPredicateFromAnnotatedRegexFieldsIn( Class<? extends Annotation> annotation, Class<?>... klasses)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 getAnnotatedFieldValues( Class<? extends Annotation> annotation, Class<?>... klasses)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 addAnnotatedFieldValues( Class<? extends Annotation> annotation, Class<?> klass, Set<String> values)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 addFieldValueIfAnnotated( Class<? extends Annotation> annotation, Field field, Set<String> values)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 */ in(final Collection<? extends String> collection)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