package com.google.android.libraries.backup; import android.content.Context; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; /** Static utility methods returning {@link BackupKeyPredicate} instances. */ public class BackupKeyPredicates { /** * Returns a predicate that determines whether a key was defined as a field with the given * annotation in one of the given classes. Assumes that the given annotation and classes are * valid. You must ensure that proguard does not remove your annotation or any fields annotated * with it. * * @see Backup */ public static BackupKeyPredicate buildPredicateFromAnnotatedFieldsIn( Class annotation, Class... klasses) { return in(getAnnotatedFieldValues(annotation, klasses)); } /** * Returns a predicate that determines whether a key matches a regex that was defined as a field * with the given annotation in one of the given classes. The test used is equivalent to * {@link #containsPattern(String)} for each annotated field value. Assumes that the given * annotation and classes are valid. You must ensure that proguard does not remove your annotation * or any fields annotated with it. * * @see Backup */ public static BackupKeyPredicate buildPredicateFromAnnotatedRegexFieldsIn( Class annotation, Class... klasses) { Set patterns = getAnnotatedFieldValues(annotation, klasses); Set patternPredicates = new HashSet<>(); for (String pattern : patterns) { patternPredicates.add(containsPattern(pattern)); } return or(patternPredicates); } private static Set getAnnotatedFieldValues( Class annotation, Class... klasses) { Set values = new HashSet<>(); for (Class klass : klasses) { addAnnotatedFieldValues(annotation, klass, values); } return values; } private static void addAnnotatedFieldValues( Class annotation, Class klass, Set values) { for (Field field : klass.getDeclaredFields()) { addFieldValueIfAnnotated(annotation, field, values); } } private static void addFieldValueIfAnnotated( Class annotation, Field field, Set values) { if (field.isAnnotationPresent(annotation) && field.getType().equals(String.class)) { try { values.add((String) field.get(null)); } catch (IllegalAccessException e) { throw new IllegalArgumentException(e); } } } /** * Returns a predicate that determines whether a key is a member of the given collection. Changes * to the given collection will change the returned predicate. */ public static BackupKeyPredicate in(final Collection collection) { if (collection == null) { throw new NullPointerException("Null collection given."); } return new BackupKeyPredicate() { @Override public boolean shouldBeBackedUp(String key) { return collection.contains(key); } }; } /** * Returns a predicate that determines whether a key contains any match for the given regular * expression pattern. The test used is equivalent to {@link Matcher#find()}. */ public static BackupKeyPredicate containsPattern(String pattern) { final Pattern compiledPattern = Pattern.compile(pattern); return new BackupKeyPredicate() { @Override public boolean shouldBeBackedUp(String key) { return compiledPattern.matcher(key).find(); } }; } /** * Returns a predicate that determines whether a key passes any of the given predicates. Each * predicate is evaluated in the order given, and the evaluation process stops as soon as an * accepting predicate is found. Changes to the given iterable will not change the returned * predicate. The returned predicate returns {@code false} for any key if the given iterable is * empty. */ public static BackupKeyPredicate or(Iterable predicates) { final List copiedPredicates = new ArrayList<>(); for (BackupKeyPredicate predicate : predicates) { copiedPredicates.add(predicate); } return orDefensivelyCopied(new ArrayList<>(copiedPredicates)); } /** * Returns a predicate that determines whether a key passes any of the given predicates. Each * predicate is evaluated in the order given, and the evaluation process stops as soon as an * accepting predicate is found. The returned predicate returns {@code false} for any key if no * there are no given predicates. */ public static BackupKeyPredicate or(BackupKeyPredicate... predicates) { return orDefensivelyCopied(Arrays.asList(predicates)); } private static BackupKeyPredicate orDefensivelyCopied( final Iterable predicates) { return new BackupKeyPredicate() { @Override public boolean shouldBeBackedUp(String key) { for (BackupKeyPredicate predicate : predicates) { if (predicate.shouldBeBackedUp(key)) { return true; } } return false; } }; } /** * Returns a predicate that determines whether a key is one of the resources from the provided * resource IDs. Assumes that all of the given resource IDs are valid. */ public static BackupKeyPredicate buildPredicateFromResourceIds( Context context, Collection ids) { Set keys = new HashSet<>(); for (Integer id : ids) { keys.add(context.getString(id)); } return in(keys); } /** Returns a predicate that returns true for any key. */ public static BackupKeyPredicate alwaysTrue() { return new BackupKeyPredicate() { @Override public boolean shouldBeBackedUp(String key) { return true; } }; } }