package com.devtf_l.app.util;
import android.util.Log;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/** Logging for lazy people. */
public final class Timber {
/** Log a verbose message with optional format args. */
public static void v(String message, Object... args) {
TREE_OF_SOULS.v(message, args);
}
/** Log a verbose exception and a message with optional format args. */
public static void v(Throwable t, String message, Object... args) {
TREE_OF_SOULS.v(t, message, args);
}
/** Log a debug message with optional format args. */
public static void d(String message, Object... args) {
TREE_OF_SOULS.d(message, args);
}
/** Log a debug exception and a message with optional format args. */
public static void d(Throwable t, String message, Object... args) {
TREE_OF_SOULS.d(t, message, args);
}
/** Log an info message with optional format args. */
public static void i(String message, Object... args) {
TREE_OF_SOULS.i(message, args);
}
/** Log an info exception and a message with optional format args. */
public static void i(Throwable t, String message, Object... args) {
TREE_OF_SOULS.i(t, message, args);
}
/** Log a warning message with optional format args. */
public static void w(String message, Object... args) {
TREE_OF_SOULS.w(message, args);
}
/** Log a warning exception and a message with optional format args. */
public static void w(Throwable t, String message, Object... args) {
TREE_OF_SOULS.w(t, message, args);
}
/** Log an error message with optional format args. */
public static void e(String message, Object... args) {
TREE_OF_SOULS.e(message, args);
}
/** Log an error exception and a message with optional format args. */
public static void e(Throwable t, String message, Object... args) {
TREE_OF_SOULS.e(t, message, args);
}
/** Log an assert message with optional format args. */
public static void wtf(String message, Object... args) {
TREE_OF_SOULS.wtf(message, args);
}
/** Log an assert exception and a message with optional format args. */
public static void wtf(Throwable t, String message, Object... args) {
TREE_OF_SOULS.wtf(t, message, args);
}
/**
* A view into Timber's planted trees as a tree itself. This can be used for
* injecting a logger instance rather than using static methods or to
* facilitate testing.
*/
public static Tree asTree() {
return TREE_OF_SOULS;
}
/** Set a one-time tag for use on the next logging call. */
public static Tree tag(String tag) {
List<Tree> forest = FOREST;
// noinspection ForLoopReplaceableByForEach
for (int i = 0, count = forest.size(); i < count; i++) {
forest.get(i).explicitTag.set(tag);
}
return TREE_OF_SOULS;
}
/** Add a new logging tree. */
public static void plant(Tree tree) {
if (tree == null) {
throw new NullPointerException("tree == null");
}
if (tree == TREE_OF_SOULS) {
throw new IllegalArgumentException("Cannot plant Timber into itself.");
}
FOREST.add(tree);
}
/** Remove a planted tree. */
public static void uproot(Tree tree) {
if (!FOREST.remove(tree)) {
throw new IllegalArgumentException("Cannot uproot tree which is not planted: " + tree);
}
}
/** Remove all planted trees. */
public static void uprootAll() {
FOREST.clear();
}
private static final List<Tree> FOREST = new CopyOnWriteArrayList<Tree>();
/**
* A {@link Tree} that delegates to all planted trees in the
* {@linkplain #FOREST forest}.
*/
private static final Tree TREE_OF_SOULS = new Tree() {
@Override
public void v(String message, Object... args) {
List<Tree> forest = FOREST;
// noinspection ForLoopReplaceableByForEach
for (int i = 0, count = forest.size(); i < count; i++) {
forest.get(i).v(message, args);
}
}
@Override
public void v(Throwable t, String message, Object... args) {
List<Tree> forest = FOREST;
// noinspection ForLoopReplaceableByForEach
for (int i = 0, count = forest.size(); i < count; i++) {
forest.get(i).v(t, message, args);
}
}
@Override
public void d(String message, Object... args) {
List<Tree> forest = FOREST;
// noinspection ForLoopReplaceableByForEach
for (int i = 0, count = forest.size(); i < count; i++) {
forest.get(i).d(message, args);
}
}
@Override
public void d(Throwable t, String message, Object... args) {
List<Tree> forest = FOREST;
// noinspection ForLoopReplaceableByForEach
for (int i = 0, count = forest.size(); i < count; i++) {
forest.get(i).d(t, message, args);
}
}
@Override
public void i(String message, Object... args) {
List<Tree> forest = FOREST;
// noinspection ForLoopReplaceableByForEach
for (int i = 0, count = forest.size(); i < count; i++) {
forest.get(i).i(message, args);
}
}
@Override
public void i(Throwable t, String message, Object... args) {
List<Tree> forest = FOREST;
// noinspection ForLoopReplaceableByForEach
for (int i = 0, count = forest.size(); i < count; i++) {
forest.get(i).i(t, message, args);
}
}
@Override
public void w(String message, Object... args) {
List<Tree> forest = FOREST;
// noinspection ForLoopReplaceableByForEach
for (int i = 0, count = forest.size(); i < count; i++) {
forest.get(i).w(message, args);
}
}
@Override
public void w(Throwable t, String message, Object... args) {
List<Tree> forest = FOREST;
// noinspection ForLoopReplaceableByForEach
for (int i = 0, count = forest.size(); i < count; i++) {
forest.get(i).w(t, message, args);
}
}
@Override
public void e(String message, Object... args) {
List<Tree> forest = FOREST;
// noinspection ForLoopReplaceableByForEach
for (int i = 0, count = forest.size(); i < count; i++) {
forest.get(i).e(message, args);
}
}
@Override
public void e(Throwable t, String message, Object... args) {
List<Tree> forest = FOREST;
// noinspection ForLoopReplaceableByForEach
for (int i = 0, count = forest.size(); i < count; i++) {
forest.get(i).e(t, message, args);
}
}
@Override
public void wtf(String message, Object... args) {
List<Tree> forest = FOREST;
// noinspection ForLoopReplaceableByForEach
for (int i = 0, count = forest.size(); i < count; i++) {
forest.get(i).wtf(message, args);
}
}
@Override
public void wtf(Throwable t, String message, Object... args) {
List<Tree> forest = FOREST;
// noinspection ForLoopReplaceableByForEach
for (int i = 0, count = forest.size(); i < count; i++) {
forest.get(i).wtf(t, message, args);
}
}
@Override
protected void log(int priority, String tag, String message, Throwable t) {
throw new AssertionError("Missing override for log method.");
}
};
private Timber() {
throw new AssertionError("No instances.");
}
/**
* A facade for handling logging calls. Install instances via {@link #plant
* Timber.plant()}.
*/
public static abstract class Tree {
private final ThreadLocal<String> explicitTag = new ThreadLocal<String>();
String getTag() {
String tag = explicitTag.get();
if (tag != null) {
explicitTag.remove();
}
return tag;
}
/** Log a verbose message with optional format args. */
public void v(String message, Object... args) {
prepareLog(Log.VERBOSE, null, message, args);
}
/** Log a verbose exception and a message with optional format args. */
public void v(Throwable t, String message, Object... args) {
prepareLog(Log.VERBOSE, t, message, args);
}
/** Log a debug message with optional format args. */
public void d(String message, Object... args) {
prepareLog(Log.DEBUG, null, message, args);
}
/** Log a debug exception and a message with optional format args. */
public void d(Throwable t, String message, Object... args) {
prepareLog(Log.DEBUG, t, message, args);
}
/** Log an info message with optional format args. */
public void i(String message, Object... args) {
prepareLog(Log.INFO, null, message, args);
}
/** Log an info exception and a message with optional format args. */
public void i(Throwable t, String message, Object... args) {
prepareLog(Log.INFO, t, message, args);
}
/** Log a warning message with optional format args. */
public void w(String message, Object... args) {
prepareLog(Log.WARN, null, message, args);
}
/** Log a warning exception and a message with optional format args. */
public void w(Throwable t, String message, Object... args) {
prepareLog(Log.WARN, t, message, args);
}
/** Log an error message with optional format args. */
public void e(String message, Object... args) {
prepareLog(Log.ERROR, null, message, args);
}
/** Log an error exception and a message with optional format args. */
public void e(Throwable t, String message, Object... args) {
prepareLog(Log.ERROR, t, message, args);
}
/** Log an assert message with optional format args. */
public void wtf(String message, Object... args) {
prepareLog(Log.ASSERT, null, message, args);
}
/** Log an assert exception and a message with optional format args. */
public void wtf(Throwable t, String message, Object... args) {
prepareLog(Log.ASSERT, t, message, args);
}
private void prepareLog(int priority, Throwable t, String message, Object... args) {
if (message != null && message.length() == 0) {
message = null;
}
if (message == null) {
if (t == null) {
return; // Swallow message if it's null and there's no
// throwable.
}
message = Log.getStackTraceString(t);
} else {
if (args.length > 0) {
message = String.format(message, args);
}
if (t != null) {
message += "\n" + Log.getStackTraceString(t);
}
}
log(priority, getTag(), message, t);
}
/**
* Write a log message to its destination. Called for all level-specific
* methods by default.
*
* @param priority
* Log level. See {@link Log} for constants.
* @param tag
* Explicit or inferred tag. May be {@code null}.
* @param message
* Formatted log message. May be {@code null}, but then
* {@code t} will not be.
* @param t
* Accompanying exceptions. May be {@code null}, but then
* {@code message} will not be.
*/
protected abstract void log(int priority, String tag, String message, Throwable t);
}
/**
* A {@link Tree Tree} for debug builds. Automatically infers the tag from
* the calling class.
*/
public static class DebugTree extends Tree {
private static final int MAX_LOG_LENGTH = 4000;
private static final int CALL_STACK_INDEX = 5;
private static final Pattern ANONYMOUS_CLASS = Pattern.compile("(\\$\\d+)+$");
/**
* Extract the tag which should be used for the message from the
* {@code element}. By default this will use the class name without any
* anonymous class suffixes (e.g., {@code Foo$1} becomes {@code Foo}).
* <p>
* Note: This will not be called if a {@linkplain #tag(String) manual
* tag} was specified.
*/
protected String createStackElementTag(StackTraceElement element) {
String tag = element.getClassName();
Matcher m = ANONYMOUS_CLASS.matcher(tag);
if (m.find()) {
tag = m.replaceAll("");
}
return tag.substring(tag.lastIndexOf('.') + 1);
}
@Override
final String getTag() {
String tag = super.getTag();
if (tag != null) {
return tag;
}
// DO NOT switch this to Thread.getCurrentThread().getStackTrace().
// The test will pass
// because Robolectric runs them on the JVM but on Android the
// elements are different.
StackTraceElement[] stackTrace = new Throwable().getStackTrace();
if (stackTrace.length <= CALL_STACK_INDEX) {
throw new IllegalStateException("Synthetic stacktrace didn't have enough elements: are you using proguard?");
}
return createStackElementTag(stackTrace[CALL_STACK_INDEX]);
}
/**
* Break up {@code message} into maximum-length chunks (if needed) and
* send to either {@link Log#println(int, String, String) Log.println()}
* or {@link Log#wtf(String, String) Log.wtf()} for logging.
*
* {@inheritDoc}
*/
@Override
protected void log(int priority, String tag, String message, Throwable t) {
if (message.length() < MAX_LOG_LENGTH) {
if (priority == Log.ASSERT) {
Log.wtf(tag, message);
} else {
Log.println(priority, tag, message);
}
return;
}
// Split by line, then ensure each line can fit into Log's maximum
// length.
for (int i = 0, length = message.length(); i < length; i++) {
int newline = message.indexOf('\n', i);
newline = newline != -1 ? newline : length;
do {
int end = Math.min(newline, i + MAX_LOG_LENGTH);
String part = message.substring(i, end);
if (priority == Log.ASSERT) {
Log.wtf(tag, part);
} else {
Log.println(priority, tag, part);
}
i = end;
} while (i < newline);
}
}
}
}