All Downloads are FREE. Search and download functionalities are using the official Maven repository.

it.tidalwave.mobile.android.ui.ControlFlow Maven / Gradle / Ivy

/***********************************************************************************************************************
 *
 * blueBill Mobile - open source birdwatching
 * ==========================================
 *
 * Copyright (C) 2009, 2010 by Tidalwave s.a.s. (http://www.tidalwave.it)
 * http://bluebill.tidalwave.it/mobile/
 *
 ***********************************************************************************************************************
 *
 * 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.
 *
 ***********************************************************************************************************************
 *
 * $Id: ControlFlow.java,v 67dcb737b74e 2010/06/09 01:20:14 fabrizio $
 *
 **********************************************************************************************************************/
package it.tidalwave.mobile.android.ui;

import javax.annotation.Nonnull;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.openide.util.Lookup;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import it.tidalwave.util.Parameters;
import it.tidalwave.util.logging.Logger;
import java.util.ArrayList;
import java.util.List;

/***********************************************************************************************************************
 *
 * @author  Fabrizio Giudici
 * @version $Id: $
 *
 **********************************************************************************************************************/
public abstract class ControlFlow
  {
    private static final String CLASS = ControlFlow.class.getName();
    private static final Logger logger = Logger.getLogger(CLASS);

    /** Flags nextStep() to use the standard Intent mechanism for finding the next Activity, instead of relying of
     *  the registered sequences. It's still important to use nextStep() instead of Activity.startActivity() in
     *  order to propagate the ControlFlow class name and requestCode. */
    public final static Object USE_INTENT_FILTER = "USE_INTENT_FILTER";

    private static int nextRequestCode = 1000;

    /* package */ final static String CONTROL_FLOW_CLASS_NAME = ControlFlow.class.getName() + ".controlFlowClassName";

    private final static Map> REQUEST_CODE_TO_CONTROL_FLOW_MAP = new HashMap>();
    private final static Map, Integer> CONTROL_FLOW_MAP_TO_REQUEST_CODE = new HashMap, Integer>();

    private final Map, List> transitionMap =
            new HashMap, List>();

    private Class initialActivity;

    protected Activity activity;

    private final int requestCode;

    /*******************************************************************************************************************
     *
     *
     ******************************************************************************************************************/
    public static interface Condition
      {
        public boolean compute (@Nonnull Object ... args);
      }

    /*******************************************************************************************************************
     *
     *
     ******************************************************************************************************************/
    private static class ConditionAndActivityPair
      {
        @Nonnull
        protected final Condition condition;

        @Nonnull
        protected final Class activityClass;

        public ConditionAndActivityPair (final @Nonnull Condition condition,
                                         final @Nonnull Class activityClass)
          {
            this.condition = condition;
            this.activityClass = activityClass;
          }
      }

    /*******************************************************************************************************************
     *
     *
     ******************************************************************************************************************/
    public class FlowBuilder
      {
        private final Class from;
        private Condition condition;

        protected FlowBuilder (final @Nonnull Class from)
          {
            this.from = from;
          }

        @Nonnull
        public FlowBuilder completes()
          {
            return completesWith(TRUE);
          }

        @Nonnull
        public synchronized FlowBuilder completesWith (final @Nonnull Condition condition)
          {
            this.condition = condition;
            return this;
          }

        @Nonnull
        public FlowBuilder completesWithout (final @Nonnull Condition condition)
          {
            return completesWith(not(condition));
          }

        public void thenForwardTo (final @Nonnull Class to)
          {
            List pairs = transitionMap.get(from);

            if (pairs == null)
              {
                pairs = new ArrayList();
                transitionMap.put(from, pairs);
              }

            pairs.add(new ConditionAndActivityPair(condition, to));
          }

        private final Condition TRUE = new Condition()
          {
            public boolean compute (final @Nonnull Object... args)
              {
                return true;
              }
         };

        @Nonnull
        private Condition not (final @Nonnull Condition condition)
          {
            return new Condition()
              {
                public boolean compute (final @Nonnull Object... args)
                  {
                    return !condition.compute(args);
                  }
              };
          }
      }

    /*******************************************************************************************************************
     *
     *
     ******************************************************************************************************************/
    // TODO: I'd like to have the requestCode auto-initialized, but I'm not sure it always works. What about the
    // activity gets unloaded, thus we lose the CONTROL_FLOW_MAP_TO_REQUEST_CODE? When the ControlFlow is used again
    // it would recreate a requestCode, but we don't have a guarantee that ControlFlow subclasses are reinstantiated
    // in the same order, so they'd get different requestCodes. Must understand better when classes are unloaded to
    // understand whether it can happen or not.

    protected ControlFlow()
      {
        synchronized (CONTROL_FLOW_MAP_TO_REQUEST_CODE)
          {
            final Class clazz = getClass();

            if (CONTROL_FLOW_MAP_TO_REQUEST_CODE.containsKey(clazz))
              {
                requestCode = CONTROL_FLOW_MAP_TO_REQUEST_CODE.get(clazz);
              }
            else
              {
                requestCode = nextRequestCode++;
                REQUEST_CODE_TO_CONTROL_FLOW_MAP.put(requestCode, clazz);
                CONTROL_FLOW_MAP_TO_REQUEST_CODE.put(clazz, requestCode);
                logger.info("%s registered to requestCode: %d", clazz, requestCode);
              }
          }
      }

    /*******************************************************************************************************************
     *
     *
     ******************************************************************************************************************/
    protected void startFrom (final @Nonnull Class initialActivity)
      {
        this.initialActivity = initialActivity;
      }

    /*******************************************************************************************************************
     *
     *
     ******************************************************************************************************************/
    @Nonnull
    protected FlowBuilder when (final @Nonnull Class initialActivity)
      {
        return new FlowBuilder(initialActivity);
      }

    /*******************************************************************************************************************
     *
     *
     ******************************************************************************************************************/
    public int getRequestCode()
      {
        return requestCode;
      }

    /*******************************************************************************************************************
     *
     *
     ******************************************************************************************************************/
    @Nonnull
    public Class getInitialActivity()
      {
        return initialActivity;
      }

    /*******************************************************************************************************************
     *
     * Starts a new {@link ControlFlow}.
     *
     * @param  controlFlowClass  the {@link ControlFlow} to start
     *
     ******************************************************************************************************************/
    public final void start (final @Nonnull Class controlFlowClass)
      {
        try
          {
            logger.info("startActivity(%s) - %s", controlFlowClass, activity);
            final ControlFlow controlFlow = controlFlowClass.newInstance();
            final Class initialActivity = controlFlow.getInitialActivity();
            final int requestCode = controlFlow.getRequestCode();
            logger.info(">>>> initialActivity: %s, requestCode: %d", initialActivity, requestCode);
            final Intent intent = new Intent(getContext(), initialActivity);
            intent.putExtra(CONTROL_FLOW_CLASS_NAME, controlFlowClass.getName());
            activity.startActivityForResult(intent, requestCode);
          }
        catch (Exception e)
          {
            throw new RuntimeException(e);
          }
      }

    /*******************************************************************************************************************
     *
     *
     ******************************************************************************************************************/
    @Nonnull
    public static ControlFlow forActivity (final @Nonnull Activity activity)
      {
        return new ControlFlowLazyProxy(activity);
      }

    /*******************************************************************************************************************
     *
     *
     ******************************************************************************************************************/
    @Nonnull
    /* package */ static ControlFlow forActivity2 (final @Nonnull Activity activity)
      {
        Parameters.checkNonNull(activity, "activity");

        try
          {
            final String className = activity.getIntent().getStringExtra(CONTROL_FLOW_CLASS_NAME);
            assert className != null : "Sorry, I've lost the CONTROL_FLOW_CLASS_NAME!";
            final ControlFlow controlFlow = (ControlFlow)Class.forName(className).newInstance();
            controlFlow.activity = activity;
            return controlFlow;
          }
        catch (Exception e)
          {
            throw new RuntimeException(e);
          }
      }

    /*******************************************************************************************************************
     *
     *
     ******************************************************************************************************************/
    public static int findRequestCode (final @Nonnull Class controlFlowClass)
      {
        if (!CONTROL_FLOW_MAP_TO_REQUEST_CODE.containsKey(controlFlowClass))
          {
            try // try to register it
              {
                controlFlowClass.newInstance();
              }
            catch (InstantiationException e)
              {
                throw new RuntimeException(e);
              }
            catch (IllegalAccessException e)
              {
                throw new RuntimeException(e);
              }
          }

        if (!CONTROL_FLOW_MAP_TO_REQUEST_CODE.containsKey(controlFlowClass))
          {
            throw new RuntimeException("ControlFlow not registered: " + controlFlowClass);
          }

        return CONTROL_FLOW_MAP_TO_REQUEST_CODE.get(controlFlowClass);
      }

    /*******************************************************************************************************************
     *
     *
     ******************************************************************************************************************/
    public void toNextStep (final @Nonnull Object ... args)
      {
        toNextStep(new Intent(), args);
      }

    /*******************************************************************************************************************
     *
     *
     ******************************************************************************************************************/
    public void toNextStep (final @Nonnull Intent intent, final @Nonnull Object ... args)
      {
        logger.info("toNextStep(%s, %s)", intent, Arrays.toString(args));

        if (activity == null)
          {
            throw new IllegalStateException("ControlFlow must have been retrevied by ControlFlow.for_()");
          }

        if (Arrays.asList(args).contains(USE_INTENT_FILTER))
          {
            logger.info(">>>> using the intent for starting the next Activity...");
            final Intent intentDecorator = new Intent(intent);
            intentDecorator.putExtra(CONTROL_FLOW_CLASS_NAME, getClass().getName());
            activity.startActivityForResult(intentDecorator, requestCode);
            return;
          }

        Class nextActivityClass = null;
        final ComponentName componentName = intent.getComponent();

        if (componentName == null)
          {
            final List pairs = transitionMap.get(activity.getClass());

            if (pairs != null)
              {
                for (final ConditionAndActivityPair pair : pairs)
                  {
                    if (pair.condition.compute(args))
                      {
                        nextActivityClass = pair.activityClass;
                        break;
                      }
                  }
              }
          }
        else
          {
            final String explicitNextActivityClassName = componentName.getClassName();
            logger.info(">>>> explicit next activity: %s", explicitNextActivityClassName);

            try
              {
                nextActivityClass = (Class)Class.forName(explicitNextActivityClassName);
              }
            catch (ClassNotFoundException e2)
              {
                throw new RuntimeException(e2);
              }
          }

        if (nextActivityClass == null)
          {
            logger.info(">>>> it was the final step, calling finish()...");
            activity.setResult(Activity.RESULT_OK);
            activity.finish();
          }
        else
          {
            logger.info(">>>> next Activity is: %s", nextActivityClass);
            final Intent intentDecorator = new Intent(intent);
            intentDecorator.setClass(getContext(), nextActivityClass);
            intentDecorator.putExtra(CONTROL_FLOW_CLASS_NAME, getClass().getName());
            activity.startActivityForResult(intentDecorator, requestCode);
          }
      }

    /*******************************************************************************************************************
     *
     *
     ******************************************************************************************************************/
    @Nonnull
    private static Context getContext()
      {
        return Lookup.getDefault().lookup(Context.class);
      }
  }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy