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 
17 package com.android.tv.common;
18 
19 import android.content.Context;
20 import android.support.annotation.Nullable;
21 import android.text.TextUtils;
22 import android.util.Log;
23 import com.android.tv.common.feature.Feature;
24 import 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  */
35 public 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      */
checkArgument( final boolean expression, String tag, @Nullable String errorMessageTemplate, @Nullable Object... errorMessageArgs)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      */
checkArgument(final boolean expression)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      */
checkNotNull( final T reference, String tag, @Nullable String errorMessageTemplate, @Nullable Object... errorMessageArgs)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      */
checkNotNull(final T reference)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      */
checkState( final boolean expression, String tag, @Nullable String errorMessageTemplate, @Nullable Object... errorMessageArgs)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      */
checkState(final boolean expression)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      */
checkFeatureEnabled(Context context, Feature feature, String tag)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      */
warn(String tag, String prefix, Exception e, String msg)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      */
format(@ullable String template, @Nullable Object... args)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 
SoftPreconditions()247     private SoftPreconditions() {}
248 }
249