Java tutorial
/* * Copyright [2015] [Alexander Dridiger - drisoftie@gmail.com] * * 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.drisoftie.action.async; import android.os.Handler; import android.os.Looper; import android.util.Log; import android.view.View; import com.drisoftie.action.BuildConfig; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Triple; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.List; /** * Dynamic {@link java.lang.reflect.Proxy} {@link java.lang.reflect.InvocationHandler} that intercepts interface callbacks of <b>any</b> * derived {@link android.view.View} (generic {@link ViewT} argument) and wraps them inside a generic three-step process. * <ol> * <li>The first step gives the possibility to handle a callback inside the same {@link Thread} it was invoked in (in most cases the UI * {@link Thread}, when dealing with UI). This step is also the one returning the return value of the action callback (if it has any, or * else it simply returns {@code null}). * <li>The second step runs inside a new {@link Thread} and is supposed to be the one doing the heavy work, so that the invoking action * thread (in Android referred to as the "UI {@link Thread}") is not occupied with this work. This step can also return a value. * <li>The third step runs again in the UI Thread and is useful to clean up stuff after the heavy work is done or it can show the user * an indicator that the work is finished. * </ol> * <p> * Additionally it provides ways to apply various tag information to facilitate data transfer between those steps and to help identifying * which action is invoked, if the implemented interface provided multiple action callbacks. * </p> * Use this class in the following way: * <pre> * {@code * // this action applies to a View and its OnClickListener. It has no tags, no result and no method filters. * AsyncAction<View, Void, Void, Void> action = new AsyncAction<View, Void, Void, Void>(myView, OnClickListener.class, * // method to register the callback * "setOnClickListener") { * * public Object onActionPrepare(String methodName, Object[] methodArgs, Void tag1, Void tag2, Object[] additionalTags) { * // preparations, progress bar and return a click value (due to expected void, it's null) * return null; * } * * public Void onActionDoWork(String methodName, Object[] methodArgs, Void tag1, Void tag2, Object[] additionalTags) { * // actual work like internet access, loading stuff from database, heavy computing, no value to return * return null; * } * * public void onActionAfterWork(String methodName, Object[] methodArgs, Void workResult, Void tag1, Void tag2, * Object[] additionalTags) { * // cleanup, hide progress bar, show results... * } * }; * } * </pre> * For providing optional information, use it like this: * <pre> * {@code * // this action applies to a Button and its OnClickListener. It has method filters and tags. * AsyncAction<Button, Point, Animations, Void> action = new AsyncAction<Button, Point, Animations, Void>(myButton, OnClickListener.class, * // registration method * "setOnClickListener", * // allow only onClick methods to pass, filter * new String[] { "onClick" }, * myAnimation, null, additionalTag1, additionalTag2, identifier) { * * public Object onActionPrepare(String methodName, Object[] methodArgs, Animation myAnimation, Void tag2, Object[] additionalTags) { * // use tags to start a fancy animation at a point * // tags usable for this work, tag array can be updated for future work (passed to every other step) * return true; * } * * public Point onActionDoWork(String methodName, Object[] methodArgs, Animation myAnimation, Void tag2, Object[] additionalTags) { * // actual work like internet access, loading stuff from database, heavy computing... * // tags usable for this work, tag array can be updated for future work * return currentPoint; * } * * public void onActionAfterWork(String methodName, Object[] methodArgs, Point currentPoint, Animation myAnimation, Void tag2, * Object[] additionalTags) { * // cleanup and show user results * } * }; * } * </pre> * When implementing multiple Java Interfaces, use it like this: * <pre> * {@code * // this action applies to a Button and its OnClickListener, additionally it implements another generic Interface. It has method filters * // and tags. * AsyncAction<Button, Point, Animation, Void> action = new AsyncAction<Button, Point, Animation, Void>(myButton, * new Class[] { OnClickListener.class, IGenericInterface.class}, * // registration method * "setOnClickListener", * // allow only onClick and invokeAction methods to pass, filter * new String[] { "onClick", "invokeAction" }, * myAnimation, null, additionalTag1, additionalTag2, identifier) { * * public Object onActionPrepare(String methodName, Object[] methodArgs, Animation tag1, Void tag2, Object[] additionalTags) { * // find out what method was invoked * if (ActionMethod.ON_CLICK.matches.equals(methodName)) { * // OnClickListener was invoked * return null; * } else { * // the other interface was invoked * return result; * } * } * * public Point onActionDoWork(String methodName, Object[] methodArgs, Point tag1, Animation tag2, Object[] additionalTags) { * // actual work like internet access, loading stuff from database, heavy computing... * // tags usable for this work, tag array can be updated for future work * return currentPoint; * } * * public void onActionAfterWork(String methodName, Object[] methodArgs, Point tag1, Animation tag2, Object[] additionalTags) { * // cleanup and user indicator... * // tags usable for that... * } * }; * } * </pre> * * @author Alexander Dridiger */ public abstract class AsyncAction<ViewT extends View, ResultT, ProgressT, Tag1T, Tag2T> extends BaseAsyncAction<ViewT, ResultT, ProgressT, Tag1T, Tag2T> { /** * @see com.drisoftie.action.async.BaseAsyncAction#BaseAsyncAction(Object, Object, Object...) */ public AsyncAction(Tag1T tag1, Tag2T tag2, Object... additionalTags) { super(tag1, tag2, additionalTags); } /** * @see com.drisoftie.action.async.BaseAsyncAction#BaseAsyncAction(Object, Class, String) */ public AsyncAction(ViewT view, Class<?> actionType, String regMethodName) { super(view, actionType, regMethodName); } /** * @see com.drisoftie.action.async.BaseAsyncAction#BaseAsyncAction(Object[], Class, String) */ public AsyncAction(ViewT[] views, Class<?> actionType, String regMethodName) { super(views, actionType, regMethodName); } /** * @see com.drisoftie.action.async.BaseAsyncAction#BaseAsyncAction(Object, Class[], String) */ public AsyncAction(ViewT view, Class<?>[] actionTypes, String regMethodName) { super(view, actionTypes, regMethodName); } /** * @see com.drisoftie.action.async.BaseAsyncAction#BaseAsyncAction(Object[], Class[], String) */ public AsyncAction(ViewT[] views, Class<?>[] actionTypes, String regMethodName) { super(views, actionTypes, regMethodName); } /** * @see com.drisoftie.action.async.BaseAsyncAction#BaseAsyncAction(Object, Class, String, String[]) */ public AsyncAction(ViewT view, Class<?> actionTypes, String regMethodName, String[] actionMethodNames) { super(view, actionTypes, regMethodName, actionMethodNames); } /** * @see com.drisoftie.action.async.BaseAsyncAction#BaseAsyncAction(Object[], Class, String, String[]) */ public AsyncAction(ViewT[] views, Class<?> actionTypes, String regMethodName, String[] actionMethodNames) { super(views, actionTypes, regMethodName, actionMethodNames); } /** * @see com.drisoftie.action.async.BaseAsyncAction#BaseAsyncAction(Object, Class[], String, String[]) */ public AsyncAction(ViewT view, Class<?>[] actionTypes, String regMethodName, String[] actionMethodNames) { super(view, actionTypes, regMethodName, actionMethodNames); } /** * @see com.drisoftie.action.async.BaseAsyncAction#BaseAsyncAction(Object[], Class[], String, String[]) */ public AsyncAction(ViewT[] views, Class<?>[] actionTypes, String regMethodName, String[] actionMethodNames) { super(views, actionTypes, regMethodName, actionMethodNames); } /** * @see com.drisoftie.action.async.BaseAsyncAction#BaseAsyncAction(Object, Class, String, String[], Object, Object, * Object...) */ public AsyncAction(ViewT view, Class<?> actionType, String regMethodName, String[] actionMethodNames, Tag1T tag1, Tag2T tag2, Object... additionalTags) { super(view, actionType, regMethodName, actionMethodNames, tag1, tag2, additionalTags); } /** * @see com.drisoftie.action.async.BaseAsyncAction#BaseAsyncAction(Object[], Class, String, String[], Object, Object, * Object...) */ public AsyncAction(ViewT[] views, Class<?> actionType, String regMethodName, String[] actionMethodNames, Tag1T tag1, Tag2T tag2, Object... additionalTags) { super(views, actionType, regMethodName, actionMethodNames, tag1, tag2, additionalTags); } /** * @see com.drisoftie.action.async.BaseAsyncAction#BaseAsyncAction(Object, Class[], String, String[], Object, Object, * Object...) */ public AsyncAction(ViewT view, Class<?>[] actionTypes, String regMethodName, String[] actionMethodNames, Tag1T tag1, Tag2T tag2, Object... additionalTags) { super(view, actionTypes, regMethodName, actionMethodNames, tag1, tag2, additionalTags); } /** * @see com.drisoftie.action.async.BaseAsyncAction#BaseAsyncAction(Object[], Class[], String, String[], Object, Object, * Object...) */ public AsyncAction(ViewT[] views, Class<?>[] actionTypes, String regMethodName, String[] actionMethodNames, Tag1T tag1, Tag2T tag2, Object... additionalTags) { super(views, actionTypes, regMethodName, actionMethodNames, tag1, tag2, additionalTags); } @Override public void registerAction(List<ActionBinding<ViewT>> bindings) { ClassLoader cl = this.getClass().getClassLoader(); for (ActionBinding<ViewT> binding : bindings) { Class<?>[] actions = new Class<?>[binding.registrations.size()]; for (int i = 0; i < actions.length; i++) { actions[i] = binding.registrations.get(i).getLeft(); } binding.actionHandler = Proxy.newProxyInstance(cl, actions, this); } for (ActionBinding<ViewT> actionBinding : bindings) { if (actionBinding.view != null) { for (Triple<Class<?>, String, String[]> reg : actionBinding.registrations) { if (StringUtils.isNotEmpty(reg.getMiddle())) { try { Method regMethod = actionBinding.view.getClass().getMethod(reg.getMiddle(), reg.getLeft()); if (regMethod != null) { regMethod.invoke(actionBinding.view, actionBinding.actionHandler); } } catch (NoSuchMethodException | IllegalArgumentException | IllegalAccessException | InvocationTargetException e) { // do nothing if binding fails if (BuildConfig.DEBUG) { Log.v(getClass().getSimpleName(), e.getClass().getName() + " for callback" + reg.getLeft().getName()); } } } } } } this.bindings = bindings; } @Override public void invokeRunnableOnViewImpl(ViewT view, Runnable runnable) { view.post(runnable); } @Override public void invokeRunnableOnUiThread(Runnable runnable) { new Handler(Looper.getMainLooper()).post(runnable); } }