com.wit.android.support.database.adapter.BaseCursorAdapter.java Source code

Java tutorial

Introduction

Here is the source code for com.wit.android.support.database.adapter.BaseCursorAdapter.java

Source

/*
 * =================================================================================================
 *                Copyright (C) 2013 - 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.database.adapter;

import android.content.Context;
import android.content.res.Resources;
import android.database.Cursor;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.LayoutRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.support.v4.widget.CursorAdapter;
import android.view.AbsSavedState;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.wit.android.support.database.MissingDatabaseAnnotationException;
import com.wit.android.support.database.annotation.CursorItemView;
import com.wit.android.support.database.annotation.CursorItemViewHolder;
import com.wit.android.support.database.annotation.CursorItemViewHolderFactory;
import com.wit.android.support.database.util.DatabaseAnnotations;

import java.lang.reflect.InvocationTargetException;

/**
 * <h3>Class Overview</h3>
 * todo: description
 * <h3>Accepted annotations</h3>
 * <ul>
 * <li>
 * {@link com.wit.android.support.database.annotation.CursorItemView @CursorItemView} <b>[class - inherited]</b>
 * <p>
 * If this annotation is presented, a resource id provided by this annotation will be used to inflate
 * the desired view in {@link #onCreateView(int, android.database.Cursor, android.view.LayoutInflater, android.view.ViewGroup)}.
 * </li>
 * <li>
 * {@link com.wit.android.support.database.annotation.CursorItemViewHolderFactory @CursorItemViewHolderFactory} <b>[class - inherited]</b>
 * <p>
 * If this annotation is presented, a class provided by this annotation will be used to create instances
 * of holders for item views.
 * </li>
 * <li>
 * {@link com.wit.android.support.database.annotation.CursorItemViewHolder @CursorItemHolder} <b>[class - inherited]</b>
 * <p>
 * If this annotation is presented, a class provided by this annotation will be used to instantiate
 * an instance of the desired holder in {@link #onCreateViewHolder(int, android.database.Cursor, android.view.View)}.
 * </li>
 * </ul>
 * <h3>State saving</h3>
 * <pre>
 * public class MyCursorAdapter extends BaseCursorAdapter&lt;Cursor&gt; {
 *
 *     // ...
 *
 *     &#64;Override
 *     protected Parcelable onSaveInstanceState() {
 *         final MyCursorAdapterState state = new MyCursorAdapterState(super.onSaveInstanceState());
 *
 *         // ...
 *         // Pass here all data of this adapter which need to be saved to the state.
 *         // ...
 *
 *         return state;
 *     }
 *
 *     &#64;Override
 *     protected void onRestoreInstanceState(Parcelable savedState) {
 *          if (!(savedState instanceof MyCursorAdapterState)) {
 *              // Passed savedState is not our state, let super to process it.
 *              super.onRestoreInstanceState(savedState);
 *              return;
 *          }
 *
 *          final MyCursorAdapterState state = (MyCursorAdapterState) savedState;
 *          // Pass superState to super to process it.
 *          super.onRestoreInstanceState(savedState.getSuperState());
 *
 *          // ...
 *          // Set here all data of this adapter which need to be restored from the savedState.
 *          // ...
 *     }
 *
 *     // ...
 *
 *     // Implementation of BaseSavedState for this adapter.
 *     static class MyCursorAdapterState extends BaseSavedState {
 *
 *         // Each implementation of saved state need to have its own CREATOR provided.
 *         public static final Creator&lt;MyCursorAdapterState&gt; CREATOR = new Creator&lt;&gt; {
 *
 *              &#64;Override
 *              public MyCursorAdapterState createFromParcel(Parcel source) {
 *                  return new MyCursorAdapterState(source);
 *              }
 *
 *              &#64;Override
 *              public MyCursorAdapterState[] newArray(int size) {
 *                  return new MyCursorAdapterState[size];
 *              }
 *         }
 *
 *         MyCursorAdapterState(Parcel source) {
 *              super(source);
 *              // Restore here state's data.
 *         }
 *
 *         // Constructor used to chain the state of inheritance hierarchies.
 *         MyCursorAdapterState(Parcelable superState) {
 *              super(superState);
 *         }
 *
 *         &#64;Override
 *         public void writeToParcel(Parcel dest, int flags) {
 *              super.writeToParcel(dest, flags);
 *              // Save here state's data.
 *         }
 *     }
 * }
 * </pre>
 *
 * @param <C> A type of the cursor of which data will be presented by a subclass of this BaseCursorAdapter.
 * @author Martin Albedinsky
 */
public abstract class BaseCursorAdapter<C extends Cursor> extends CursorAdapter
        implements FactoryHolderCursorAdapter {

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

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

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

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

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

    /**
     * Static members ==============================================================================
     */

    /**
     * Members =====================================================================================
     */

    /**
     * Context in which will be this adapter used.
     */
    protected final Context mContext;

    /**
     * Layout inflater used to inflate new views for this adapter.
     */
    protected final LayoutInflater mLayoutInflater;

    /**
     * Application resources.
     */
    protected final Resources mResources;

    /**
     * Class of cursor wrapper used to wrap cursor data set of this adapter in {@link #onWrapCursor(android.database.Cursor)}.
     */
    private Class<C> mClassOfCursorWrapper;

    /**
     * Item view type for the current {@link #getView(int, android.view.View, android.view.ViewGroup)}
     * iteration.
     */
    private int mCurrentViewType;

    /**
     * Resource id of the view which should be inflated as item view.
     */
    private int mViewRes = -1;

    /**
     * Class to be used to instantiate an instance of holder for item view.
     */
    private Class<? extends CursorViewHolder> mClassOfHolder;

    /**
     * Factory responsible for instantiation of item view holders for this adapter.
     */
    private CursorViewHolderFactory mHolderFactory;

    /**
     * Registered OnCursorDataSetListener callback.
     */
    private OnCursorDataSetListener mDataSetListener;

    /**
     * Registered OnCursorDataSetActionListener callback.
     */
    private OnCursorDataSetActionListener mDataSetActionListener;

    /**
     * Constructors ================================================================================
     */

    /**
     * Same as {@link #BaseCursorAdapter(android.content.Context, android.database.Cursor)} with
     * {@code null} initial cursor.
     */
    public BaseCursorAdapter(@NonNull Context context) {
        this(context, (C) null);
    }

    /**
     * Same as {@link #BaseCursorAdapter(android.content.Context, android.database.Cursor, int)} with
     * {@link #FLAG_REGISTER_CONTENT_OBSERVER} flag.
     */
    public BaseCursorAdapter(@NonNull Context context, @Nullable C cursor) {
        this(context, cursor, FLAG_REGISTER_CONTENT_OBSERVER);
    }

    /**
     * Same as {@link #BaseCursorAdapter(android.content.Context, Class, int)} with
     * {@link #FLAG_REGISTER_CONTENT_OBSERVER} flag.
     */
    public BaseCursorAdapter(@NonNull Context context, @NonNull Class<C> classOfCursorWrapper) {
        this(context, classOfCursorWrapper, FLAG_REGISTER_CONTENT_OBSERVER);
    }

    /**
     * Creates a new instance of BaseCursorAdapter with {@code null} initial cursor.
     * <p>
     * If {@link com.wit.android.support.database.annotation.CursorItemView @CursorItemView},
     * {@link com.wit.android.support.database.annotation.CursorItemViewHolderFactory @CursorItemViewHolderFactory},
     * or {@link com.wit.android.support.database.annotation.CursorItemViewHolder @CursorItemViewHolder}
     * annotations are presented above a subclass of this BaseCursorAdapter, they will be processed here.
     *
     * @param context              Context in which will be this adapter used.
     * @param classOfCursorWrapper A class of cursor wrapper used to wrap each cursor dispatched to
     *                             this adapter by {@link #swapCursor(android.database.Cursor)}. Pass
     *                             {@code null} if cursor wrapping is not needed.
     * @param flags                Set of flags to set up cursor management behaviour.
     * @throws NullPointerException If the given context is invalid.
     */
    public BaseCursorAdapter(@NonNull Context context, @NonNull Class<C> classOfCursorWrapper, int flags) {
        this(context, (C) null, flags);
        this.mClassOfCursorWrapper = classOfCursorWrapper;
    }

    /**
     * Creates a new instance of BaseCursorAdapter with the given initial {@code cursor} data set.
     * <p>
     * If {@link com.wit.android.support.database.annotation.CursorItemView @CursorItemView} or
     * {@link com.wit.android.support.database.annotation.CursorItemViewHolder @CursorItemViewHolder}
     * annotations are presented above a subclass of this BaseCursorAdapter, they will be processed here.
     *
     * @param context Context in which will be this adapter used.
     * @param cursor  Initial cursor data set for this adapter. Can be {@code null}.
     * @param flags   Set of flags to set up cursor management behaviour.
     * @throws NullPointerException If the given context is invalid.
     */
    public BaseCursorAdapter(@NonNull Context context, @Nullable C cursor, int flags) {
        super(context, cursor, flags);
        this.processClassAnnotations(((Object) this).getClass());

        // Set up.
        this.mContext = context;
        this.mLayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        this.mResources = context.getResources();
    }

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

    /**
     * Public --------------------------------------------------------------------------------------
     */

    /**
     * Called to save the current state of this adapter.
     *
     * @return Saved state of this adapter or an <b>empty</b> state if this adapter does not need to
     * save its state.
     */
    @NonNull
    public Parcelable dispatchSaveInstanceState() {
        return onSaveInstanceState();
    }

    /**
     * Called to restore a previous state, saved by {@link #dispatchSaveInstanceState()}, of this adapter.
     *
     * @param savedState Should be the same state as obtained by {@link #dispatchSaveInstanceState()}
     *                   before.
     */
    public void dispatchRestoreInstanceState(@NonNull Parcelable savedState) {
        onRestoreInstanceState(savedState);
    }

    /**
     * Notifies the attached OnCursorDataSetListener listeners, that the data set of this adapter has
     * been loaded.
     * <p>
     * Unlike {@link #notifyDataSetChanged()} this can be used to notify data set change to target
     * directly data loading from database not just inner data set change, like change in selection,
     * for such a purpose use {@link #notifyDataSetChanged()} instead.
     */
    public void notifyDataSetLoaded() {
        notifyCursorDataSetLoadedInner();
    }

    /**
     * This will also notify the current {@link OnCursorDataSetListener} callback if it is presented.
     */
    @Override
    public void notifyDataSetChanged() {
        super.notifyDataSetChanged();
        notifyCursorDataSetChangedInner();
    }

    /**
     * This will also notify the current {@link OnCursorDataSetActionListener} callback if it is presented.
     */
    @Override
    public void notifyDataSetInvalidated() {
        super.notifyDataSetInvalidated();
        notifyCursorDataSetInvalidatedInner();
    }

    /**
     */
    @Nullable
    @Override
    public C getItem(int position) {
        return this.getItemInner(position);
    }

    /**
     */
    @Override
    public final int getCurrentViewType() {
        return mCurrentViewType;
    }

    /**
     */
    @Override
    public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
        return this.getViewInner(position, convertView, parent);
    }

    /**
     * @deprecated Due to optimization. Use {@link #onCreateView(int, android.database.Cursor, android.view.LayoutInflater, android.view.ViewGroup)}
     * instead.
     */
    @Override
    @Deprecated
    public final View newView(Context context, Cursor cursor, ViewGroup viewGroup) {
        return null;
    }

    /**
     * @deprecated Due to optimization. Use {@link #onBindView(int, android.database.Cursor, Object)}
     * instead.
     */
    @Override
    @Deprecated
    public final void bindView(View view, Context context, Cursor cursor) {
    }

    /**
     */
    @Nullable
    @Override
    public C swapCursor(@Nullable Cursor newCursor) {
        return this.swapCursorInner(newCursor);
    }

    /**
     * Wraps the given <var>cursor</var> into wrapper specific for this cursor adapter.
     *
     * @param cursor Cursor to wrap.
     * @return An instance of cursor wrapper holding the given cursor or cursor itself if this cursor
     * adapter does not requires cursor wrapping.
     */
    @NonNull
    public C wrapCursor(@NonNull Cursor cursor) {
        return onWrapCursor(cursor);
    }

    /**
     * Getters + Setters ---------------------------------------------------------------------------
     */

    /**
     */
    @Nullable
    @Override
    public C getCursor() {
        return this.getCursorInner();
    }

    /**
     * Returns the context with which was this adapter created.
     *
     * @return Same context as passed during initialization.
     */
    @NonNull
    public Context getContext() {
        return mContext;
    }

    /**
     * Returns layout inflater instance provided by the context passed during initialization of this
     * adapter.
     *
     * @return An instance of LayoutInflater.
     */
    @NonNull
    public LayoutInflater getLayoutInflater() {
        return mLayoutInflater;
    }

    /**
     * Returns an application's resources provided by the context passed during initialization of
     * this adapter.
     *
     * @return An application's resources.
     */
    @NonNull
    public Resources getResources() {
        return mResources;
    }

    /**
     * Wrapped {@link android.content.res.Resources#getString(int)} for the current resources.
     */
    @NonNull
    public String getString(@StringRes int resId) {
        return (mResources != null) ? mResources.getString(resId) : "";
    }

    /**
     * Wrapped {@link android.content.res.Resources#getString(int, Object...)} for the current resources.
     */
    @NonNull
    public String getString(@StringRes int resId, @Nullable Object... args) {
        return (mResources != null) ? mResources.getString(resId, args) : "";
    }

    /**
     * Wrapped {@link android.content.res.Resources#getText(int)} for the current resources.
     */
    @NonNull
    public CharSequence getText(@StringRes int resId) {
        return (mResources != null) ? mResources.getText(resId) : "";
    }

    /**
     * Wrapped {@link android.content.res.Resources#getText(int, java.lang.CharSequence)} for the
     * current resources.
     */
    @NonNull
    public CharSequence getText(@StringRes int resId, @Nullable CharSequence def) {
        return (mResources != null) ? mResources.getText(resId, def) : "";
    }

    /**
     * Registers a callback to be invoked when the data set of this adapter has been changed or
     * invalidated.
     *
     * @param listener Listener callback.
     */
    public void setOnDataSetListener(@NonNull OnCursorDataSetListener listener) {
        this.mDataSetListener = listener;
    }

    /**
     * Removes the current OnCursorDataSetListener callback.
     */
    public void removeOnDataSetListener() {
        this.mDataSetListener = null;
    }

    /**
     * Registers a callback to be invoked when there was a specific action performed above the
     * current data set of this adapter.
     *
     * @param listener Listener callback.
     */
    public void setOnDataSetActionListener(@NonNull OnCursorDataSetActionListener listener) {
        this.mDataSetActionListener = listener;
    }

    /**
     * Removes the current OnCursorDataSetActionListener callback.
     */
    public void removeOnDataSetActionListener() {
        this.mDataSetActionListener = null;
    }

    /**
     */
    @Override
    public int getAdapterId() {
        return 0;
    }

    /**
     * Protected -----------------------------------------------------------------------------------
     */

    /**
     * Same as {@link #getCurrentViewType()}.
     */
    protected int currentViewType() {
        return mCurrentViewType;
    }

    /**
     * Inflates a new view hierarchy from the given xml resource.
     *
     * @param resource Resource id of a view to inflate.
     * @param parent   A parent view, to resolve correct layout params for the newly creating view.
     * @return The root view of the inflated view hierarchy.
     * @see android.view.LayoutInflater#inflate(int, android.view.ViewGroup)
     */
    @NonNull
    protected final View inflate(@LayoutRes int resource, @NonNull ViewGroup parent) {
        return mLayoutInflater.inflate(resource, parent, false);
    }

    /**
     * Called from {@link #wrapCursor(android.database.Cursor)} to wrap the given <var>cursor</var>.
     * <p>
     * By default this will try to wrap the specified cursor into cursor wrapper class of which was
     * provided to constructor of this adapter. If no cursor wrapper class was provided, cursor itself
     * will be returned.
     *
     * @param cursor Cursor to wrap.
     * @return Wrapped cursor into cursor wrapper or cursor itself if no wrapping is needed.
     */
    @NonNull
    protected C onWrapCursor(@NonNull Cursor cursor) {
        return this.wrapCursorInner(cursor);
    }

    /**
     * Moves the given <var>cursor</var> to the requested <var>position</var>.
     * <p>
     * By default this will move the given cursor to position corrected by {@link #correctCursorPosition(int)}.
     * <p>
     * This is called from {@link #getView(int, android.view.View, android.view.ViewGroup)} for the
     * currently iterated position.
     *
     * @param cursor   Cursor which should be moved to the specified position.
     * @param position Position to which should be cursor moved.
     * @return {@code True} if the cursor has been moved to the requested position, {@code false}
     * otherwise.
     */
    protected boolean moveCursorToPosition(@NonNull C cursor, int position) {
        return cursor.moveToPosition(correctCursorPosition(position));
    }

    /**
     * Corrects the given cursor <var>position</var> according to the current data set size to ensure
     * that the current cursor will be moved to the correct position.
     * <p>
     * This can be useful when this cursor adapter presents also data which are not available within
     * the current cursor, like array of alphabetic headers, when the current cursor is sorted alphabetically
     * and this adapter presents above each alphabetical section a header with a letter specific for
     * such a section, so practically the current data set consists of two data sets, one is cursor
     * and other one is array of headers.
     *
     * @param position Position which should be corrected.
     * @return The given position.
     * @see #moveCursorToPosition(android.database.Cursor, int)
     */
    protected int correctCursorPosition(int position) {
        return position;
    }

    /**
     * Invoked to create a view for an item from the current cursor data set at the specified position.
     * <p>
     * This is invoked only if <var>convertView</var> for the specified <var>position</var> in
     * {@link #getView(int, android.view.View, android.view.ViewGroup)} was {@code null}.
     * <p>
     * <b>Note</b>, that if {@link com.wit.android.support.database.annotation.CursorItemView @CursorItemView}
     * annotation is presented, a resource id provided by this annotation will be used to inflate the
     * requested view, otherwise implementation of this method is <b>required</b> or exception will
     * be thrown.
     *
     * @param position Position of the item from the current cursor data set for which should be a
     *                 new view created.
     * @param cursor   Current cursor already moved to the specified position.
     * @param inflater Layout inflater which can be used to inflate the requested view.
     * @param parent   A parent view, to resolve correct layout params for the newly creating view.
     * @return New instance of the requested view.
     * @throws MissingDatabaseAnnotationException If there is no @CursorItemView annotation presented.
     * @see #inflate(int, android.view.ViewGroup)
     */
    @NonNull
    protected View onCreateView(int position, @NonNull C cursor, @NonNull LayoutInflater inflater,
            @NonNull ViewGroup parent) {
        if (mViewRes >= 0) {
            return inflater.inflate(mViewRes, parent, false);
        }
        throw new MissingDatabaseAnnotationException(
                "Cannot to create view for position(" + position + ") without resource id. " + "No @"
                        + CursorItemView.class.getSimpleName() + " annotation presented.");
    }

    /**
     * Invoked to create a view holder for a view of an item from the current cursor data set at the
     * specified position.
     * <p>
     * This is invoked only if <var>convertView</var> for the specified <var>position</var> in
     * {@link #getView(int, android.view.View, android.view.ViewGroup)} was {@code null}, so as
     * view also holder need to be created.
     * <p>
     * If {@link com.wit.android.support.database.annotation.CursorItemViewHolderFactory @CursorItemViewHolderFactory}
     * annotation is presented, factory instantiated from the class provided by this annotation will
     * be used to create the requested view holder, otherwise
     * {@link com.wit.android.support.database.annotation.CursorItemViewHolder @CursorItemViewHolder}
     * annotation will be processed as described below.
     * <p>
     * If {@link com.wit.android.support.database.annotation.CursorItemViewHolder @CursorItemViewHolder}
     * annotation is presented, a class provided by this annotation will be used to instantiate the
     * requested view holder, otherwise {@code null} holder will be returned so the view created
     * by {@link #onCreateView(int, android.database.Cursor, android.view.LayoutInflater, android.view.ViewGroup)}
     * for the specified position will be passed as holder to {@link #onBindView(int, android.database.Cursor, Object)}.
     *
     * @param position Position of the item from the current data set for which should be a new view
     *                 holder created.
     * @param cursor   Current cursor already moved to the specified position.
     * @param itemView An instance of the same view as obtained from
     *                 {@link #onCreateView(int, android.database.Cursor, android.view.LayoutInflater, android.view.ViewGroup)}
     *                 for the specified position.
     * @return New instance of the requested view holder.
     * @throws java.lang.IllegalStateException If the class provided by @CursorItemViewHolder annotation can not
     *                                         be accessed or does not have an empty public constructor.
     */
    @Nullable
    protected Object onCreateViewHolder(int position, @NonNull C cursor, @NonNull View itemView) {
        if (mHolderFactory != null) {
            final CursorViewHolder holder = mHolderFactory.createHolder(this, position, itemView);
            if (holder != null) {
                holder.create(position, itemView);
                return holder;
            }
        }
        if (mClassOfHolder != null) {
            CursorViewHolder holder;
            try {
                holder = mClassOfHolder.newInstance();
            } catch (InstantiationException | IllegalAccessException e) {
                throw new IllegalStateException("Failed to create view holder from class(" + mClassOfHolder + "). "
                        + "Check if this holder class has public access and empty public constructor.");
            }
            holder.create(position, itemView);
            return holder;
        }
        // Return null holder, so view created by onCreateView(...) will be passed as holder to onBindView(...).
        return null;
    }

    /**
     * Invoked to set up and populate a view of an item from the current cursor data set at the specified
     * position. This is invoked whenever {@link #getView(int, android.view.View, android.view.ViewGroup)}
     * is called.
     * <p>
     * <b>Note</b>, that if {@link #onCreateViewHolder(int, android.database.Cursor, android.view.View)}
     * returns {@code null} for the specified <var>position</var> here passed <var>viewHolder</var>
     * will be the view created by {@link #onCreateView(int, android.database.Cursor, android.view.LayoutInflater, android.view.ViewGroup)}
     * for the specified position or just recycled view for such a position. This approach can be used,
     * when a view hierarchy of the specific list item is represented by one custom view, where such
     * a view represents a holder for all its child views.
     * <p>
     * By default this will try to bind the given <var>viewHolder</var> (if it is instanceof {@link CursorViewHolder}),
     * otherwise implementation of this method is <b>required</b> or exception will be thrown.
     *
     * @param position   Position of the item from the current cursor data set of which view to set up.
     * @param cursor     Current cursor already moved to the specified position.
     * @param viewHolder An instance of the same holder as provided by {@link #onCreateViewHolder(int, android.database.Cursor, android.view.View)}
     *                   for the specified position or converted view as holder as described above.
     * @throws java.lang.IllegalStateException If binding process for the specified position fails.
     */
    protected void onBindView(int position, @NonNull C cursor, @NonNull Object viewHolder) {
        if (!bindViewInner(position, cursor, viewHolder)) {
            throw new IllegalStateException("Failed to bind view at position(" + position + "). " + viewHolder
                    + " is not instance of CursorViewHolder.");
        }
    }

    /**
     * Called to notify, that the given <var>action</var> was performed for the specified <var>position</var>.
     * <p>
     * If {@link #onDataSetActionSelected(int, int, Object)} will not process this call, the current
     * {@link OnCursorDataSetActionListener} will be notified if it is presented.
     *
     * @param action   Action to be dispatched.
     * @param position The position for which was the given action performed.
     * @param data     Additional data for the selected action to be dispatched to the listener.
     */
    protected void notifyDataSetActionSelected(int action, int position, @Nullable Object data) {
        if (!onDataSetActionSelected(action, position, data)) {
            notifyCursorDataSetActionSelectedInner(action, position, data);
        }
    }

    /**
     * Invoked immediately after {@link #notifyDataSetActionSelected(int, int, Object)} was called.
     *
     * @return {@code True} to indicate that this event was processed here, otherwise the current
     * {@link OnCursorDataSetActionListener} will be notified about this event if it is presented.
     */
    protected boolean onDataSetActionSelected(int action, int position, @Nullable Object data) {
        return false;
    }

    /**
     * Invoked immediately after {@link #dispatchSaveInstanceState()} was called, to save the current
     * state of this adapter.
     * <p>
     * If you decide to override this method, do not forget to call {@code super.onSaveInstanceState()}
     * and pass super state obtained from the super to constructor of your {@link BaseSavedState}
     * implementation with such a parameter to ensure the state of all classes along the chain is saved.
     *
     * @return Return here your implementation of {@link BaseSavedState} if you want to save state of
     * your adapter, otherwise no implementation of this method is necessary.
     */
    @NonNull
    protected Parcelable onSaveInstanceState() {
        return BaseSavedState.EMPTY_STATE;
    }

    /**
     * Called immediately after {@link #dispatchRestoreInstanceState(android.os.Parcelable)} was called,
     * to restore a previous state, (saved in {@link #onSaveInstanceState()}), of this adapter.
     *
     * @param savedState Before saved state of this adapter.
     */
    protected void onRestoreInstanceState(@NonNull Parcelable savedState) {
    }

    /**
     * Called to process all annotation of the given <var>classOfAdapter</var>.
     *
     * @param classOfAdapter This adapter's class.
     */
    private void processClassAnnotations(Class<?> classOfAdapter) {
        // Obtain item view.
        final CursorItemView itemView = DatabaseAnnotations.obtainAnnotationFrom(classOfAdapter,
                CursorItemView.class, BaseCursorAdapter.class);
        if (itemView != null) {
            this.mViewRes = itemView.value();
        }
        // Obtain item view holder factory.
        final CursorItemViewHolderFactory holderFactory = DatabaseAnnotations.obtainAnnotationFrom(classOfAdapter,
                CursorItemViewHolderFactory.class, BaseCursorAdapter.class);
        if (holderFactory != null) {
            try {
                this.mHolderFactory = holderFactory.value().newInstance();
            } catch (InstantiationException | IllegalAccessException e) {
                throw new IllegalStateException(
                        "Failed to create view holder factory from class(" + holderFactory.value() + "). "
                                + "Check if this factory class has public access and empty public constructor.");
            }
        }
        // Obtain item view holder.
        final CursorItemViewHolder itemViewHolder = DatabaseAnnotations.obtainAnnotationFrom(classOfAdapter,
                CursorItemViewHolder.class, BaseCursorAdapter.class);
        if (itemViewHolder != null) {
            this.mClassOfHolder = itemViewHolder.value();
        }
    }

    /**
     * Called to dispatch data set loading to the current OnCursorDataSetListener callback.
     */
    @SuppressWarnings("unchecked")
    void notifyCursorDataSetLoadedInner() {
        if (mDataSetListener != null) {
            mDataSetListener.onCursorDataSetLoaded(this);
        }
    }

    /**
     * Called to dispatch data set change to the current OnCursorDataSetListener callback.
     */
    @SuppressWarnings("unchecked")
    void notifyCursorDataSetChangedInner() {
        if (mDataSetListener != null) {
            mDataSetListener.onCursorDataSetChanged(this);
        }
    }

    /**
     * Called to dispatch data set invalidation to the current OnCursorDataSetListener callback.
     */
    @SuppressWarnings("unchecked")
    void notifyCursorDataSetInvalidatedInner() {
        if (mDataSetListener != null) {
            mDataSetListener.onCursorDataSetInvalidated(this);
        }
    }

    /**
     * Inner implementation of {@link #notifyDataSetActionSelected(int, int, Object)} to hide such an
     * implementation.
     */
    @SuppressWarnings("unchecked")
    void notifyCursorDataSetActionSelectedInner(int action, int position, Object data) {
        if (mDataSetActionListener != null) {
            mDataSetActionListener.onCursorDataSetActionSelected(this, action, position, getItemId(position), data);
        }
    }

    /**
     * Private -------------------------------------------------------------------------------------
     */

    /**
     * Inner implementation of {@link #getCursor()} to hide such an implementation.
     */
    @SuppressWarnings("unchecked")
    private C getCursorInner() {
        return (C) super.getCursor();
    }

    /**
     * Inner implementation of {@link #getItem(int)} to hide such an implementation.
     */
    @SuppressWarnings("unchecked")
    private C getItemInner(int position) {
        return (C) super.getItem(correctCursorPosition(position));
    }

    /**
     * Inner implementation of {@link #getView(int, android.view.View, android.view.ViewGroup)}  to
     * hide such an implementation.
     */
    @SuppressWarnings("unchecked")
    private View getViewInner(int position, View convertView, ViewGroup parent) {
        final C cursor = getCursor();
        if (cursor == null) {
            throw new IllegalStateException("Can not present data from the invalid cursor.");
        }
        if (!moveCursorToPosition(cursor, position)) {
            throw new IllegalStateException(
                    "Can not to move the current cursor to the specified position(" + position + ").");
        }

        View view = convertView;
        Object viewHolder;
        // Obtain current item view type.
        this.mCurrentViewType = getItemViewType(position);
        if (view == null) {
            // Dispatch to create new view.
            view = onCreateView(position, cursor, mLayoutInflater, parent);
            // Resolve holder for the newly created view.
            final Object holder = onCreateViewHolder(position, cursor, view);
            if (holder != null) {
                view.setTag(viewHolder = holder);
            } else {
                viewHolder = view;
            }
        } else {
            final Object holder = view.getTag();
            viewHolder = holder != null ? holder : view;
        }
        // Dispatch to bind view with data.
        onBindView(position, cursor, viewHolder);
        return view;
    }

    /**
     * Inner implementation of {@link #swapCursor(android.database.Cursor)} to hide such an implementation.
     */
    @SuppressWarnings("unchecked")
    private C swapCursorInner(Cursor newCursor) {
        if (newCursor != null) {
            return (C) super.swapCursor(onWrapCursor(newCursor));
        }
        return (C) super.swapCursor(null);
    }

    /**
     * Inner implementation of {@link #onWrapCursor(android.database.Cursor)} to hide such an implementation.
     */
    @SuppressWarnings("unchecked")
    private C wrapCursorInner(Cursor cursor) {
        if (mClassOfCursorWrapper != null) {
            if (mClassOfCursorWrapper.equals(cursor.getClass())) {
                // Do not wrap same classes.
                return (C) cursor;
            }

            try {
                return mClassOfCursorWrapper.getConstructor(Cursor.class).newInstance(cursor);
            } catch (InstantiationException | IllegalAccessException | InvocationTargetException
                    | NoSuchMethodException e) {
                throw new IllegalStateException("Failed to create cursor wrapper from class("
                        + mClassOfCursorWrapper + "). "
                        + "Check if this wrapper class has public access and public constructor which takes Cursor as parameter.");
            }
        }
        return (C) cursor;
    }

    /**
     * Inner implementation of {@link #onBindView(int, Cursor, Object)} to hide such an implementation.
     */
    @SuppressWarnings("unchecked")
    boolean bindViewInner(int position, C cursor, Object viewHolder) {
        if (viewHolder instanceof CursorViewHolder) {
            ((CursorViewHolder<C, BaseCursorAdapter<C>>) viewHolder).bind(position, cursor, this);
            return true;
        }
        return false;
    }

    /**
     * Inner classes ===============================================================================
     */

    /**
     * <h3>Class Overview</h3>
     * A {@link android.view.AbsSavedState} implementation that should be used by inheritance hierarchies
     * of {@link BaseCursorAdapter} to ensure the state of all classes along the chain is saved.
     *
     * @author Martin Albedinsky
     */
    public static class BaseSavedState extends AbsSavedState {

        /**
         * Members =================================================================================
         */

        /**
         * Creator used to create an instance or array of instances of BaseSavedState from {@link android.os.Parcel}.
         */
        public static final Creator<BaseSavedState> CREATOR = new Creator<BaseSavedState>() {
            /**
             */
            @Override
            public BaseSavedState createFromParcel(Parcel source) {
                return new BaseSavedState(source);
            }

            /**
             */
            @Override
            public BaseSavedState[] newArray(int size) {
                return new BaseSavedState[size];
            }
        };

        /**
         * Constructors ============================================================================
         */

        /**
         * Creates a new instance BaseSavedState with the given <var>superState</var> to allow
         * chaining of saved states in {@link #onSaveInstanceState()} and also in
         * {@link #onRestoreInstanceState(android.os.Parcelable)}.
         *
         * @param superState A super state obtained from {@code super.onSaveInstanceState()}
         *                   within {@code onSaveInstanceState()} of a specific {@link BaseCursorAdapter}
         *                   implementation.
         */
        protected BaseSavedState(Parcelable superState) {
            super(superState);
        }

        /**
         * Called form {@link #CREATOR} to create an instance of BaseSavedState form the given parcel
         * <var>source</var>.
         *
         * @param source Parcel with data for a new instance.
         */
        protected BaseSavedState(Parcel source) {
            super(source);
        }
    }
}