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  */
16 package com.android.messaging.util;
17 
18 import java.util.ArrayList;
19 import 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  */
49 public class FallbackStrategies<Input, Output> {
50     public interface Strategy<Input, Output> {
execute(Input params)51         Output execute(Input params) throws Exception;
52     }
53 
54     private final List<Strategy<Input, Output>> mChainedStrategies;
55 
FallbackStrategies(final Strategy<Input, Output> primaryStrategy)56     private FallbackStrategies(final Strategy<Input, Output> primaryStrategy) {
57         mChainedStrategies = new ArrayList<Strategy<Input, Output>>();
58         mChainedStrategies.add(primaryStrategy);
59     }
60 
startWith( final Strategy<Input, Output> primaryStrategy)61     public static <Input, Output> FallbackStrategies<Input, Output> startWith(
62             final Strategy<Input, Output> primaryStrategy) {
63         return new FallbackStrategies<Input, Output>(primaryStrategy);
64     }
65 
thenTry(final Strategy<Input, Output> strategy)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 
execute(final Input params)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