1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.android.messaging.util;
17
18import java.util.ArrayList;
19import java.util.List;
20
21/**
22 * Provides a generic and loose-coupled framework to execute one primary and multiple fallback
23 * strategies for solving a given task.<p>
24 * Basically, what would have been a nasty try-catch that's hard to separate and maintain:
25 * <pre><code>
26 * try {
27 *     // doSomething() that may fail.
28 * } catch (Exception ex) {
29 *     try {
30 *         // fallback1() that may fail.
31 *     } catch (Exception ex2) {
32 *         try {
33 *             // fallback2() that may fail.
34 *         } catch (Exception ex3) {
35 *             // ...
36 *         }
37 *     }
38 * }
39 * </code></pre>
40 * Now becomes:<br>
41 * <pre><code>
42 * FallbackStrategies
43 *      .startWith(something)
44 *      .thenTry(fallback1)
45 *      .thenTry(fallback2)
46 *      .execute();
47 * </code></pre>
48 */
49public class FallbackStrategies<Input, Output> {
50    public interface Strategy<Input, Output> {
51        Output execute(Input params) throws Exception;
52    }
53
54    private final List<Strategy<Input, Output>> mChainedStrategies;
55
56    private FallbackStrategies(final Strategy<Input, Output> primaryStrategy) {
57        mChainedStrategies = new ArrayList<Strategy<Input, Output>>();
58        mChainedStrategies.add(primaryStrategy);
59    }
60
61    public static <Input, Output> FallbackStrategies<Input, Output> startWith(
62            final Strategy<Input, Output> primaryStrategy) {
63        return new FallbackStrategies<Input, Output>(primaryStrategy);
64    }
65
66    public FallbackStrategies<Input, Output> thenTry(final Strategy<Input, Output> strategy) {
67        Assert.isFalse(mChainedStrategies.isEmpty());
68        mChainedStrategies.add(strategy);
69        return this;
70    }
71
72    public Output execute(final Input params) {
73        final int count = mChainedStrategies.size();
74        for (int i = 0; i < count; i++) {
75            final Strategy<Input, Output> strategy = mChainedStrategies.get(i);
76            try {
77                // If succeeds, this will directly return.
78                return strategy.execute(params);
79            } catch (Exception ex) {
80                LogUtil.e(LogUtil.BUGLE_TAG, "Exceptions occured when executing strategy " +
81                        strategy + (i < count - 1 ?
82                                ", attempting fallback " + mChainedStrategies.get(i + 1) :
83                                ", and running out of fallbacks."), ex);
84                // This will fall through and continue with the next strategy (if any).
85            }
86        }
87        // Running out of strategies, return null.
88        // TODO: Should this accept user-defined fallback value other than null?
89        return null;
90    }
91}
92