com.mobsandgeeks.saripaar.Registry Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of android-saripaar Show documentation
Show all versions of android-saripaar Show documentation
Rule-based UI form validation library for Android
/*
* Copyright (C) 2014 Mobs & 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.mobsandgeeks.saripaar;
import android.util.Log;
import android.view.View;
import android.widget.CheckBox;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.Spinner;
import android.widget.TextView;
import com.mobsandgeeks.saripaar.adapter.CheckBoxBooleanAdapter;
import com.mobsandgeeks.saripaar.adapter.RadioButtonBooleanAdapter;
import com.mobsandgeeks.saripaar.adapter.RadioGroupBooleanAdapter;
import com.mobsandgeeks.saripaar.adapter.SpinnerIndexAdapter;
import com.mobsandgeeks.saripaar.adapter.TextViewDoubleAdapter;
import com.mobsandgeeks.saripaar.adapter.TextViewFloatAdapter;
import com.mobsandgeeks.saripaar.adapter.TextViewIntegerAdapter;
import com.mobsandgeeks.saripaar.adapter.TextViewStringAdapter;
import com.mobsandgeeks.saripaar.adapter.ViewDataAdapter;
import com.mobsandgeeks.saripaar.annotation.ValidateUsing;
import com.mobsandgeeks.saripaar.exception.SaripaarViolationException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* Maintains a registry of all {@link android.view.View}s and
* {@link com.mobsandgeeks.saripaar.adapter.ViewDataAdapter}s that are registered to rule
* {@link java.lang.annotation.Annotation}s.
*
* @author Ragunath Jawahar {@literal }
* @since 2.0
*/
final class Registry {
// Constants
public static final String TAG = "Registry";
// Stock adapters that come with Saripaar
private static final Map,
HashMap, ViewDataAdapter>> STOCK_ADAPTERS =
new HashMap, HashMap, ViewDataAdapter>>();
// Attributes
private Map,
HashMap, ViewDataAdapter>> mMappings;
/**
* Good Ol' constructor.
*/
Registry() {
mMappings = new HashMap,
HashMap, ViewDataAdapter>>();
}
/**
* This is a convenience method for Annotations that operate on {@link android.widget.TextView}
* and it's subclasses such as {@link android.widget.EditText}. Use this to register your custom
* annotation if your {@link AnnotationRule} performs validations on
* {@link java.lang.String}s, {@link java.lang.Integer}s, {@link java.lang.Float}s and
* {@link java.lang.Double} values.
*
* @param ruleAnnotations Varargs of rule {@link java.lang.annotation.Annotation}s that operate
* on {@link android.widget.TextView}s.
*/
@SuppressWarnings("unchecked")
public void register(final Class extends Annotation>... ruleAnnotations) {
for (Class extends Annotation> ruleAnnotation : ruleAnnotations) {
assertIsValidRuleAnnotation(ruleAnnotation);
final ValidateUsing validateUsing = ruleAnnotation.getAnnotation(ValidateUsing.class);
final Class> ruleDataType = Reflector.getRuleDataType(validateUsing);
HashMap, ViewDataAdapter> viewDataAdapterMap =
STOCK_ADAPTERS.get(TextView.class);
if (viewDataAdapterMap != null) {
ViewDataAdapter dataAdapter = viewDataAdapterMap.get(ruleDataType);
if (dataAdapter != null) {
register(TextView.class, ruleDataType, dataAdapter, ruleAnnotation);
} else {
String message = String.format(
"Unable to find a matching adapter for `%s`, that returns a `%s`.",
ruleAnnotation.getName(), ruleDataType.getName());
throw new SaripaarViolationException(message);
}
}
}
}
/**
* Registers {@link com.mobsandgeeks.saripaar.adapter.ViewDataAdapter}s for the associated
* {@link AnnotationRule}s and their {@link android.view.View}s.
*
* @param viewType The {@link android.view.View} type on which the {@link AnnotationRule}
* can be used.
* @param ruleDataType Data type expected by the {@link AnnotationRule}.
* @param viewDataAdapter The {@link com.mobsandgeeks.saripaar.adapter.ViewDataAdapter}
* that can get the data for the {@link AnnotationRule} from the
* {@link android.view.View}.
* @param ruleAnnotations Varargs of rule {@link java.lang.annotation.Annotation}s that
* can be used with the {@link android.view.View} and the
* {@link com.mobsandgeeks.saripaar.adapter.ViewDataAdapter}.
*
* @param Type parameter that is a subclass of {@link android.view.View} class.
* @param Data type expected by the {@link AnnotationRule} and
* is returned by the {@link com.mobsandgeeks.saripaar.adapter.ViewDataAdapter}.
*/
public void register(
final Class viewType,
final Class ruleDataType,
final ViewDataAdapter viewDataAdapter,
final Class extends Annotation>... ruleAnnotations) {
if (ruleAnnotations != null && ruleAnnotations.length > 0) {
for (Class extends Annotation> ruleAnnotation : ruleAnnotations) {
register(ruleAnnotation, ruleDataType, viewType, viewDataAdapter);
}
}
}
/**
* Retrieve all registered rule annotations.
*
* @return {@link java.util.Set} containing all registered rule
* {@link java.lang.annotation.Annotation}s.
*/
public Set> getRegisteredAnnotations() {
return mMappings.keySet();
}
/**
* Retrieves the registered {@link com.mobsandgeeks.saripaar.adapter.ViewDataAdapter} associated
* with the rule {@link java.lang.annotation.Annotation} and
* {@link android.view.View}. If no registered adapter is not found, the method looks for a
* compatible adapter instead.
*
* @param annotationType The rule annotation type that requires a data adapter.
* @param viewType The {@link android.view.View} whose adapter we are looking for.
*
* @param Type parameter that ensures type safety for the {@link View} and
* the {@link com.mobsandgeeks.saripaar.adapter.ViewDataAdapter}.
*
* @return Registered or compatible
* {@link com.mobsandgeeks.saripaar.adapter.ViewDataAdapter} or null if none is
* found.
*/
@SuppressWarnings("unchecked")
public ViewDataAdapter getDataAdapter(
final Class< ? extends Annotation> annotationType,
final Class viewType) {
HashMap, ViewDataAdapter> viewDataAdapterHashMap =
mMappings.get(annotationType);
// Check for a direct match
ViewDataAdapter matchingViewAdapter = null;
if (viewDataAdapterHashMap != null) {
matchingViewAdapter = viewDataAdapterHashMap.get(viewType);
// If no 'ViewDataAdapter' is registered, check for a compatible one
if (matchingViewAdapter == null) {
matchingViewAdapter = getCompatibleViewDataAdapter(viewDataAdapterHashMap,
viewType);
}
}
return matchingViewAdapter;
}
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Private Methods
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
private void register(
final Class extends Annotation> ruleAnnotation,
final Class ruleDataType,
final Class view,
final ViewDataAdapter viewDataAdapter) {
assertIsValidRuleAnnotation(ruleAnnotation);
assertCompatibleReturnType(ruleDataType, viewDataAdapter);
// Get the view-adapter pairs registered to a rule annotation.
HashMap, ViewDataAdapter> viewAdapterPairs;
if (mMappings.containsKey(ruleAnnotation)) {
viewAdapterPairs = mMappings.get(ruleAnnotation);
} else {
viewAdapterPairs = new HashMap, ViewDataAdapter>();
mMappings.put(ruleAnnotation, viewAdapterPairs);
}
if (viewAdapterPairs.containsKey(view)) {
String message = String.format("A '%s' for '%s' has already been registered.",
ruleAnnotation.getName(), view.getName());
Log.w(TAG, message);
} else {
viewAdapterPairs.put(view, viewDataAdapter);
}
}
private void assertIsValidRuleAnnotation(final Class extends Annotation> ruleAnnotation) {
// 1. Check for @ValidateUsing annotation
boolean validRuleAnnotation = Reflector.isAnnotated(ruleAnnotation, ValidateUsing.class);
if (!validRuleAnnotation) {
String message = String.format("'%s' is not annotated with '%s'.",
ruleAnnotation.getName(), ValidateUsing.class.getName());
throw new IllegalArgumentException(message);
}
// 2. Check for 'sequence' attribute
assertAttribute(ruleAnnotation, "sequence", Integer.TYPE);
// 3. Check for 'message' attribute
assertAttribute(ruleAnnotation, "message", String.class);
// 4. Check for 'messageResId' attribute
assertAttribute(ruleAnnotation, "messageResId", Integer.TYPE);
}
private void assertAttribute(final Class extends Annotation> annotationType,
final String attributeName, final Class> attributeType) {
Method attributeMethod = Reflector.getAttributeMethod(annotationType, attributeName);
if (attributeMethod == null) {
String message = String.format("'%s' requires the '%s' attribute.",
annotationType.getName(), attributeName);
throw new SaripaarViolationException(message);
}
final Class> returnType = attributeMethod.getReturnType();
if (!attributeType.equals(returnType)) {
String message = String.format("'%s' in '%s' should be of type '%s', but was '%s'.",
attributeName, annotationType.getName(),
attributeType.getName(), returnType.getName());
throw new SaripaarViolationException(message);
}
}
private void assertCompatibleReturnType(
final Class ruleDataType,
final ViewDataAdapter viewDataAdapter) {
Method getDataMethod = Reflector.findGetDataMethod(viewDataAdapter.getClass());
Class> adapterReturnType = getDataMethod.getReturnType();
if (!ruleDataType.equals(adapterReturnType)) {
String message = String.format("'%s' returns '%s', but expecting '%s'.",
viewDataAdapter.getClass().getName(),
adapterReturnType.getName(),
ruleDataType.getName());
throw new IllegalArgumentException(message);
}
}
private ViewDataAdapter getCompatibleViewDataAdapter(
final HashMap, ViewDataAdapter> viewDataAdapterHashMap,
final Class viewType) {
ViewDataAdapter compatibleViewAdapter = null;
Set> registeredViews = viewDataAdapterHashMap.keySet();
for (Class extends View> registeredView : registeredViews) {
if (registeredView.isAssignableFrom(viewType)) {
compatibleViewAdapter = viewDataAdapterHashMap.get(registeredView);
}
}
return compatibleViewAdapter;
}
// Register all views along with their corresponding adapters
static {
HashMap, ViewDataAdapter> adapters;
// CheckBox
adapters = new HashMap, ViewDataAdapter>();
adapters.put(Boolean.class, new CheckBoxBooleanAdapter());
STOCK_ADAPTERS.put(CheckBox.class, adapters);
// RadioButton
adapters = new HashMap, ViewDataAdapter>();
adapters.put(Boolean.class, new RadioButtonBooleanAdapter());
STOCK_ADAPTERS.put(RadioButton.class, adapters);
// RadioGroup
adapters = new HashMap, ViewDataAdapter>();
adapters.put(Boolean.class, new RadioGroupBooleanAdapter());
STOCK_ADAPTERS.put(RadioGroup.class, adapters);
// Spinner
adapters = new HashMap, ViewDataAdapter>();
adapters.put(Integer.class, new SpinnerIndexAdapter());
STOCK_ADAPTERS.put(Spinner.class, adapters);
// TextView
adapters = new HashMap, ViewDataAdapter>();
adapters.put(String.class, new TextViewStringAdapter());
adapters.put(Integer.class, new TextViewIntegerAdapter());
adapters.put(Float.class, new TextViewFloatAdapter());
adapters.put(Double.class, new TextViewDoubleAdapter());
STOCK_ADAPTERS.put(TextView.class, adapters);
}
}