/* * Copyright (C) 2012 Mobs and Geeks * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.android.pc.ioc.verification; import java.util.LinkedHashMap; import java.util.Set; import android.inputmethodservice.ExtractEditText; import android.text.TextUtils; import android.view.View; import android.widget.AutoCompleteTextView; import android.widget.CheckBox; import android.widget.Checkable; import android.widget.CheckedTextView; import android.widget.EditText; import android.widget.MultiAutoCompleteTextView; import android.widget.RadioButton; import android.widget.Spinner; import android.widget.TextView; import android.widget.ToggleButton; /** * A built-in class with a collection of common rules. {@link TextView} references notable direct and indirect subclasses that includes but not limited to {@link EditText}, {@link AutoCompleteTextView}, {@link ExtractEditText} and {@link MultiAutoCompleteTextView}. {@link Checkable} references notable implementing classes but not limited to {@link CheckBox}, {@link CheckedTextView}, {@link RadioButton} and {@link ToggleButton}. * * You may use it with any custom {@link View}s you may define that extends or implements the above mentioned classes and interfaces. * * @author Ragunath Jawahar <rj@mobsandgeeks.com> */ public final class Rules { public static final String EMPTY_STRING = ""; public static final String REGEX_INTEGER = "\\d+"; public static final String REGEX_DECIMAL = "[-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?"; public static final String REGEX_EMAIL = "^[_A-Za-z0-9-]+(\\.[_A-Za-z0-9-]+)*@" + "[A-Za-z0-9]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$"; public static final String REGEX_IP_ADDRESS = "^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." + "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." + "([01]?\\d\\d?|2[0-4]\\d|25[0-5])$"; /** * The classical required {@link Rule}. Checks if the {@link TextView} or its subclass {@link View}'s displayed text is not empty. * * @param failureMessage * The failure message for this {@link Rule}. * @param trimInput * Specifies whether to trim the text returned by {@code getText()}. * * @return True if the {@link View} is not empty, false otherwise. The return value is affected by the {@code trimInput} parameter. */ public static Rule<TextView> required(final String failureMessage, final boolean trimInput) { return new Rule<TextView>(failureMessage) { @Override public boolean isValid(TextView textView) { return !TextUtils.isEmpty(getText(textView, trimInput)); } }; } /** * Checks if the {@link TextView} or its subclass {@link View}'s displayed text matches the given regular expression. * * @param failureMessage * The failure message for this {@link Rule}. * @param regex * Regular expression pattern to be matched against the text returned by {@code getText()}. * @param trimInput * Specifies whether to trim the text returned by {@code getText()}. * * @throws IllegalArgumentException * If {@code regex} is {@code null}. * * @return True if the text matches the regular expression. The return value is affected by the {@code trimInput} parameter. */ public static Rule<TextView> regex(final String failureMessage, final String regex, final boolean trimInput) { if (regex == null) { throw new IllegalArgumentException("\'regex\' cannot be null"); } return new Rule<TextView>(failureMessage) { @Override public boolean isValid(TextView textView) { String text = getText(textView, trimInput); return text != null ? text.matches(regex) : false; } }; } /** * Checks if the {@link TextView} or its subclass {@link View}'s displayed text has at least the minimum number of characters specified by this {@link Rule}. * * @param failureMessage * The failure message for this {@link Rule}. * @param minLength * Minimum number of characters required in the text returned by {@code getText()}. The returned text is affected by the {@code trimInput} parameter. * @param trimInput * Specifies whether to trim the text returned by {@code getText()}. * * @return True if the text has the minimum number of characters specified, false otherwise. */ public static Rule<TextView> minLength(final String failureMessage, final int minLength, final boolean trimInput) { return new Rule<TextView>(failureMessage) { @Override public boolean isValid(TextView view) { String text = getText(view, trimInput); return text != null ? text.length() >= minLength : false; } }; } /** * Checks if the {@link TextView} or its subclass {@link View}'s displayed text's length is less than or equal to the maximum number of characters specified by this {@link Rule}. * * @param failureMessage * The failure message for this {@link Rule}. * @param maxLength * Maximum number of characters allowed in the text returned by {@code getText()}. The returned text is affected by the {@code trimInput} parameter. * @param trimInput * Specifies whether to trim the text returned by {@code getText()}. * * @return True if the text length is less than or equal to the maximum number of characters specified, false otherwise. */ public static Rule<TextView> maxLength(final String failureMessage, final int maxLength, final boolean trimInput) { return new Rule<TextView>(failureMessage) { @Override public boolean isValid(TextView view) { String text = getText(view, trimInput); return text != null ? text.length() <= maxLength : false; } }; } /** * Checks if the contents of two {@link TextView}s are equal. Ideal for password and confirm password. * * @param failureMessage * The failure message for this {@link Rule}. * @param anotherTextView * The {@link TextView} whose contents have to be checked against the {@link TextView} that is being validated. * * @throws IllegalArgumentException * If {@code anotherTextView} is {@code null}. * * @return True if both the {@link TextView} contents are equal. */ public static Rule<TextView> eq(final String failureMessage, final TextView anotherTextView) { if (anotherTextView == null) { throw new IllegalArgumentException("\'anotherTextView\' cannot be null"); } return new Rule<TextView>(failureMessage) { @Override public boolean isValid(TextView view) { return view.getText().toString().equals(anotherTextView.getText().toString()); } }; } /** * Checks if the {@link TextView} or its subclass {@link View}'s displayed text value equals the given {@link String} value. * * @param failureMessage * The failure message for this {@link Rule}. * @param expectedString * {@link String} value to be compared with the text returned by {@code getText()}. {@code null} is treated as empty {@link String}. * * @return True if the text matches the {@code expectedString{@code value, false otherwise. */ public static Rule<TextView> eq(final String failureMessage, final String expectedString) { return eq(failureMessage, expectedString, false, false); } /** * Checks if the {@link TextView} or its subclass {@link View}'s displayed text value equals the given {@link String} value. * * @param failureMessage * The failure message for this {@link Rule}. * @param expectedString * {@link String} value to be compared with the text returned by {@code getText()}. {@code null} is treated as empty {@link String}. * @param ignoreCase * Specifies whether the text's case differences should be ignored. * @param trimInput * Specifies whether to trim the text returned by {@code getText()}. * * @return True if the text matches the {@code expectedString{@code value, false otherwise. The return value is affected by {@code ignoreCase} and {@code trimInput} parameters. */ public static Rule<TextView> eq(final String failureMessage, final String expectedString, final boolean ignoreCase, final boolean trimInput) { final String cleanString = expectedString == null ? EMPTY_STRING : expectedString; return new Rule<TextView>(failureMessage) { @Override public boolean isValid(TextView textView) { boolean valid = false; String actualString = getText(textView, trimInput); if (actualString != null) { valid = ignoreCase ? actualString.equalsIgnoreCase(cleanString) : actualString.equals(cleanString); } return valid; } }; } /** * Checks if the {@link TextView} or its subclass {@link View}'s displayed text value equals the specified {@code int} value. * * @param failureMessage * The failure message for this {@link Rule}. * @param expectedInt * {@code int} value to be compared with the text returned by {@code getText()}. * * @return True if the input text is equal to the {@code expectedInt{@code value. */ public static Rule<TextView> eq(final String failureMessage, final int expectedInt) { return eq(failureMessage, (long) expectedInt); } /** * Checks if the {@link TextView} or its subclass {@link View}'s displayed text value is greater than the specified {@code int} value. * * @param failureMessage * The failure message for this {@link Rule}. * @param lesserInt * {@code int} value to be compared with the text returned by {@code getText()}. * * @return True if the input text is greater to the {@code expectedInt{@code value. */ public static Rule<TextView> gt(final String failureMessage, final int lesserInt) { return gt(failureMessage, (long) lesserInt); } /** * Checks if the {@link TextView} or its subclass {@link View}'s displayed text value is less than the specified {@code int} value. * * @param failureMessage * The failure message for this {@link Rule}. * @param greaterInt * {@code int} value to be compared with the text returned by {@code getText()}. * * @return True if the input text is less than the {@code expectedInt{@code value. */ public static Rule<TextView> lt(final String failureMessage, final int greaterInt) { return lt(failureMessage, (long) greaterInt); } /** * Checks if the {@link TextView} or its subclass {@link View}'s displayed text value equals the specified {@code long} value. * * @param failureMessage * The failure message for this {@link Rule}. * @param expectedLong * {@code long} value to be compared with the text returned by {@code getText()}. * * @return True if the input text is equal to the {@code expectedLong{@code value. */ public static Rule<TextView> eq(final String failureMessage, final long expectedLong) { return new Rule<TextView>(failureMessage) { @Override public boolean isValid(TextView textView) { boolean valid = false; String actualLong = getText(textView, true); if (actualLong != null) { valid = actualLong.matches(REGEX_INTEGER) ? Long.parseLong(actualLong) == expectedLong : false; } return valid; } }; } /** * Checks if the {@link TextView} or its subclass {@link View}'s displayed text value is greater than the specified {@code long} value. * * @param failureMessage * The failure message for this {@link Rule}. * @param lesserLong * {@code long} value to be compared with the text returned by {@code getText()}. * * @return True if the input text is greater than the {@code expectedLong{@code value. */ public static Rule<TextView> gt(final String failureMessage, final long lesserLong) { return new Rule<TextView>(failureMessage) { @Override public boolean isValid(TextView textView) { boolean valid = false; String actualLong = getText(textView, true); if (actualLong != null) { valid = actualLong.matches(REGEX_INTEGER) ? Long.parseLong(actualLong) > lesserLong : false; } return valid; } }; } /** * Checks if the {@link TextView} or its subclass {@link View}'s displayed text value is less than the specified {@code long} value. * * @param failureMessage * The failure message for this {@link Rule}. * @param greaterLong * {@code long} value to be compared with the text returned by {@code getText()}. * * @return True if the input text is less than the {@code expectedLong{@code value. */ public static Rule<TextView> lt(final String failureMessage, final long greaterLong) { return new Rule<TextView>(failureMessage) { @Override public boolean isValid(TextView textView) { boolean valid = false; String actualLong = getText(textView, true); if (actualLong != null) { valid = actualLong.matches(REGEX_INTEGER) ? Long.parseLong(actualLong) < greaterLong : false; } return valid; } }; } /** * Checks if the {@link TextView} or its subclass {@link View}'s displayed text value equals the specified {@code float} value. * * @param failureMessage * The failure message for this {@link Rule}. * @param expectedFloat * {@code float} value to be compared with the text returned by {@code getText()}. * * @return True if the input text is equal to the {@code expectedFloat{@code value. */ public static Rule<TextView> eq(final String failureMessage, final float expectedFloat) { return new Rule<TextView>(failureMessage) { @Override public boolean isValid(TextView view) { boolean valid = false; String actualFloat = getText(view, true); if (actualFloat != null) { valid = actualFloat.matches(REGEX_DECIMAL) ? Float.parseFloat(actualFloat) == expectedFloat : false; } return valid; } }; } /** * Checks if the {@link TextView} or its subclass {@link View}'s displayed text value is greater than the specified {@code float} value. * * @param failureMessage * The failure message for this {@link Rule}. * @param lesserFloat * {@code float} value to be compared with the text returned by {@code getText()}. * * @return True if the input text is equal to the {@code expectedFloat{@code value. */ public static Rule<TextView> gt(final String failureMessage, final float lesserFloat) { return new Rule<TextView>(failureMessage) { @Override public boolean isValid(TextView view) { boolean valid = false; String actualFloat = getText(view, true); if (actualFloat != null) { valid = actualFloat.matches(REGEX_DECIMAL) ? Float.parseFloat(actualFloat) > lesserFloat : false; } return valid; } }; } /** * Checks if the {@link TextView} or its subclass {@link View}'s displayed text value is less than the specified {@code float} value. * * @param failureMessage * The failure message for this {@link Rule}. * @param greaterFloat * {@code float} value to be compared with the text returned by {@code getText()}. * * @return True if the input text is less than the {@code expectedFloat{@code value. */ public static Rule<TextView> lt(final String failureMessage, final float greaterFloat) { return new Rule<TextView>(failureMessage) { @Override public boolean isValid(TextView view) { boolean valid = false; String actualFloat = getText(view, true); if (actualFloat != null) { valid = actualFloat.matches(REGEX_DECIMAL) ? Float.parseFloat(actualFloat) < greaterFloat : false; } return valid; } }; } /** * Checks if the {@link TextView} or its subclass {@link View}'s displayed text value equals the specified {@code double} value. * * @param failureMessage * The failure message for this {@link Rule}. * @param expectedDouble * {@code double} value to be compared with the text returned by {@code getText()}. * * @return True if the input text is equal to the {@code expectedDouble{@code value. */ public static Rule<TextView> eq(final String failureMessage, final double expectedDouble) { return new Rule<TextView>(failureMessage) { @Override public boolean isValid(TextView view) { boolean valid = false; String actualDouble = getText(view, true); if (actualDouble != null) { valid = actualDouble.matches(REGEX_DECIMAL) ? Double.parseDouble(actualDouble) == expectedDouble : false; } return valid; } }; } /** * Checks if the {@link TextView} or its subclass {@link View}'s displayed text value is greater than the specified {@code double} value. * * @param failureMessage * The failure message for this {@link Rule}. * @param lesserDouble * {@code double} value to be compared with the text returned by {@code getText()}. * * @return True if the input text is greater than the {@code expectedDouble{@code value. */ public static Rule<TextView> gt(final String failureMessage, final double lesserDouble) { return new Rule<TextView>(failureMessage) { @Override public boolean isValid(TextView view) { boolean valid = false; String actualDouble = getText(view, true); if (actualDouble != null) { valid = actualDouble.matches(REGEX_DECIMAL) ? Double.parseDouble(actualDouble) > lesserDouble : false; } return valid; } }; } /** * Checks if the {@link TextView} or its subclass {@link View}'s displayed text value is less than the specified {@code double} value. * * @param failureMessage * The failure message for this {@link Rule}. * @param greaterDouble * {@code double} value to be compared with the text returned by {@code getText()}. * * @return True if the input text is less than the {@code expectedDouble{@code value. */ public static Rule<TextView> lt(final String failureMessage, final double greaterDouble) { return new Rule<TextView>(failureMessage) { @Override public boolean isValid(TextView view) { boolean valid = false; String actualDouble = getText(view, true); if (actualDouble != null) { valid = actualDouble.matches(REGEX_DECIMAL) ? Double.parseDouble(actualDouble) < greaterDouble : false; } return valid; } }; } /** * Checks if the {@link Checkable} or its subclass {@link View}'s state is same as the state specified. * * @param failureMessage * The failure message for this {@link Rule}. * @param checked * The expected state of the {@link Checkable} widget. * * @return True if the state is same as {@code checked}. */ public static Rule<Checkable> checked(final String failureMessage, final boolean checked) { return new Rule<Checkable>(failureMessage) { @Override public boolean isValid(Checkable checkableView) { return checkableView.isChecked() == checked; } }; } /** * Checks if the {@link Spinner}'s selected item's {@link String} value (obtained by calling {@code toString()} on the selected item) equals the expected {@link String} value. * * @param failureMessage * The failure message for this {@link Rule}. * @param expectedString * {@link String} value to be compared with the text returned by calling {@code toString()} on the selected {@link Spinner} item. * @param ignoreCase * Specifies whether the text's case differences should be ignored. * @param trimInput * Specifies whether to trim the String returned by {@code toString()} on the selected item. * * @return True if both the {@link String} values are equal. */ public static Rule<Spinner> spinnerEq(final String failureMessage, final String expectedString, final boolean ignoreCase, final boolean trimInput) { return new Rule<Spinner>(failureMessage) { @Override public boolean isValid(Spinner spinner) { boolean equals = false; Object selectedItem = spinner.getSelectedItem(); if (expectedString == null && selectedItem == null) { equals = true; } else if (expectedString != null && selectedItem != null) { String selectedItemString = selectedItem.toString(); selectedItemString = trimInput ? selectedItemString.trim() : selectedItemString; equals = ignoreCase ? selectedItemString.equalsIgnoreCase(expectedString) : selectedItemString.equals(expectedString); } return equals; } }; } /** * Checks if the {@link Spinner}'s selected item's position (obtained by calling {@code getSelectionItemPosition()}) equals the expected selection index. * * @param failureMessage * The failure message for this {@link Rule}. * @param expectedPosition * The position to be compared with the position returned by calling {@code getSelectedItemPosition()} on the {@link Spinner}. * * @return True if both the {@link String} values are equal. */ public static Rule<Spinner> spinnerEq(final String failureMessage, final int expectedPosition) { return new Rule<Spinner>(failureMessage) { @Override public boolean isValid(Spinner spinner) { return spinner.getSelectedItemPosition() == expectedPosition; } }; } /** * Performs an '&&' (and) operation on the given array of {@link Rules}. * * @param failureMessage * The failure message for this {@link Rule}. * @param rules * An array of {@link Rule}s on which the '&&' (and) operation is to be performed. * * @return True if all {@link Rule}s are valid. */ public static Rule<View> and(final String failureMessage, final Rule<?>... rules) { return new Rule<View>(failureMessage) { @Override public boolean isValid(View view) { boolean valid = true; for (Rule rule : rules) { if (rule != null) valid &= rule.isValid(view); if (!valid) break; } return valid; } }; } /** * Performs a '||' (or) operation on the given array of {@link Rules}. * * @param failureMessage * The failure message for this {@link Rule}. * @param rules * An array of {@link Rule}s on which the '||' (or) operation is to be performed. * * @return True if at least one of the {@link Rule}s is valid. */ public static Rule<View> or(final String failureMessage, final Rule<?>... rules) { return new Rule<View>(failureMessage) { @Override public boolean isValid(View view) { boolean valid = false; for (Rule rule : rules) { if (rule != null) valid |= rule.isValid(view); if (valid) break; } return valid; } }; } /** * Unlike the other rules, this one performs an '&&' (and) operation on several {@link View}s. * * @param failureMessage * The failure message for this {@link Rule}. * @param viewsAndRules * A {@link LinkedHashMap} containing rules for different {@link View}s. * * @return True if all {@link Rule}s are valid. */ public static Rule<View> compositeAnd(final String failureMessage, final LinkedHashMap<View, Rule<?>> viewsAndRules) { return new Rule<View>(failureMessage) { @Override public boolean isValid(View view) { boolean valid = true; Set<View> keySet = viewsAndRules.keySet(); for (View viewKey : keySet) { Rule rule = viewsAndRules.get(viewKey); valid &= rule.isValid(view); if (!valid) break; } return valid; } }; } /** * Unlike the other rules, this one performs a '||' (or) operation on several {@link View}s. * * @param failureMessage * The failure message for this {@link Rule}. * @param viewsAndRules * A {@link LinkedHashMap} containing rules for different {@link View}s. * * @return True if at least one of the {@link Rule}s is valid. */ public static Rule<View> compositeOr(final String failureMessage, final LinkedHashMap<View, Rule<?>> viewsAndRules) { return new Rule<View>(failureMessage) { @Override public boolean isValid(View view) { boolean valid = false; Set<View> keySet = viewsAndRules.keySet(); for (View viewKey : keySet) { Rule rule = viewsAndRules.get(viewKey); valid |= rule.isValid(viewKey); if (valid) break; } return valid; } }; } private static String getText(final TextView textView, final boolean trim) { CharSequence text = null; if (textView != null) { text = textView.getText(); text = trim ? text.toString().trim() : text; } return text != null ? text.toString() : null; } }