diff --git a/build.gradle b/build.gradle index 6dfc56a9..f92c82f9 100644 --- a/build.gradle +++ b/build.gradle @@ -31,6 +31,7 @@ ext { compileSdk : 28, appcompat : '1.0.2', androidx : '1.0.0', + fragment : '1.1.0', material : '1.0.0', lifecycle : '2.0.0', dagger : '2.17', diff --git a/logging/build.gradle b/logging/build.gradle index 21ed2450..aa5b69e8 100644 --- a/logging/build.gradle +++ b/logging/build.gradle @@ -1,4 +1,6 @@ apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' android { compileSdkVersion versions.compileSdk @@ -14,5 +16,6 @@ android { } dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "androidx.annotation:annotation:$versions.androidx" } diff --git a/logging/src/main/java/ru/touchin/roboswag/core/log/ConsoleLogProcessor.java b/logging/src/main/java/ru/touchin/roboswag/core/log/ConsoleLogProcessor.java deleted file mode 100644 index 8e00787e..00000000 --- a/logging/src/main/java/ru/touchin/roboswag/core/log/ConsoleLogProcessor.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) - * - * This file is part of RoboSwag library. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package ru.touchin.roboswag.core.log; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import android.util.Log; - -/** - * Created by Gavriil Sitnikov on 13/11/2015. - * Simple {@link LogProcessor} implementation which is logging messages to console (logcat). - */ -public class ConsoleLogProcessor extends LogProcessor { - - private static final int MAX_LOG_LENGTH = 4000; - - public ConsoleLogProcessor(@NonNull final LcLevel lclevel) { - super(lclevel); - } - - @NonNull - private String normalize(@NonNull final String message) { - return message.replace("\r\n", "\n").replace("\0", ""); - } - - @Override - @SuppressWarnings({"WrongConstant", "LogConditional"}) - //WrongConstant, LogConditional: level.getPriority() is not wrong constant! - public void processLogMessage(@NonNull final LcGroup group, @NonNull final LcLevel level, - @NonNull final String tag, @NonNull final String message, @Nullable final Throwable throwable) { - final String messageToLog = normalize(message + (throwable != null ? '\n' + Log.getStackTraceString(throwable) : "")); - final int length = messageToLog.length(); - for (int i = 0; i < length; i++) { - int newline = messageToLog.indexOf('\n', i); - newline = newline != -1 ? newline : length; - do { - final int end = Math.min(newline, i + MAX_LOG_LENGTH); - Log.println(level.getPriority(), tag, messageToLog.substring(i, end)); - i = end; - } - while (i < newline); - } - - } - -} diff --git a/logging/src/main/java/ru/touchin/roboswag/core/log/ConsoleLogProcessor.kt b/logging/src/main/java/ru/touchin/roboswag/core/log/ConsoleLogProcessor.kt new file mode 100644 index 00000000..4e6acd08 --- /dev/null +++ b/logging/src/main/java/ru/touchin/roboswag/core/log/ConsoleLogProcessor.kt @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2019 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) + * + * This file is part of RoboSwag library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package ru.touchin.roboswag.core.log + +import android.util.Log + +/** + * Simple [LogProcessor] implementation which is logging messages to console (logcat). + */ +class ConsoleLogProcessor(lclevel: LcLevel) : LogProcessor(lclevel) { + + companion object { + private const val MAX_LOG_LENGTH = 4000 + } + + @SuppressWarnings("WrongConstant", "LogConditional") + //WrongConstant, LogConditional: level.getPriority() is not wrong constant! + override fun processLogMessage( + group: LcGroup, + level: LcLevel, + tag: String, + message: String, + throwable: Throwable? + ) { + message.plus(throwable?.let { "\n${Log.getStackTraceString(throwable)}" }.orEmpty()) + .normalize() + .split('\n') + .map { msg -> msg.chunked(MAX_LOG_LENGTH) } + .flatten() + .forEach { msg -> + Log.println(level.priority, tag, msg) + } + } + + private fun String.normalize(): String = this + .replace("\r\n", "\n") + .replace("\u0000", "") + +} diff --git a/logging/src/main/java/ru/touchin/roboswag/core/log/Lc.java b/logging/src/main/java/ru/touchin/roboswag/core/log/Lc.java deleted file mode 100644 index 435852fc..00000000 --- a/logging/src/main/java/ru/touchin/roboswag/core/log/Lc.java +++ /dev/null @@ -1,290 +0,0 @@ -/* - * Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) - * - * This file is part of RoboSwag library. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package ru.touchin.roboswag.core.log; - -import android.annotation.SuppressLint; -import android.os.Handler; -import android.os.Looper; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import android.text.TextUtils; -import android.util.Log; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import ru.touchin.roboswag.core.utils.ShouldNotHappenException; - -/** - * Created by Gavriil Sitnikov on 13/11/2015. - * General logging utility of RoboSwag library. - * You can initialize {@link LogProcessor} to intercept log messages and make decision how to show them. - * Also you can specify assertions behavior to manually make application more stable in production but intercept illegal states in some - * third-party tool to fix them later but not crash in production. - */ -@SuppressWarnings({"checkstyle:methodname", "PMD.ShortMethodName", "PMD.ShortClassName"}) -//MethodNameCheck,ShortMethodName: log methods better be 1-symbol -public final class Lc { - - public static final LcGroup GENERAL_LC_GROUP = new LcGroup("GENERAL"); - - public static final int STACK_TRACE_CODE_DEPTH; - - private static boolean crashOnAssertions = true; - @NonNull - private static LogProcessor logProcessor = new ConsoleLogProcessor(LcLevel.ERROR); - - static { - final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); - int stackDepth; - for (stackDepth = 0; stackDepth < stackTrace.length; stackDepth++) { - if (stackTrace[stackDepth].getClassName().equals(Lc.class.getName())) { - break; - } - } - STACK_TRACE_CODE_DEPTH = stackDepth + 1; - } - - /** - * Flag to crash application or pass it to {@link LogProcessor#processLogMessage(LcGroup, LcLevel, String, String, Throwable)} - * on specific {@link LcGroup#assertion(Throwable)} points of code. - * - * @return True if application should crash on assertion. - */ - public static boolean isCrashOnAssertions() { - return crashOnAssertions; - } - - /** - * Returns {@link LogProcessor} object to intercept incoming log messages (by default it returns {@link ConsoleLogProcessor}). - * - * @return Specific {@link LogProcessor}. - */ - @NonNull - public static LogProcessor getLogProcessor() { - return logProcessor; - } - - /** - * Initialize general logging behavior. - * - * @param logProcessor {@link LogProcessor} to intercept all log messages; - * @param crashOnAssertions Flag to crash application - * or pass it to {@link LogProcessor#processLogMessage(LcGroup, LcLevel, String, String, Throwable)} - * on specific {@link LcGroup#assertion(Throwable)} points of code. - */ - public static void initialize(@NonNull final LogProcessor logProcessor, final boolean crashOnAssertions) { - Lc.crashOnAssertions = crashOnAssertions; - Lc.logProcessor = logProcessor; - } - - /** - * Logs debug message via {@link #GENERAL_LC_GROUP}. - * - * @param message Message or format of message to log; - * @param args Arguments of formatted message. - */ - public static void d(@NonNull final String message, @NonNull final Object... args) { - GENERAL_LC_GROUP.d(message, args); - } - - /** - * Logs debug message via {@link #GENERAL_LC_GROUP}. - * - * @param throwable Exception to log; - * @param message Message or format of message to log; - * @param args Arguments of formatted message. - */ - public static void d(@NonNull final Throwable throwable, @NonNull final String message, @NonNull final Object... args) { - GENERAL_LC_GROUP.d(throwable, message, args); - } - - /** - * Logs info message via {@link #GENERAL_LC_GROUP}. - * - * @param message Message or format of message to log; - * @param args Arguments of formatted message. - */ - public static void i(@NonNull final String message, @NonNull final Object... args) { - GENERAL_LC_GROUP.i(message, args); - } - - /** - * Logs info message via {@link #GENERAL_LC_GROUP}. - * - * @param throwable Exception to log; - * @param message Message or format of message to log; - * @param args Arguments of formatted message. - */ - public static void i(@NonNull final Throwable throwable, @NonNull final String message, @NonNull final Object... args) { - GENERAL_LC_GROUP.i(throwable, message, args); - } - - /** - * Logs warning message via {@link #GENERAL_LC_GROUP}. - * - * @param message Message or format of message to log; - * @param args Arguments of formatted message. - */ - public static void w(@NonNull final String message, @NonNull final Object... args) { - GENERAL_LC_GROUP.w(message, args); - } - - /** - * Logs warning message via {@link #GENERAL_LC_GROUP}. - * - * @param throwable Exception to log; - * @param message Message or format of message to log; - * @param args Arguments of formatted message. - */ - public static void w(@NonNull final Throwable throwable, @NonNull final String message, @NonNull final Object... args) { - GENERAL_LC_GROUP.w(throwable, message, args); - } - - /** - * Logs error message via {@link #GENERAL_LC_GROUP}. - * - * @param message Message or format of message to log; - * @param args Arguments of formatted message. - */ - public static void e(@NonNull final String message, @NonNull final Object... args) { - GENERAL_LC_GROUP.e(message, args); - } - - /** - * Logs error message via {@link #GENERAL_LC_GROUP}. - * - * @param throwable Exception to log; - * @param message Message or format of message to log; - * @param args Arguments of formatted message. - */ - public static void e(@NonNull final Throwable throwable, @NonNull final String message, @NonNull final Object... args) { - GENERAL_LC_GROUP.e(throwable, message, args); - } - - /** - * Processes assertion. Normally it will throw {@link ShouldNotHappenException} and crash app. - * If it should crash or not is specified at {@link Lc#isCrashOnAssertions()}. - * In some cases crash on assertions should be switched off and assertion should be processed in {@link LogProcessor}. - * It is useful for example to not crash but log it as handled crash in Crashlitycs in production build. - * - * @param message Message that is describing assertion. - */ - public static void assertion(@NonNull final String message) { - GENERAL_LC_GROUP.assertion(message); - } - - /** - * Processes assertion. Normally it will throw {@link ShouldNotHappenException} and crash app. - * If it should crash or not is specified at {@link Lc#isCrashOnAssertions()}. - * In some cases crash on assertions should be switched off and assertion should be processed in {@link LogProcessor}. - * It is useful for example to not crash but log it as handled crash in Crashlitycs in production build. - * - * @param throwable Exception that is describing assertion. - */ - public static void assertion(@NonNull final Throwable throwable) { - GENERAL_LC_GROUP.assertion(throwable); - } - - /** - * Throws assertion on main thread (to avoid Rx exceptions e.g.) and cuts top causes by type of exception class. - * - * @param assertion Source throwable; - * @param exceptionsClassesToCut Classes which will be cut from top of causes stack of source throwable. - */ - @SafeVarargs - public static void cutAssertion(@NonNull final Throwable assertion, @NonNull final Class... exceptionsClassesToCut) { - new Handler(Looper.getMainLooper()).post(() -> { - final List processedExceptions = new ArrayList<>(); - Throwable result = assertion; - boolean exceptionAssignableFromIgnores; - do { - exceptionAssignableFromIgnores = false; - processedExceptions.add(result); - for (final Class exceptionClass : exceptionsClassesToCut) { - if (result.getClass().isAssignableFrom(exceptionClass)) { - exceptionAssignableFromIgnores = true; - result = result.getCause(); - break; - } - } - } - while (exceptionAssignableFromIgnores && result != null && !processedExceptions.contains(result)); - Lc.assertion(result != null ? result : assertion); - }); - } - - /** - * Returns line of code from where this method called. - * - * @param caller Object who is calling for code point; - * @return String represents code point. - */ - @NonNull - public static String getCodePoint(@Nullable final Object caller) { - return getCodePoint(caller, 1); - } - - /** - * Returns line of code from where this method called. - * - * @param caller Object who is calling for code point; - * @param stackShift caller Shift of stack (e.g. 2 means two elements deeper); - * @return String represents code point. - */ - @NonNull - public static String getCodePoint(@Nullable final Object caller, final int stackShift) { - final StackTraceElement traceElement = Thread.currentThread().getStackTrace()[STACK_TRACE_CODE_DEPTH + stackShift]; - return traceElement.getMethodName() + '(' + traceElement.getFileName() + ':' + traceElement.getLineNumber() + ')' - + (caller != null ? " of object " + caller.getClass().getSimpleName() + '(' + Integer.toHexString(caller.hashCode()) + ')' : ""); - } - - /** - * Returns line of code from where this method called. - * - * @param caller Object who is calling for code point; - * @param methodName String represents lifecycle method in which it was called - * @return String represents code point. - */ - @NonNull - public static String getCodePoint(@Nullable final Object caller, final String methodName) { - return methodName - + (caller != null ? " of object " + caller.getClass().getSimpleName() : ""); - } - - /** - * Prints stacktrace in log with specified tag. - * - * @param tag Tag to be shown in logs. - */ - - @SuppressLint("LogConditional") - public static void printStackTrace(@NonNull final String tag) { - final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); - if (Log.isLoggable(tag, Log.DEBUG)) { - Log.d(tag, TextUtils.join("\n", Arrays.copyOfRange(stackTrace, STACK_TRACE_CODE_DEPTH, stackTrace.length))); - } - } - - private Lc() { - } - -} diff --git a/logging/src/main/java/ru/touchin/roboswag/core/log/Lc.kt b/logging/src/main/java/ru/touchin/roboswag/core/log/Lc.kt new file mode 100644 index 00000000..e2da9193 --- /dev/null +++ b/logging/src/main/java/ru/touchin/roboswag/core/log/Lc.kt @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2019 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) + * + * This file is part of RoboSwag library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package ru.touchin.roboswag.core.log + +import android.annotation.SuppressLint +import android.os.Handler +import android.os.Looper +import android.util.Log +import ru.touchin.roboswag.core.utils.ShouldNotHappenException +import java.util.Arrays + +/** + * General logging utility of RoboSwag library. + * You can initialize [LogProcessor] to intercept log messages and make decision how to show them. + * Also you can specify assertions behavior to manually make application more stable in production but intercept illegal states in some + * third-party tool to fix them later but not crash in production. + */ +//MethodNameCheck,ShortMethodName: log methods better be 1-symbol +object Lc { + + private val GENERAL_LC_GROUP = LcGroup("GENERAL") + + val STACK_TRACE_CODE_DEPTH: Int + + /** + * Flag to crash application or pass it to [LogProcessor.processLogMessage] + * on specific [LcGroup.assertion] points of code. + * + * @return True if application should crash on assertion. + */ + var isCrashOnAssertions = true + private set + + /** + * Returns [LogProcessor] object to intercept incoming log messages (by default it returns [ConsoleLogProcessor]). + * + * @return Specific [LogProcessor]. + */ + var logProcessor: LogProcessor = ConsoleLogProcessor(LcLevel.ERROR) + private set + + init { + val stackTrace = Thread.currentThread().stackTrace + var stackDepth = 0 + while (stackDepth < stackTrace.size) { + if (stackTrace[stackDepth].className == Lc::class.java.name) { + break + } + stackDepth++ + } + STACK_TRACE_CODE_DEPTH = stackDepth + 1 + } + + /** + * Initialize general logging behavior. + * + * @param logProcessor [LogProcessor] to intercept all log messages; + * @param crashOnAssertions Flag to crash application + * or pass it to [LogProcessor.processLogMessage] + * on specific [LcGroup.assertion] points of code. + */ + fun initialize(logProcessor: LogProcessor, crashOnAssertions: Boolean) { + isCrashOnAssertions = crashOnAssertions + Lc.logProcessor = logProcessor + } + + /** + * Logs debug message via [.GENERAL_LC_GROUP]. + * + * @param message Message or format of message to log; + * @param args Arguments of formatted message. + */ + fun d(message: String, vararg args: Any) { + GENERAL_LC_GROUP.d(message, *args) + } + + /** + * Logs debug message via [.GENERAL_LC_GROUP]. + * + * @param throwable Exception to log; + * @param message Message or format of message to log; + * @param args Arguments of formatted message. + */ + fun d(throwable: Throwable, message: String, vararg args: Any) { + GENERAL_LC_GROUP.d(throwable, message, *args) + } + + /** + * Logs info message via [.GENERAL_LC_GROUP]. + * + * @param message Message or format of message to log; + * @param args Arguments of formatted message. + */ + fun i(message: String, vararg args: Any) { + GENERAL_LC_GROUP.i(message, *args) + } + + /** + * Logs info message via [.GENERAL_LC_GROUP]. + * + * @param throwable Exception to log; + * @param message Message or format of message to log; + * @param args Arguments of formatted message. + */ + fun i(throwable: Throwable, message: String, vararg args: Any) { + GENERAL_LC_GROUP.i(throwable, message, *args) + } + + /** + * Logs warning message via [.GENERAL_LC_GROUP]. + * + * @param message Message or format of message to log; + * @param args Arguments of formatted message. + */ + fun w(message: String, vararg args: Any) { + GENERAL_LC_GROUP.w(message, *args) + } + + /** + * Logs warning message via [.GENERAL_LC_GROUP]. + * + * @param throwable Exception to log; + * @param message Message or format of message to log; + * @param args Arguments of formatted message. + */ + fun w(throwable: Throwable, message: String, vararg args: Any) { + GENERAL_LC_GROUP.w(throwable, message, *args) + } + + /** + * Logs error message via [.GENERAL_LC_GROUP]. + * + * @param message Message or format of message to log; + * @param args Arguments of formatted message. + */ + fun e(message: String, vararg args: Any) { + GENERAL_LC_GROUP.e(message, *args) + } + + /** + * Logs error message via [.GENERAL_LC_GROUP]. + * + * @param throwable Exception to log; + * @param message Message or format of message to log; + * @param args Arguments of formatted message. + */ + fun e(throwable: Throwable, message: String, vararg args: Any) { + GENERAL_LC_GROUP.e(throwable, message, *args) + } + + /** + * Processes assertion. Normally it will throw [ShouldNotHappenException] and crash app. + * If it should crash or not is specified at [Lc.isCrashOnAssertions]. + * In some cases crash on assertions should be switched off and assertion should be processed in [LogProcessor]. + * It is useful for example to not crash but log it as handled crash in Crashlitycs in production build. + * + * @param message Message that is describing assertion. + */ + fun assertion(message: String) { + GENERAL_LC_GROUP.assertion(message) + } + + /** + * Processes assertion. Normally it will throw [ShouldNotHappenException] and crash app. + * If it should crash or not is specified at [Lc.isCrashOnAssertions]. + * In some cases crash on assertions should be switched off and assertion should be processed in [LogProcessor]. + * It is useful for example to not crash but log it as handled crash in Crashlitycs in production build. + * + * @param throwable Exception that is describing assertion. + */ + fun assertion(throwable: Throwable) { + GENERAL_LC_GROUP.assertion(throwable) + } + + /** + * Throws assertion on main thread (to avoid Rx exceptions e.g.) and cuts top causes by type of exception class. + * + * @param assertion Source throwable; + * @param exceptionsClassesToCut Classes which will be cut from top of causes stack of source throwable. + */ + @SafeVarargs + fun cutAssertion(assertion: Throwable, vararg exceptionsClassesToCut: Class) { + Handler(Looper.getMainLooper()).post { + val processedExceptions = arrayListOf() + var result: Throwable? = assertion + var exceptionAssignableFromIgnores: Boolean + do { + exceptionAssignableFromIgnores = false + processedExceptions.add(result!!) + for (exceptionClass in exceptionsClassesToCut) { + if (result!!.javaClass.isAssignableFrom(exceptionClass)) { + exceptionAssignableFromIgnores = true + result = result.cause + break + } + } + } while (exceptionAssignableFromIgnores && result != null && !processedExceptions.contains(result)) + Lc.assertion(result ?: assertion) + } + } + + /** + * Returns line of code from where this method called. + * + * @param caller Object who is calling for code point; + * @param stackShift caller Shift of stack (e.g. 2 means two elements deeper); + * @return String represents code point. + */ + fun getCodePoint(caller: Any?, stackShift: Int = 1): String { + val traceElement = Thread.currentThread().stackTrace[STACK_TRACE_CODE_DEPTH + stackShift] + return "${traceElement.methodName}(${traceElement.fileName}:${traceElement.lineNumber})" + .plus(caller?.let { " of object ${caller.javaClass.simpleName}(${Integer.toHexString(caller.hashCode())})" }.orEmpty()) + } + + /** + * Returns line of code from where this method called. + * + * @param caller Object who is calling for code point; + * @param methodName String represents lifecycle method in which it was called + * @return String represents code point. + */ + fun getCodePoint(caller: Any?, methodName: String): String = methodName + .plus(caller?.let { " of object ${caller.javaClass.simpleName}" }.orEmpty()) + + /** + * Prints stacktrace in log with specified tag. + * + * @param tag Tag to be shown in logs. + */ + @SuppressLint("LogConditional") + fun printStackTrace(tag: String) { + val stackTrace = Thread.currentThread().stackTrace + if (Log.isLoggable(tag, Log.DEBUG)) { + Log.d(tag, Arrays.copyOfRange(stackTrace, STACK_TRACE_CODE_DEPTH, stackTrace.size).joinToString(separator = "\n")) + } + } + +} diff --git a/logging/src/main/java/ru/touchin/roboswag/core/log/LcGroup.java b/logging/src/main/java/ru/touchin/roboswag/core/log/LcGroup.java deleted file mode 100644 index f1ea1b02..00000000 --- a/logging/src/main/java/ru/touchin/roboswag/core/log/LcGroup.java +++ /dev/null @@ -1,237 +0,0 @@ -/* - * Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) - * - * This file is part of RoboSwag library. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package ru.touchin.roboswag.core.log; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.text.SimpleDateFormat; -import java.util.Locale; - -import ru.touchin.roboswag.core.utils.ShouldNotHappenException; -import ru.touchin.roboswag.core.utils.ThreadLocalValue; - -/** - * Created by Gavriil Sitnikov on 14/05/2016. - * Group of log messages with specific tag prefix (name of group). - * It could be used in specific {@link LogProcessor} to filter messages by group. - */ -@SuppressWarnings({"checkstyle:methodname", "PMD.ShortMethodName"}) -//MethodNameCheck,ShortMethodName: log methods better be 1-symbol -public class LcGroup { - - /** - * Logging group to log UI metrics (like inflation or layout time etc.). - */ - public static final LcGroup UI_METRICS = new LcGroup("UI_METRICS"); - /** - * Logging group to log UI lifecycle (onCreate, onStart, onResume etc.). - */ - public static final LcGroup UI_LIFECYCLE = new LcGroup("UI_LIFECYCLE"); - - private static final ThreadLocalValue DATE_TIME_FORMATTER - = new ThreadLocalValue<>(() -> new SimpleDateFormat("HH:mm:ss.SSS", Locale.getDefault())); - - @NonNull - private final String name; - private boolean disabled; - - public LcGroup(@NonNull final String name) { - this.name = name; - } - - /** - * Disables logging of this group. - */ - public void disable() { - disabled = true; - } - - /** - * Enables logging of this group. - */ - public void enable() { - disabled = false; - } - - @NonNull - private String createLogTag() { - final StackTraceElement trace = Thread.currentThread().getStackTrace()[Lc.STACK_TRACE_CODE_DEPTH + 3]; - return trace.getFileName() + ':' + trace.getLineNumber(); - } - - @SuppressWarnings("PMD.AvoidCatchingThrowable") - //AvoidCatchingThrowable: it is needed to safety format message - @Nullable - private String createFormattedMessage(@Nullable final String message, @NonNull final Object... args) { - try { - if (args.length > 0 && message == null) { - throw new ShouldNotHappenException("Args are not empty but format message is null"); - } - return message != null ? (args.length > 0 ? String.format(message, args) : message) : null; - } catch (final Throwable formattingException) { - Lc.assertion(formattingException); - return null; - } - } - - @NonNull - private String createLogMessage(@Nullable final String formattedMessage) { - return DATE_TIME_FORMATTER.get().format(System.currentTimeMillis()) - + ' ' + Thread.currentThread().getName() - + ' ' + name - + (formattedMessage != null ? (' ' + formattedMessage) : ""); - } - - private void logMessage(@NonNull final LcLevel logLevel, @Nullable final String message, - @Nullable final Throwable throwable, @NonNull final Object... args) { - if (disabled || logLevel.lessThan(Lc.getLogProcessor().getMinLogLevel())) { - return; - } - - if (throwable == null && args.length > 0 && args[0] instanceof Throwable) { - Lc.w("Maybe you've misplaced exception with first format arg? format: %s; arg: %s", message, args[0]); - } - - final String formattedMessage = createFormattedMessage(message, args); - if (logLevel == LcLevel.ASSERT && Lc.isCrashOnAssertions()) { - throw createAssertion(formattedMessage, throwable); - } - - Lc.getLogProcessor().processLogMessage(this, logLevel, createLogTag(), createLogMessage(formattedMessage), throwable); - } - - @NonNull - private ShouldNotHappenException createAssertion(@Nullable final String message, @Nullable final Throwable exception) { - return exception != null - ? (message != null ? new ShouldNotHappenException(message, exception) - : (exception instanceof ShouldNotHappenException ? (ShouldNotHappenException) exception : new ShouldNotHappenException(exception))) - : (message != null ? new ShouldNotHappenException(message) : new ShouldNotHappenException()); - } - - /** - * Logs debug message. - * - * @param message Message or format of message to log; - * @param args Arguments of formatted message. - */ - public void d(@NonNull final String message, @NonNull final Object... args) { - logMessage(LcLevel.DEBUG, message, null, args); - } - - /** - * Logs debug message. - * - * @param throwable Exception to log; - * @param message Message or format of message to log; - * @param args Arguments of formatted message. - */ - public void d(@NonNull final Throwable throwable, @NonNull final String message, @NonNull final Object... args) { - logMessage(LcLevel.DEBUG, message, throwable, args); - } - - /** - * Logs info message. - * - * @param message Message or format of message to log; - * @param args Arguments of formatted message. - */ - public void i(@NonNull final String message, @NonNull final Object... args) { - logMessage(LcLevel.INFO, message, null, args); - } - - /** - * Logs info message. - * - * @param throwable Exception to log; - * @param message Message or format of message to log; - * @param args Arguments of formatted message. - */ - public void i(@NonNull final Throwable throwable, @NonNull final String message, @NonNull final Object... args) { - logMessage(LcLevel.INFO, message, throwable, args); - } - - /** - * Logs warning message. - * - * @param message Message or format of message to log; - * @param args Arguments of formatted message. - */ - public void w(@NonNull final String message, @NonNull final Object... args) { - logMessage(LcLevel.WARN, message, null, args); - } - - /** - * Logs warning message. - * - * @param throwable Exception to log; - * @param message Message or format of message to log; - * @param args Arguments of formatted message. - */ - public void w(@NonNull final Throwable throwable, @NonNull final String message, @NonNull final Object... args) { - logMessage(LcLevel.WARN, message, throwable, args); - } - - /** - * Logs error message. - * - * @param message Message or format of message to log; - * @param args Arguments of formatted message. - */ - public void e(@NonNull final String message, @NonNull final Object... args) { - logMessage(LcLevel.ERROR, message, null, args); - } - - /** - * Logs error message. - * - * @param throwable Exception to log; - * @param message Message or format of message to log; - * @param args Arguments of formatted message. - */ - public void e(@NonNull final Throwable throwable, @NonNull final String message, @NonNull final Object... args) { - logMessage(LcLevel.ERROR, message, throwable, args); - } - - /** - * Processes assertion. Normally it will throw {@link ShouldNotHappenException} and crash app. - * If it should crash or not is specified at {@link Lc#isCrashOnAssertions()}. - * In some cases crash on assertions should be switched off and assertion should be processed in {@link LogProcessor}. - * It is useful for example to not crash but log it as handled crash in Crashlitycs in production build. - * - * @param message Message that is describing assertion. - */ - public void assertion(@NonNull final String message) { - logMessage(LcLevel.ASSERT, "Assertion appears at %s with message: %s", null, Lc.getCodePoint(null, 2), message); - } - - /** - * Processes assertion. Normally it will throw {@link ShouldNotHappenException} and crash app. - * If it should crash or not is specified at {@link Lc#isCrashOnAssertions()}. - * In some cases crash on assertions should be switched off and assertion should be processed in {@link LogProcessor}. - * It is useful for example to not crash but log it as handled crash in Crashlitycs in production build. - * - * @param throwable Exception that is describing assertion. - */ - public void assertion(@NonNull final Throwable throwable) { - logMessage(LcLevel.ASSERT, "Assertion appears at %s", throwable, Lc.getCodePoint(null, 2)); - } - -} diff --git a/logging/src/main/java/ru/touchin/roboswag/core/log/LcGroup.kt b/logging/src/main/java/ru/touchin/roboswag/core/log/LcGroup.kt new file mode 100644 index 00000000..699e1a65 --- /dev/null +++ b/logging/src/main/java/ru/touchin/roboswag/core/log/LcGroup.kt @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2019 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) + * + * This file is part of RoboSwag library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package ru.touchin.roboswag.core.log + +import ru.touchin.roboswag.core.utils.ShouldNotHappenException +import ru.touchin.roboswag.core.utils.ThreadLocalValue +import java.text.SimpleDateFormat +import java.util.Locale + +/** + * Group of log messages with specific tag prefix (name of group). + * It could be used in specific [LogProcessor] to filter messages by group. + */ +//MethodNameCheck,ShortMethodName: log methods better be 1-symbol +class LcGroup(private val name: String) { + + companion object { + + /** + * Logging group to log UI metrics (like inflation or layout time etc.). + */ + val UI_METRICS = LcGroup("UI_METRICS") + + /** + * Logging group to log UI lifecycle (onCreate, onStart, onResume etc.). + */ + val UI_LIFECYCLE = LcGroup("UI_LIFECYCLE") + + private val DATE_TIME_FORMATTER = ThreadLocalValue(object : ThreadLocalValue.Fabric { + + override fun create(): SimpleDateFormat = SimpleDateFormat("HH:mm:ss.SSS", Locale.getDefault()) + + }) + } + + private var disabled: Boolean = false + + /** + * Disables logging of this group. + */ + fun disable() { + disabled = true + } + + /** + * Enables logging of this group. + */ + fun enable() { + disabled = false + } + + private fun createLogTag(): String { + val trace = Thread.currentThread().stackTrace[Lc.STACK_TRACE_CODE_DEPTH + 3] + return "${trace.fileName}:${trace.lineNumber}" + } + + @SuppressWarnings("PMD.AvoidCatchingThrowable") + //AvoidCatchingThrowable: it is needed to safety format message + private fun createFormattedMessage(message: String?, vararg args: Any): String? = + try { + if (args.isNotEmpty() && message == null) { + throw ShouldNotHappenException("Args are not empty but format message is null") + } + message?.let { if (args.isNotEmpty()) String.format(message, *args) else message } + } catch (formattingException: Throwable) { + Lc.assertion(formattingException) + null + } + + private fun createLogMessage(formattedMessage: String?): String = DATE_TIME_FORMATTER.get() + .let { formatter -> + formatter?.format(System.currentTimeMillis()) ?: throw ShouldNotHappenException("Formatter is null") + } + .plus(" ${Thread.currentThread().name}") + .plus(" $name") + .plus(formattedMessage?.let { " $formattedMessage" }.orEmpty()) + + private fun logMessage( + logLevel: LcLevel, + message: String, + throwable: Throwable?, + vararg args: Any + ) { + if (disabled || logLevel.lessThan(Lc.logProcessor.minLogLevel)) return + + if (throwable == null && args.isNotEmpty() && args[0] is Throwable) { + Lc.w("Maybe you've misplaced exception with first format arg? format: %s; arg: %s", message, args[0]) + } + + val formattedMessage = createFormattedMessage(message, *args) + if (logLevel == LcLevel.ASSERT && Lc.isCrashOnAssertions) { + throw createAssertion(formattedMessage, throwable) + } + + Lc.logProcessor.processLogMessage(this, logLevel, createLogTag(), createLogMessage(formattedMessage), throwable) + } + + private fun createAssertion(message: String?, exception: Throwable?): ShouldNotHappenException = when { + exception != null && message != null -> ShouldNotHappenException(message, exception) + exception != null && exception is ShouldNotHappenException -> exception + exception != null -> ShouldNotHappenException(exception) + message != null -> ShouldNotHappenException(message) + else -> ShouldNotHappenException() + } + + /** + * Logs debug message. + * + * @param message Message or format of message to log; + * @param args Arguments of formatted message. + */ + fun d(message: String, vararg args: Any) { + logMessage(LcLevel.DEBUG, message, null, *args) + } + + /** + * Logs debug message. + * + * @param throwable Exception to log; + * @param message Message or format of message to log; + * @param args Arguments of formatted message. + */ + fun d(throwable: Throwable, message: String, vararg args: Any) { + logMessage(LcLevel.DEBUG, message, throwable, *args) + } + + /** + * Logs info message. + * + * @param message Message or format of message to log; + * @param args Arguments of formatted message. + */ + fun i(message: String, vararg args: Any) { + logMessage(LcLevel.INFO, message, null, *args) + } + + /** + * Logs info message. + * + * @param throwable Exception to log; + * @param message Message or format of message to log; + * @param args Arguments of formatted message. + */ + fun i(throwable: Throwable, message: String, vararg args: Any) { + logMessage(LcLevel.INFO, message, throwable, *args) + } + + /** + * Logs warning message. + * + * @param message Message or format of message to log; + * @param args Arguments of formatted message. + */ + fun w(message: String, vararg args: Any) { + logMessage(LcLevel.WARN, message, null, *args) + } + + /** + * Logs warning message. + * + * @param throwable Exception to log; + * @param message Message or format of message to log; + * @param args Arguments of formatted message. + */ + fun w(throwable: Throwable, message: String, vararg args: Any) { + logMessage(LcLevel.WARN, message, throwable, *args) + } + + /** + * Logs error message. + * + * @param message Message or format of message to log; + * @param args Arguments of formatted message. + */ + fun e(message: String, vararg args: Any) { + logMessage(LcLevel.ERROR, message, null, *args) + } + + /** + * Logs error message. + * + * @param throwable Exception to log; + * @param message Message or format of message to log; + * @param args Arguments of formatted message. + */ + fun e(throwable: Throwable, message: String, vararg args: Any) { + logMessage(LcLevel.ERROR, message, throwable, *args) + } + + /** + * Processes assertion. Normally it will throw [ShouldNotHappenException] and crash app. + * If it should crash or not is specified at [Lc.isCrashOnAssertions]. + * In some cases crash on assertions should be switched off and assertion should be processed in [LogProcessor]. + * It is useful for example to not crash but log it as handled crash in Crashlitycs in production build. + * + * @param message Message that is describing assertion. + */ + fun assertion(message: String) { + logMessage(LcLevel.ASSERT, "Assertion appears at %s with message: %s", null, Lc.getCodePoint(null, 2), message) + } + + /** + * Processes assertion. Normally it will throw [ShouldNotHappenException] and crash app. + * If it should crash or not is specified at [Lc.isCrashOnAssertions]. + * In some cases crash on assertions should be switched off and assertion should be processed in [LogProcessor]. + * It is useful for example to not crash but log it as handled crash in Crashlitycs in production build. + * + * @param throwable Exception that is describing assertion. + */ + fun assertion(throwable: Throwable) { + logMessage(LcLevel.ASSERT, "Assertion appears at %s", throwable, Lc.getCodePoint(null, 2)) + } + +} diff --git a/logging/src/main/java/ru/touchin/roboswag/core/log/LcLevel.java b/logging/src/main/java/ru/touchin/roboswag/core/log/LcLevel.kt similarity index 57% rename from logging/src/main/java/ru/touchin/roboswag/core/log/LcLevel.java rename to logging/src/main/java/ru/touchin/roboswag/core/log/LcLevel.kt index fb72f33e..7e36b4f6 100644 --- a/logging/src/main/java/ru/touchin/roboswag/core/log/LcLevel.java +++ b/logging/src/main/java/ru/touchin/roboswag/core/log/LcLevel.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) + * Copyright (c) 2019 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) * * This file is part of RoboSwag library. * @@ -17,16 +17,21 @@ * */ -package ru.touchin.roboswag.core.log; +package ru.touchin.roboswag.core.log -import androidx.annotation.NonNull; -import android.util.Log; +import android.util.Log /** - * Created by Gavriil Sitnikov on 14/05/2016. * Level of log message. */ -public enum LcLevel { +enum class LcLevel( + /** + * Standard [Log] integer value of level represents priority of message. + * + * @return Integer level. + */ + val priority: Int +) { VERBOSE(Log.VERBOSE), DEBUG(Log.DEBUG), @@ -35,29 +40,12 @@ public enum LcLevel { ERROR(Log.ERROR), ASSERT(Log.ASSERT); - private final int priority; - - LcLevel(final int priority) { - this.priority = priority; - } - - /** - * Standard {@link Log} integer value of level represents priority of message. - * - * @return Integer level. - */ - public int getPriority() { - return priority; - } - /** * Compares priorities of LcLevels and returns if current is less than another. * - * @param logLevel {@link LcLevel} to compare priority with; + * @param logLevel [LcLevel] to compare priority with; * @return True if current level priority less than level passed as parameter. */ - public boolean lessThan(@NonNull final LcLevel logLevel) { - return this.priority < logLevel.priority; - } + fun lessThan(logLevel: LcLevel): Boolean = this.priority < logLevel.priority } diff --git a/logging/src/main/java/ru/touchin/roboswag/core/log/LogProcessor.java b/logging/src/main/java/ru/touchin/roboswag/core/log/LogProcessor.java deleted file mode 100644 index 0c4c4a84..00000000 --- a/logging/src/main/java/ru/touchin/roboswag/core/log/LogProcessor.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) - * - * This file is part of RoboSwag library. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package ru.touchin.roboswag.core.log; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -/** - * Created by Gavriil Sitnikov on 13/11/2015. - * Abstract object to intercept log messages coming from {@link LcGroup} and {@link Lc} log methods. - */ -public abstract class LogProcessor { - - @NonNull - private final LcLevel minLogLevel; - - public LogProcessor(@NonNull final LcLevel minLogLevel) { - this.minLogLevel = minLogLevel; - } - - /** - * Minimum logging level. - * Any messages with lower priority won't be passed into {@link #processLogMessage(LcGroup, LcLevel, String, String, Throwable)}. - * - * @return Minimum log level represented by {@link LcLevel} object. - */ - @NonNull - public LcLevel getMinLogLevel() { - return minLogLevel; - } - - /** - * Core method to process any incoming log messages from {@link LcGroup} and {@link Lc} with level higher or equals {@link #getMinLogLevel()}. - * - * @param group {@link LcGroup} where log message came from; - * @param level {@link LcLevel} level (priority) of message; - * @param tag String mark of message; - * @param message Message to log; - * @param throwable Exception to log. - */ - public abstract void processLogMessage(@NonNull final LcGroup group, @NonNull final LcLevel level, - @NonNull final String tag, @NonNull final String message, @Nullable final Throwable throwable); - -} diff --git a/logging/src/main/java/ru/touchin/roboswag/core/log/LogProcessor.kt b/logging/src/main/java/ru/touchin/roboswag/core/log/LogProcessor.kt new file mode 100644 index 00000000..0c64ba6a --- /dev/null +++ b/logging/src/main/java/ru/touchin/roboswag/core/log/LogProcessor.kt @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2019 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) + * + * This file is part of RoboSwag library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package ru.touchin.roboswag.core.log + +/** + * Abstract object to intercept log messages coming from [LcGroup] and [Lc] log methods. + */ +abstract class LogProcessor( + /** + * Minimum logging level. + * Any messages with lower priority won't be passed into [.processLogMessage]. + * + * @return Minimum log level represented by [LcLevel] object. + */ + val minLogLevel: LcLevel +) { + + /** + * Core method to process any incoming log messages from [LcGroup] and [Lc] with level higher or equals [.getMinLogLevel]. + * + * @param group [LcGroup] where log message came from; + * @param level [LcLevel] level (priority) of message; + * @param tag String mark of message; + * @param message Message to log; + * @param throwable Exception to log. + */ + abstract fun processLogMessage(group: LcGroup, level: LcLevel, tag: String, message: String, throwable: Throwable?) + +} diff --git a/logging/src/main/java/ru/touchin/roboswag/core/utils/ShouldNotHappenException.java b/logging/src/main/java/ru/touchin/roboswag/core/utils/ShouldNotHappenException.kt similarity index 53% rename from logging/src/main/java/ru/touchin/roboswag/core/utils/ShouldNotHappenException.java rename to logging/src/main/java/ru/touchin/roboswag/core/utils/ShouldNotHappenException.kt index 8bbed443..25ac62fd 100644 --- a/logging/src/main/java/ru/touchin/roboswag/core/utils/ShouldNotHappenException.java +++ b/logging/src/main/java/ru/touchin/roboswag/core/utils/ShouldNotHappenException.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) + * Copyright (c) 2019 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) * * This file is part of RoboSwag library. * @@ -17,33 +17,24 @@ * */ -package ru.touchin.roboswag.core.utils; - -import androidx.annotation.NonNull; +package ru.touchin.roboswag.core.utils /** - * Created by Gavriil Sitnikov on 13/11/2015. * Exception that should be threw when some unexpected code reached. * E.g. if some value null but it is not legal or in default case in switch if all specific cases should be processed. */ -public class ShouldNotHappenException extends RuntimeException { - - private static final long serialVersionUID = 0; +class ShouldNotHappenException : RuntimeException { - public ShouldNotHappenException() { - super(); + companion object { + private const val serialVersionUID: Long = 0 } - public ShouldNotHappenException(@NonNull final String detailMessage) { - super(detailMessage); - } + constructor() : super() - public ShouldNotHappenException(@NonNull final String detailMessage, @NonNull final Throwable throwable) { - super(detailMessage, throwable); - } + constructor(detailMessage: String) : super(detailMessage) - public ShouldNotHappenException(@NonNull final Throwable throwable) { - super(throwable); - } + constructor(detailMessage: String, throwable: Throwable) : super(detailMessage, throwable) + + constructor(throwable: Throwable) : super(throwable) } diff --git a/logging/src/main/java/ru/touchin/roboswag/core/utils/ThreadLocalValue.java b/logging/src/main/java/ru/touchin/roboswag/core/utils/ThreadLocalValue.kt similarity index 61% rename from logging/src/main/java/ru/touchin/roboswag/core/utils/ThreadLocalValue.java rename to logging/src/main/java/ru/touchin/roboswag/core/utils/ThreadLocalValue.kt index 8d3f1b1c..e2aa6ef1 100644 --- a/logging/src/main/java/ru/touchin/roboswag/core/utils/ThreadLocalValue.java +++ b/logging/src/main/java/ru/touchin/roboswag/core/utils/ThreadLocalValue.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) + * Copyright (c) 2019 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) * * This file is part of RoboSwag library. * @@ -17,44 +17,28 @@ * */ -package ru.touchin.roboswag.core.utils; - -import androidx.annotation.NonNull; +package ru.touchin.roboswag.core.utils /** - * Created by Gavriil Sitnikov on 13/11/2015. * Thread local value with specified creator of value per thread. */ -public class ThreadLocalValue extends ThreadLocal { - - @NonNull - private final Fabric fabric; +class ThreadLocalValue(private val fabric: Fabric) : ThreadLocal() { - public ThreadLocalValue(@NonNull final Fabric fabric) { - super(); - this.fabric = fabric; - } - - @NonNull - @Override - protected T initialValue() { - return fabric.create(); - } + override fun initialValue(): T = fabric.create() /** * Fabric of thread-local objects. * * @param Type of objects. */ - public interface Fabric { + interface Fabric { /** * Creates object. * * @return new instance of object. */ - @NonNull - T create(); + fun create(): T }