public class Actions {
    private Actions() {
    }
    private static ConcurrentHashMap<Class<?>, Object> classCache = new ConcurrentHashMap<>();

    /**
     * Create instance of interface with action creator functions.
     * <p>
     * When reductor processor is available, will instantiate generated implementation.
     * If generated class isn't available, fallback to dynamic proxy implementation.
     * <p>
     * Created instances are cached, so it's cheap to call this method many times for the same interface.
     *
     * @param actionCreator interface class
     * @param <T>           type of provided interface
     * @return instance of type T
     */
    public static <T> T from(Class<T> actionCreator) {
        Object creator = classCache.get(actionCreator);
        if (creator == null) {
            creator = createCreator(actionCreator);
            classCache.put(actionCreator, creator);
        }
        return (T) creator;
    }

    private static Object createCreator(Class<?> actionCreator) {
        String className = actionCreator.getName();
        String generatedActionCreator = className + "_AutoImpl";

        try {
            Class<?> generatedClass = Class.forName(generatedActionCreator);
            //Ok, if class exists then it was generated by our annotation processor
            //so no need to validate
            return generatedClass.newInstance();
        } catch (InstantiationException e) {
            //should not happen with auto generated classes
            throw new IllegalStateException(e);
        } catch (IllegalAccessException e) {
            //should not happen with auto generated classes
            throw new IllegalStateException(e);
        } catch (ClassNotFoundException e) {
            //if there are not generated action creators just fallback to dynamic proxy
            return createDynamicProxy(actionCreator);
        }
    }

    private static Object createDynamicProxy(Class<?> actionCreator) {
        if (actionCreator.getAnnotation(ActionCreator.class) == null) {
            throw new IllegalStateException(String.format(
                    "%s should be interface annotated with @%s", actionCreator, ActionCreator.class.getSimpleName()));
        }

        Method[] methods = actionCreator.getMethods();
        final HashMap<Method, String> actionsMap = new HashMap<>(methods.length);
        for (Method method : methods) {
            ActionCreator.Action annotation = method.getAnnotation(ActionCreator.Action.class);
            if (annotation == null) {
                throw new IllegalStateException(
                        String.format("Method %s should be annotated with @%s",
                                method, ActionCreator.Action.class.getCanonicalName()));
            }
            actionsMap.put(method, annotation.value());
        }
        return Proxy.newProxyInstance(actionCreator.getClassLoader(), new Class<?>[]{actionCreator},
                (instance, method, args) -> {
                    String action = actionsMap.get(method);
                    if (args == null) {
                        args = new Object[0];
                    }
                    return new Action(action, args);
                });
    }
} public interface Cancelable {
    void cancel();
} public class Cursors {
    private Cursors(){}

    /**
     * Creates a Cursor by applying a specified function to state.
     *
     * @param cursor source Cursor to be mapped
     * @param mapper the function that will be applied to a state. Must be free of side effects.
     * @param <State> source state type
     * @param <R>    the output state type
     * @return Cursor that holds state of type T produced by mapper function
     */
    public static <State, R> Cursor<R> map(Cursor<State> cursor, Function<State, R> mapper) {
        return new MappedCursor<>(cursor, mapper);
    }

    /**
     * Notify listener for every state in Store.
     * <p>
     * Note: equivalent to {@link Cursor#subscribe(StateChangeListener)} but current state will be propagated too
     *
     * @param cursor source Cursor
     * @param listener callback which will be notified
     * @param <State> source state type
     * @return instance of {@link Cancelable} to be used to cancel subscription (remove listener)
     */
    public static <State> Cancelable forEach(Cursor<State> cursor, StateChangeListener<State> listener) {
        Cancelable cancelable = cursor.subscribe(listener);
        listener.onStateChanged(cursor.getState());
        return cancelable;
    }
} public class Store<State> implements Dispatcher, Cursor<State> {
    public static final String INIT_ACTION = "@@reductor/INIT";

    private final Reducer<State> reducer;
    private Dispatcher dispatcher;
    private final List<StateChangeListener<State>> listeners = new CopyOnWriteArrayList<>();
    private volatile State state;

    private Store(Reducer<State> reducer, State initialState, Middleware<State>[] middlewares) {
        this.reducer = reducer;
        this.state = initialState;

        dispatcher = this::dispatchAction;
        dispatchAction(Action.create(INIT_ACTION));

        for (int i = middlewares.length - 1; i >= 0; i--) {
            Middleware<State> middleware = middlewares[i];
            dispatcher = middleware.create(Store.this, dispatcher);
        }
    }

    private void dispatchAction(final Object actionObject) {
        if (actionObject instanceof Action) {
            final Action action = (Action) actionObject;
            synchronized (this) {
                state = reducer.reduce(state, action);
            }
            for (StateChangeListener<State> listener : listeners) {
                listener.onStateChanged(state);
            }
        } else {
            throw new IllegalArgumentException(String.format("action %s of %s is not instance of %s, use custom Middleware to dispatch another types of actions", actionObject, actionObject.getClass(), Action.class));
        }
    }

    /**
     * Create store with given {@link Reducer} and optional array of {@link Middleware}
     *
     * @param reducer     Reducer of type S which will be used to dispatch actions
     * @param middlewares array of middlewares to be used to dispatch actions in the same order as provided
     *                    look {@link Middleware} for more information
     * @param <S>         type of state to hold and maintain
     * @return Store initialised with initialState
     */
    @SafeVarargs
    public static <S> Store<S> create(Reducer<S> reducer, Middleware<S>... middlewares) {
        return create(reducer, null, middlewares);
    }

    /**
     * Create store with given {@link Reducer}, initalState and optional array of {@link Middleware}
     *
     * @param reducer      Reducer of type S which will be used to dispatch actions
     * @param initialState state to be initial state of create store
     * @param middlewares  array of middlewares to be used to dispatch actions in the same order as provided
     *                     look {@link Middleware} for more information
     * @param <S>          type of state to hold and maintain
     * @return Store initialised with initialState
     */
    @SafeVarargs
    public static <S> Store<S> create(Reducer<S> reducer, S initialState, Middleware<S>... middlewares) {
        return new Store<>(reducer, initialState, middlewares);
    }

    /**
     * Dispatch action through {@link Reducer} and store the next state
     *
     * @param action action to be dispatched, usually instance of {@link Action}
     *               but custom {@link Middleware} can be used to support other types of actions
     */
    public void dispatch(final Object action) {
        dispatcher.dispatch(action);
    }

    /**
     * {@inheritDoc}
     */
    public State getState() {
        return state;
    }

    /**
     * {@inheritDoc}
     */
    public Cancelable subscribe(final StateChangeListener<State> listener) {
        listeners.add(listener);
        return () -> listeners.remove(listener);
    }
} @CombinedState
@AutoValue
public abstract class AppState {
    public abstract List<Note> notes();

    public abstract NotesFilter filter();

    public static TypeAdapter<AppState> typeAdapter(Gson gson) {
        return new AutoValue_AppState.GsonTypeAdapter(gson);
    }

    public List<Note> getFilteredNotes() {
        List<Note> notes = this.notes();
        NotesFilter filter = this.filter();
        return Utils.filter(ConsPStack.from(notes), note ->
                filter == NotesFilter.ALL
                        || filter == NotesFilter.CHECKED && note.checked
                        || filter == NotesFilter.UNCHECKED && !note.checked);
    }
} public class Note {
    public final int id;
    public final String note;
    public final boolean checked;

    public Note(int id, String note, boolean checked) {
        this.id = id;
        this.note = note;
        this.checked = checked;
    }

    @Override
    public String toString() {
        return String.format("[%s] %s", checked ? "+" : " ", note);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Note note1 = (Note) o;

        if (id != note1.id) return false;
        if (checked != note1.checked) return false;
        return note != null ? note.equals(note1.note) : note1.note == null;

    }

    @Override
    public int hashCode() {
        int result = id;
        result = 31 * result + (note != null ? note.hashCode() : 0);
        result = 31 * result + (checked ? 1 : 0);
        return result;
    }
} public enum NotesFilter {
    ALL,
    CHECKED,
    UNCHECKED
} @ActionCreator
public interface FilterActions {
    String SET_FILTER = "SET_FILTER";

    @ActionCreator.Action(SET_FILTER)
    Action setFilter(NotesFilter filter);
} @ActionCreator
public interface NotesActions {
    String ADD_ACTION = "ADD_ITEM";
    String TOGGLE = "TOGGLE";
    String REMOVE_ITEM = "REMOVE_ITEM";

    @ActionCreator.Action(NotesActions.ADD_ACTION)
    Action add(int id, String content);

    @ActionCreator.Action(NotesActions.REMOVE_ITEM)
    Action remove(int id);

    @ActionCreator.Action(NotesActions.TOGGLE)
    Action toggle(int id);
} public class UndoableReducer<State> implements Reducer<State> {
    private final Reducer<State> sourceReducer;

    public static Action pop() {
        return Action.create("POP");
    }

    private LinkedList<State> stack = new LinkedList<>();

    public UndoableReducer(Reducer<State> sourceReducer) {
        this.sourceReducer = sourceReducer;
    }

    @Override
    public State reduce(State state, Action action) {
        if (action.type.equals("POP")) {
            return stack.isEmpty()
                    ? state
                    : stack.pop();
        } else if (!action.type.equals(Store.INIT_ACTION)) {
            stack.push(state);
        }
        return sourceReducer.reduce(state, action);
    }
}
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.*;
import com.yheriatovych.reductor.Actions;
import com.yheriatovych.reductor.Cancelable;
import com.yheriatovych.reductor.Cursors;
import com.yheriatovych.reductor.Store;
import com.yheriatovych.reductor.example.model.AppState;
import com.yheriatovych.reductor.example.model.Note;
import com.yheriatovych.reductor.example.model.NotesFilter;
import com.yheriatovych.reductor.example.reductor.filter.FilterActions;
import com.yheriatovych.reductor.example.reductor.notelist.NotesActions;
import com.yheriatovych.reductor.example.reductor.utils.UndoableReducer;
import java.util.concurrent.atomic.AtomicInteger;
package com.yheriatovych.reductor.example;



public class MainActivity extends AppCompatActivity {

    private Store<AppState> store;
    private AtomicInteger idGenerator = new AtomicInteger();
    private Cancelable mCancelable;
    private NotesActions notesActions;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        notesActions = Actions.from(NotesActions.class);
        store = ((ReductorApp) getApplicationContext()).store;
        setContentView(R.layout.activity_main);

        Spinner spinner = (Spinner) findViewById(R.id.spinner);
        setupSpinner(spinner, store);

        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));

        final TodoAdapter adapter = new TodoAdapter(store.getState().notes(),
                note -> store.dispatch(notesActions.toggle(note.id)));

        new ItemTouchHelper(new NoteTouchCallback(position -> {
            Note note = store.getState().notes().get(position);
            store.dispatch(notesActions.remove(note.id));
        })).attachToRecyclerView(recyclerView);


        adapter.setHasStableIds(true);
        recyclerView.setAdapter(adapter);

        mCancelable = Cursors.forEach(store, state -> {
            adapter.setNotes(state.getFilteredNotes());
            spinner.setSelection(state.filter().ordinal());
        });

        final EditText editText = (EditText) findViewById(R.id.note_edit_text);
        findViewById(R.id.add).setOnClickListener(view -> {
            String note = editText.getText().toString();
            int id = idGenerator.getAndIncrement();
            store.dispatch(notesActions.add(id, note));
            editText.setText(null);
        });
    }

    private void setupSpinner(Spinner spinner, Store<AppState> store) {
        SpinnerAdapter adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_dropdown_item, NotesFilter.values());
        spinner.setAdapter(adapter);
        spinner.setSelection(0);
        spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> adapterView, View view, int position, long l) {
                NotesFilter filter = NotesFilter.values()[position];
                store.dispatch(Actions.from(FilterActions.class).setFilter(filter));
            }

            @Override
            public void onNothingSelected(AdapterView<?> adapterView) {
            }
        });
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
        menu.add(R.string.undo)
                .setIcon(R.drawable.ic_undo_24dp)
                .setOnMenuItemClickListener(menuItem -> {
                    store.dispatch(UndoableReducer.pop());