com.wit.android.support.fragment.util.FragmentAnnotations.java Source code

Java tutorial

Introduction

Here is the source code for com.wit.android.support.fragment.util.FragmentAnnotations.java

Source

/*
 * =================================================================================================
 *                   Copyright (C) 2014 Martin Albedinsky [Wolf-ITechnologies]
 * =================================================================================================
 *         Licensed under the Apache License, Version 2.0 or later (further "License" only).
 * -------------------------------------------------------------------------------------------------
 * You may use this file only in compliance with the License. More details and copy of this License
 * you may obtain at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * You can redistribute, modify or publish any part of the code written within this file but as it
 * is described in the License, the software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES or CONDITIONS OF ANY KIND.
 *
 * See the License for the specific language governing permissions and limitations under the License.
 * =================================================================================================
 */
package com.wit.android.support.fragment.util;

import android.app.Activity;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.View;

import com.wit.android.support.fragment.annotation.InjectView;
import com.wit.android.support.fragment.annotation.InjectViews;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;

/**
 * <h3>Class Overview</h3>
 * Annotation utils for fragments.
 *
 * @author Martin Albedinsky
 */
public final class FragmentAnnotations {

    /**
     * Interface ===================================================================================
     */

    /**
     * <h3>Interface Overview</h3>
     * Simple callback which allows processing of all declared fields of a desired class using one of
     * {@link #iterateFields(Class, FragmentAnnotations.FieldProcessor)},
     * {@link #iterateFields(Class, FragmentAnnotations.FieldProcessor, Class)} methods.
     *
     * @author Martin Albedinsky
     */
    public static interface FieldProcessor {

        /**
         * Invoked for each of iterated fields.
         *
         * @param field The currently iterated field.
         * @param name  A name of the currently iterated field.
         */
        public void onProcessField(@NonNull Field field, @NonNull String name);
    }

    /**
     * Constants ===================================================================================
     */

    /**
     * Log TAG.
     */
    // private static final String TAG = "FragmentAnnotations";

    /**
     * Flag indicating whether the debug output trough log-cat is enabled or not.
     */
    // private static final boolean DEBUG = FragmentsConfig.LIBRARY_DEBUG_LOG_ENABLED;

    /**
     * Flag indicating whether the output for user trough log-cat is enabled or not.
     */
    // private static final boolean USER_LOG = true;

    /**
     * Methods =====================================================================================
     */

    /**
     * Same as {@link #obtainAnnotationFrom(Class, Class, Class)} with no <var>maxSuperClass</var>
     * specified.
     */
    @Nullable
    public static <A extends Annotation> A obtainAnnotationFrom(@NonNull Class<?> fromClass,
            @NonNull Class<A> classOfAnnotation) {
        return obtainAnnotationFrom(fromClass, classOfAnnotation, null);
    }

    /**
     * Obtains the specified type of an annotation from the given <var>fromClass</var> if it is presented.
     *
     * @param fromClass         A class from which should be the requested annotation obtained.
     * @param classOfAnnotation A class of the requested annotation.
     * @param maxSuperClass     If {@code not null}, this method will be called (recursively)
     *                          for all super classes of the given annotated class (max to the specified
     *                          <var>maxSuperClass</var>) until the requested annotation is presented
     *                          and obtained, otherwise annotation will be obtained only from the given
     *                          annotated class.
     * @param <A>               The type of the requested annotation.
     * @return Obtained annotation or {@code null} if the requested annotation is not presented
     * for the given class or its supers if requested.
     * @see #obtainAnnotationFrom(Class, Class)
     */
    @Nullable
    public static <A extends Annotation> A obtainAnnotationFrom(@NonNull Class<?> fromClass,
            @NonNull Class<A> classOfAnnotation, @Nullable Class<?> maxSuperClass) {
        if (fromClass.isAnnotationPresent(classOfAnnotation)) {
            return fromClass.getAnnotation(classOfAnnotation);
        } else if (maxSuperClass != null) {
            final Class<?> parent = fromClass.getSuperclass();
            if (parent != null && !parent.equals(maxSuperClass)) {
                return obtainAnnotationFrom(parent, classOfAnnotation, maxSuperClass);
            }
        }
        return null;
    }

    /**
     * Same as {@link #iterateFields(Class, FieldProcessor, Class)} with no <var>maxSuperClass</var>
     * specified.
     */
    public static void iterateFields(@NonNull Class<?> fieldsClass, @NonNull FieldProcessor processor) {
        iterateFields(fieldsClass, processor, null);
    }

    /**
     * Iterates all declared fields of the given <var>ofClass</var>.
     *
     * @param ofClass       A class of which fields to iterate.
     * @param processor     A field processor callback to be invoked for each of iterated fields.
     * @param maxSuperClass If {@code not null}, this method will be called (recursively)
     *                      for all super classes of the given class (max to the specified
     *                      <var>maxSuperClass</var>), otherwise only fields of the given class will
     *                      be iterated.
     * @see #iterateFields(Class, FieldProcessor)
     */
    public static void iterateFields(@NonNull Class<?> ofClass, @NonNull FieldProcessor processor,
            @Nullable Class<?> maxSuperClass) {
        final Field[] fields = ofClass.getDeclaredFields();
        if (fields.length > 0) {
            for (Field field : fields) {
                processor.onProcessField(field, field.getName());
            }
        }
        if (maxSuperClass != null) {
            final Class<?> parent = ofClass.getSuperclass();
            if (parent != null && !parent.equals(maxSuperClass)) {
                iterateFields(parent, processor, maxSuperClass);
            }
        }
    }

    /**
     * Same as {@link #injectView(java.lang.reflect.Field, Object, android.view.View, android.view.View.OnClickListener)}
     * with {@code null} OnClickListener.
     */
    public static boolean injectView(@NonNull Field field, @NonNull Object fieldHolder, @NonNull View root) {
        return injectViewInner(field, fieldHolder, root, null);
    }

    /**
     * Injects view obtained from the given <var>root</var> view by id presented within
     * {@link com.wit.android.support.fragment.annotation.InjectView @InjectView} or
     * {@link com.wit.android.support.fragment.annotation.InjectView.Last @InjectView.Last} annotation
     * as value to the given <var>field</var>.
     *
     * @param field           Field to which should be obtained view set as value.
     * @param fieldParent     Context in which is the passed view <var>field</var> presented.
     * @param root            Root view from which can be requested view obtained.
     * @param onClickListener An instance of OnClickListener which should be set to injected view
     *                        if {@link com.wit.android.support.fragment.annotation.InjectView#clickable() @InjectView.clickable()}
     *                        flag is set to {@code true}.
     * @return @return {@code True} when view obtained from the given root view by id presented within
     * annotation placed above the given field was successfully set to that field, {@code false}
     * if such a view was not found or the given field does not have InjectView or InjectView.Last
     * presented.
     * @throws RuntimeException If the given field is not instance of {@link android.view.View}.
     */
    public static boolean injectView(@NonNull Field field, @NonNull Object fieldParent, @NonNull View root,
            @NonNull View.OnClickListener onClickListener) {
        return injectViewInner(field, fieldParent, root, onClickListener);
    }

    /**
     * Same as {@link #injectFragmentViews(android.support.v4.app.Fragment, Class, android.view.View.OnClickListener)}
     * with without <var>onClickListener</var>.
     */
    public static void injectFragmentViews(@NonNull Fragment fragment, @Nullable Class<?> maxSuperClass) {
        final View root = fragment.getView();
        if (root != null) {
            injectViews(fragment, ((Object) fragment).getClass(), root, maxSuperClass, null);
        } else {
            throw new IllegalStateException("Can not to inject views. Fragment(" + fragment
                    + ") does not have root view created yet or it is already invalid.");
        }
    }

    /**
     * Injects all annotated field views into the given <var>fragment</var> context. Each view to inject
     * must be marked with {@link com.wit.android.support.fragment.annotation.InjectView @InjectView}
     * or {@link com.wit.android.support.fragment.annotation.InjectView.Last @InjectView.Last}
     * annotation and a class of <var>fragment</var> (also its super classes) must be marked with
     * {@link com.wit.android.support.fragment.annotation.InjectViews @InjectViews}, otherwise views
     * of classes without @InjectViews annotation will be not injected. This is due to optimization,
     * because some of super classes of the given <var>fragment</var> may not have @InjectView annotated
     * fields, but still all their fields would be without optimization iterated.
     * <p>
     * <b>Note</b>, that views to inject will be obtained from the current root view of the given
     * fragment ({@link android.support.v4.app.Fragment#getView()}).
     *  <p>
     * <b>If the context of which views you want to inject has like really a lot of fields, consider
     * to not use this approach, because it can really decrease performance (increase time of displaying
     * new screen, etc.).</b>
     *
     * @param fragment        An instance of the fragment into which context should be views injected.
     * @param maxSuperClass   If {@code not null}, this method will be called (recursively)
     *                        for all super classes of the given fragment (max to the specified
     *                        <var>maxSuperClass</var>), otherwise only fields of the given fragment's
     *                        class will be iterated.
     * @param onClickListener An instance of OnClickListener which should be set to injected views
     *                        if {@link com.wit.android.support.fragment.annotation.InjectView#clickable() @InjectView.clickable()}
     *                        flag is set to {@code true}.
     * @throws java.lang.IllegalStateException If the given fragment does not have created root view
     *                                         yet or it is already invalid.
     * @throws RuntimeException                If one of the marked fields of the fragment (or its super)
     *                                         to inject is not instance of {@link android.view.View}.
     * @see #injectActivityViews(android.app.Activity, Class)
     */
    public static void injectFragmentViews(@NonNull Fragment fragment, @Nullable Class<?> maxSuperClass,
            @NonNull View.OnClickListener onClickListener) {
        final View root = fragment.getView();
        if (root != null) {
            injectViews(fragment, ((Object) fragment).getClass(), root, maxSuperClass, onClickListener);
        } else {
            throw new IllegalStateException("Can not to inject views. Fragment(" + fragment
                    + ") does not have root view created yet or it is already invalid.");
        }
    }

    /**
     * Same as {@link #injectActivityViews(android.app.Activity, Class, android.view.View.OnClickListener)}
     * with without <var>onClickListener</var>.
     */
    public static void injectActivityViews(@NonNull Activity activity, @Nullable Class<?> maxSuperClass) {
        final View content = activity.getWindow().getDecorView().findViewById(android.R.id.content);
        if (content != null) {
            injectViews(activity, ((Object) activity).getClass(), content, maxSuperClass, null);
        } else {
            throw new IllegalStateException("Can not to inject views. Activity(" + activity
                    + ") does not have root view with id(android.R.id.content).");
        }
    }

    /**
     * Same as {@link #injectFragmentViews(android.support.v4.app.Fragment, Class)}, where the given
     * <var>activity</var> will be used as context into which will be views injected.
     *  <p>
     * <b>Note</b>, that views to inject will be obtained from the current root content view of the
     * given activity ({@code activity.getWindow().getDecorView().findViewById(android.R.id.content)}).
     *
     * @param activity        An instance of the activity into which context should be views injected.
     * @param maxSuperClass   If {@code not null}, this method will be called (recursively)
     *                        for all super classes of the given activity (max to the specified
     *                        <var>maxSuperClass</var>), otherwise only fields of the given activity's
     *                        class will be iterated.
     * @param onClickListener An instance of OnClickListener which should be set to injected views
     *                        if {@link com.wit.android.support.fragment.annotation.InjectView#clickable() @InjectView.clickable()}
     *                        flag is set to {@code true}.
     * @throws java.lang.IllegalStateException If view with the {@link android.R.id#content} id can
     *                                         not be found within the given activity's view hierarchy.
     * @throws RuntimeException                If one of the marked fields of the given activity (or its
     *                                         super) to inject is not instance of {@link android.view.View}.
     */
    public static void injectActivityViews(@NonNull Activity activity, @Nullable Class<?> maxSuperClass,
            @NonNull View.OnClickListener onClickListener) {
        final View content = activity.getWindow().getDecorView().findViewById(android.R.id.content);
        if (content != null) {
            injectViews(activity, ((Object) activity).getClass(), content, maxSuperClass, onClickListener);
        } else {
            throw new IllegalStateException("Can not to inject views. Activity(" + activity
                    + ") does not have root view with id(android.R.id.content).");
        }
    }

    /**
     * Injects all annotated field views. Note, that this can run recursively, so it will check all
     * fields for {@link com.wit.android.support.fragment.annotation.InjectView @InjectView} or
     * {@link com.wit.android.support.fragment.annotation.InjectView.Last @InjectView.Last}
     * annotation presented above each of fields of the given <var>classOfRootContext</var>.
     *
     * @param rootContext        A context in which is the passed <var>root</var> view presented and
     *                           into which should be views injected.
     * @param classOfRootContext A class of a context for the current recursive iteration.
     * @param root               A root view of the given context.
     * @param maxSuperClass      If {@code not null}, this method will be called (recursively)
     *                           for all super classes of the given class (max to the specified
     *                           <var>maxSuperClass</var>), otherwise only fields of the given class
     *                           will be iterated.
     * @param onClickListener    An instance of OnClickListener which should be set to injected views
     *                           if {@link com.wit.android.support.fragment.annotation.InjectView#clickable() @InjectView.clickable()}
     *                           flag is set to {@code true}.
     */
    private static void injectViews(Object rootContext, Class<?> classOfRootContext, View root,
            Class<?> maxSuperClass, View.OnClickListener onClickListener) {
        // Class of fragment must have @InjectViews annotation present to really iterate and inject
        // annotated views.
        if (classOfRootContext.isAnnotationPresent(InjectViews.class)) {
            // Process annotated fields.
            final Field[] fields = classOfRootContext.getDeclaredFields();
            if (fields.length > 0) {
                for (Field field : fields) {
                    injectViewInner(field, rootContext, root, onClickListener);
                    if (field.isAnnotationPresent(InjectView.Last.class)) {
                        break;
                    }
                }
            }
        }

        // Inject also views of supper class, but only to this BaseFragment super.
        final Class<?> superOfRootContext = classOfRootContext.getSuperclass();
        if (superOfRootContext != null && !superOfRootContext.equals(maxSuperClass)) {
            injectViews(rootContext, superOfRootContext, root, maxSuperClass, onClickListener);
        }
    }

    /**
     * Sets a view obtained by the given <var>id</var> from the given <var>root</var> view as value
     * the the given <var>field</var>.
     *
     * @param field           A field to which should be obtained view set as value.
     * @param fieldParent     A context in which is the passed view <var>field</var> presented.
     * @param root            A root view of the given context.
     * @param onClickListener An instance of OnClickListener which should be set to injected view
     *                        if {@link com.wit.android.support.fragment.annotation.InjectView#clickable() @InjectView.clickable()}
     *                        flag is set to {@code true}.
     * @return {@code True} when view obtained from the given root view by id presented within
     * annotation placed above the given field was successfully set to that field, {@code false}
     * if such a view was not found or the given field does not have InjectView or InjectView.Last
     * presented.
     */
    private static boolean injectViewInner(Field field, Object fieldParent, View root,
            View.OnClickListener onClickListener) {
        View view;
        if (field.isAnnotationPresent(InjectView.class)) {
            final InjectView injectView = field.getAnnotation(InjectView.class);
            if ((view = root.findViewById(injectView.value())) != null && attachView(field, fieldParent, view)) {
                if (injectView.clickable()) {
                    view.setOnClickListener(onClickListener);
                }
                return true;
            }
        } else if (field.isAnnotationPresent(InjectView.Last.class)) {
            final InjectView.Last injectLastView = field.getAnnotation(InjectView.Last.class);
            if ((view = root.findViewById(injectLastView.value())) != null
                    && attachView(field, fieldParent, view)) {
                if (injectLastView.clickable()) {
                    view.setOnClickListener(onClickListener);
                }
                return true;
            }
        }
        return false;
    }

    /**
     * Attaches the given <var>view</var> instance to the given <var>field</var> as its value for the
     * given <var>fieldParent</var> object.
     *
     * @param field       A field to which should be the given view set as value.
     * @param fieldParent A context in which is the passed view <var>field</var> presented.
     * @param view        View to be set as value of the given field.
     * @return {@code True} when attaching succeeded, {@code false} otherwise.
     * @throws RuntimeException If the given field is not instance of {@link android.view.View}.
     */
    private static boolean attachView(Field field, Object fieldParent, View view) {
        // Check correct type of the field.
        if (View.class.isAssignableFrom(field.getType())) {
            field.setAccessible(true);
            try {
                field.set(fieldParent, view);
                return true;
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        throw new RuntimeException("Field(" + fieldParent.getClass().getSimpleName() + "." + field.getName()
                + ") is not instance of view, thus can not be injected.");
    }
}