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
17package com.android.tv.common;
18
19import android.content.Context;
20import android.support.annotation.Nullable;
21import android.text.TextUtils;
22import android.util.Log;
23import com.android.tv.common.feature.Feature;
24import com.android.tv.common.util.CommonUtils;
25
26/**
27 * Simple static methods to be called at the start of your own methods to verify correct arguments
28 * and state.
29 *
30 * <p>{@code checkXXX} methods throw exceptions when {@link BuildConfig#ENG} is true, and logs a
31 * warning when it is false.
32 *
33 * <p>This is based on com.android.internal.util.Preconditions.
34 */
35public final class SoftPreconditions {
36    private static final String TAG = "SoftPreconditions";
37
38    /**
39     * Throws or logs if an expression involving the parameter of the calling method is not true.
40     *
41     * @param expression a boolean expression
42     * @param tag Used to identify the source of a log message. It usually identifies the class or
43     *     activity where the log call occurs.
44     * @param errorMessageTemplate a template for the exception message should the check fail. The
45     *     message is formed by replacing each {@code %s} placeholder in the template with an
46     *     argument. These are matched by position - the first {@code %s} gets {@code
47     *     errorMessageArgs[0]}, etc. Unmatched arguments will be appended to the formatted message
48     *     in square braces. Unmatched placeholders will be left as-is.
49     * @param errorMessageArgs the arguments to be substituted into the message template. Arguments
50     *     are converted to strings using {@link String#valueOf(Object)}.
51     * @return the evaluation result of the boolean expression
52     * @throws IllegalArgumentException if {@code expression} is true
53     */
54    public static boolean checkArgument(
55            final boolean expression,
56            String tag,
57            @Nullable String errorMessageTemplate,
58            @Nullable Object... errorMessageArgs) {
59        if (!expression) {
60            String msg = format(errorMessageTemplate, errorMessageArgs);
61            warn(tag, "Illegal argument", new IllegalArgumentException(msg), msg);
62        }
63        return expression;
64    }
65
66    /**
67     * Throws or logs if an expression involving the parameter of the calling method is not true.
68     *
69     * @param expression a boolean expression
70     * @return the evaluation result of the boolean expression
71     * @throws IllegalArgumentException if {@code expression} is true
72     */
73    public static boolean checkArgument(final boolean expression) {
74        checkArgument(expression, null, null);
75        return expression;
76    }
77
78    /**
79     * Throws or logs if an and object is null.
80     *
81     * @param reference an object reference
82     * @param tag Used to identify the source of a log message. It usually identifies the class or
83     *     activity where the log call occurs.
84     * @param errorMessageTemplate a template for the exception message should the check fail. The
85     *     message is formed by replacing each {@code %s} placeholder in the template with an
86     *     argument. These are matched by position - the first {@code %s} gets {@code
87     *     errorMessageArgs[0]}, etc. Unmatched arguments will be appended to the formatted message
88     *     in square braces. Unmatched placeholders will be left as-is.
89     * @param errorMessageArgs the arguments to be substituted into the message template. Arguments
90     *     are converted to strings using {@link String#valueOf(Object)}.
91     * @return true if the object is null
92     * @throws NullPointerException if {@code reference} is null
93     */
94    public static <T> T checkNotNull(
95            final T reference,
96            String tag,
97            @Nullable String errorMessageTemplate,
98            @Nullable Object... errorMessageArgs) {
99        if (reference == null) {
100            String msg = format(errorMessageTemplate, errorMessageArgs);
101            warn(tag, "Null Pointer", new NullPointerException(msg), msg);
102        }
103        return reference;
104    }
105
106    /**
107     * Throws or logs if an and object is null.
108     *
109     * @param reference an object reference
110     * @return true if the object is null
111     * @throws NullPointerException if {@code reference} is null
112     */
113    public static <T> T checkNotNull(final T reference) {
114        return checkNotNull(reference, null, null);
115    }
116
117    /**
118     * Throws or logs if an expression involving the state of the calling instance, but not
119     * involving any parameters to the calling method is not true.
120     *
121     * @param expression a boolean expression
122     * @param tag Used to identify the source of a log message. It usually identifies the class or
123     *     activity where the log call occurs.
124     * @param errorMessageTemplate a template for the exception message should the check fail. The
125     *     message is formed by replacing each {@code %s} placeholder in the template with an
126     *     argument. These are matched by position - the first {@code %s} gets {@code
127     *     errorMessageArgs[0]}, etc. Unmatched arguments will be appended to the formatted message
128     *     in square braces. Unmatched placeholders will be left as-is.
129     * @param errorMessageArgs the arguments to be substituted into the message template. Arguments
130     *     are converted to strings using {@link String#valueOf(Object)}.
131     * @return the evaluation result of the boolean expression
132     * @throws IllegalStateException if {@code expression} is true
133     */
134    public static boolean checkState(
135            final boolean expression,
136            String tag,
137            @Nullable String errorMessageTemplate,
138            @Nullable Object... errorMessageArgs) {
139        if (!expression) {
140            String msg = format(errorMessageTemplate, errorMessageArgs);
141            warn(tag, "Illegal State", new IllegalStateException(msg), msg);
142        }
143        return expression;
144    }
145
146    /**
147     * Throws or logs if an expression involving the state of the calling instance, but not
148     * involving any parameters to the calling method is not true.
149     *
150     * @param expression a boolean expression
151     * @return the evaluation result of the boolean expression
152     * @throws IllegalStateException if {@code expression} is true
153     */
154    public static boolean checkState(final boolean expression) {
155        checkState(expression, null, null);
156        return expression;
157    }
158
159    /**
160     * Throws or logs if the Feature is not enabled
161     *
162     * @param context an android context
163     * @param feature the required feature
164     * @param tag used to identify the source of a log message. It usually identifies the class or
165     *     activity where the log call occurs
166     * @throws IllegalStateException if {@code feature} is not enabled
167     */
168    public static void checkFeatureEnabled(Context context, Feature feature, String tag) {
169        checkState(feature.isEnabled(context), tag, feature.toString());
170    }
171
172    /**
173     * Throws a {@link RuntimeException} if {@link BuildConfig#ENG} is true and not running in a
174     * test, else log a warning.
175     *
176     * @param tag Used to identify the source of a log message. It usually identifies the class or
177     *     activity where the log call occurs.
178     * @param e The exception to wrap with a RuntimeException when thrown.
179     * @param msg The message to be logged
180     */
181    public static void warn(String tag, String prefix, Exception e, String msg)
182            throws RuntimeException {
183        if (TextUtils.isEmpty(tag)) {
184            tag = TAG;
185        }
186        String logMessage;
187        if (TextUtils.isEmpty(msg)) {
188            logMessage = prefix;
189        } else if (TextUtils.isEmpty(prefix)) {
190            logMessage = msg;
191        } else {
192            logMessage = prefix + ": " + msg;
193        }
194
195        if (BuildConfig.ENG && !CommonUtils.isRunningInTest()) {
196            throw new RuntimeException(msg, e);
197        } else {
198            Log.w(tag, logMessage, e);
199        }
200    }
201
202    /**
203     * Substitutes each {@code %s} in {@code template} with an argument. These are matched by
204     * position: the first {@code %s} gets {@code args[0]}, etc. If there are more arguments than
205     * placeholders, the unmatched arguments will be appended to the end of the formatted message in
206     * square braces.
207     *
208     * @param template a string containing 0 or more {@code %s} placeholders. null is treated as
209     *     "null".
210     * @param args the arguments to be substituted into the message template. Arguments are
211     *     converted to strings using {@link String#valueOf(Object)}. Arguments can be null.
212     */
213    static String format(@Nullable String template, @Nullable Object... args) {
214        template = String.valueOf(template); // null -> "null"
215
216        args = args == null ? new Object[] {"(Object[])null"} : args;
217
218        // start substituting the arguments into the '%s' placeholders
219        StringBuilder builder = new StringBuilder(template.length() + 16 * args.length);
220        int templateStart = 0;
221        int i = 0;
222        while (i < args.length) {
223            int placeholderStart = template.indexOf("%s", templateStart);
224            if (placeholderStart == -1) {
225                break;
226            }
227            builder.append(template, templateStart, placeholderStart);
228            builder.append(args[i++]);
229            templateStart = placeholderStart + 2;
230        }
231        builder.append(template, templateStart, template.length());
232
233        // if we run out of placeholders, append the extra args in square braces
234        if (i < args.length) {
235            builder.append(" [");
236            builder.append(args[i++]);
237            while (i < args.length) {
238                builder.append(", ");
239                builder.append(args[i++]);
240            }
241            builder.append(']');
242        }
243
244        return builder.toString();
245    }
246
247    private SoftPreconditions() {}
248}
249