From a7168a011cc3691c8289dc9a288e79141df9f73a Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Sun, 5 May 2024 20:15:04 +0100 Subject: [PATCH 01/23] Add `CallerFinder` from Logging --- .../java/io/spine/reflect/CallerFinder.java | 119 ++++++++++++++++++ src/main/java/io/spine/reflect/Checks.java | 67 ++++++++++ .../java/io/spine/reflect/StackGetter.java | 67 ++++++++++ .../spine/reflect/StackWalkerStackGetter.java | 91 ++++++++++++++ .../spine/reflect/ThrowableStackGetter.java | 88 +++++++++++++ .../spine/reflect/AbstractStackGetterSpec.kt | 105 ++++++++++++++++ .../io/spine/reflect/CallerFinderSpec.kt | 83 ++++++++++++ .../reflect/StackWalkerStackGetterSpec.kt | 38 ++++++ .../spine/reflect/ThrowableStackGetterSpec.kt | 38 ++++++ 9 files changed, 696 insertions(+) create mode 100644 src/main/java/io/spine/reflect/CallerFinder.java create mode 100644 src/main/java/io/spine/reflect/Checks.java create mode 100644 src/main/java/io/spine/reflect/StackGetter.java create mode 100644 src/main/java/io/spine/reflect/StackWalkerStackGetter.java create mode 100644 src/main/java/io/spine/reflect/ThrowableStackGetter.java create mode 100644 src/test/kotlin/io/spine/reflect/AbstractStackGetterSpec.kt create mode 100644 src/test/kotlin/io/spine/reflect/CallerFinderSpec.kt create mode 100644 src/test/kotlin/io/spine/reflect/StackWalkerStackGetterSpec.kt create mode 100644 src/test/kotlin/io/spine/reflect/ThrowableStackGetterSpec.kt diff --git a/src/main/java/io/spine/reflect/CallerFinder.java b/src/main/java/io/spine/reflect/CallerFinder.java new file mode 100644 index 0000000..a5cd02a --- /dev/null +++ b/src/main/java/io/spine/reflect/CallerFinder.java @@ -0,0 +1,119 @@ +/* + * Copyright 2024, TeamDev. All rights reserved. + * + * 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 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.reflect; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +import static io.spine.reflect.Checks.checkNotNull; + +/** + * A helper class for determining callers of a specified class currently on the stack. + * + * @see + * Original Java code from Google Flogger + */ +public final class CallerFinder { + + private static final StackGetter STACK_GETTER = createBestStackGetter(); + + /** Prevents instantiation of this utility class. */ + private CallerFinder() { + } + + /** + * Returns the first available class implementing the {@link StackGetter} methods. + * The implementation returned is dependent on the current Java version. + */ + private static StackGetter createBestStackGetter() { + try { + return new StackWalkerStackGetter(); + } catch (Throwable ignored) { + // We may not be able to create `StackWalkerStackGetter` sometimes, + // for example, on Android. This is not a problem because we have + // `ThrowableStackGetter` as a fallback option. + return new ThrowableStackGetter(); + } + } + + /** + * Returns the stack trace element of the immediate caller of the specified class. + * + * @param target + * the target class whose callers we are looking for + * @param skip + * the minimum number of calls known to have occurred between the first call to the + * target class and the point at which the specified throwable was created. + * If in doubt, specify zero here to avoid accidentally skipping past the caller. + * This is particularly important for code which might be used in Android, since you + * cannot know whether a tool such as Proguard has merged methods or classes and + * reduced the number of intermediate stack frames. + * @return the stack trace element representing the immediate caller of the specified class, or + * null if no caller was found (due to incorrect target, wrong skip count or + * use of JNI). + */ + @Nullable + public static StackTraceElement findCallerOf(Class target, int skip) { + checkTarget(target); + if (skip < 0) { + throw skipCountCannotBeNegative(skip); + } + return STACK_GETTER.callerOf(target, skip + 1); + } + + /** + * Returns a synthetic stack trace starting at the immediate caller of the specified target. + * + * @param target + * the class who caller the returned stack trace will start at. + * @param maxDepth + * the maximum size of the returned stack (pass -1 for the complete stack). + * @param skip + * the minimum number of stack frames to skip before looking for callers. + * @return a synthetic stack trace starting at the immediate caller of the specified target, or + * the empty array if no caller was found (due to incorrect target, wrong skip count or + * use of JNI). + */ + public static StackTraceElement[] getStackForCallerOf(Class target, int maxDepth, int skip) { + checkTarget(target); + if (maxDepth <= 0 && maxDepth != -1) { + throw new IllegalArgumentException("invalid maximum depth: " + maxDepth); + } + if (skip < 0) { + throw skipCountCannotBeNegative(skip); + } + return STACK_GETTER.getStackForCaller(target, maxDepth, skip + 1); + } + + private static void checkTarget(Class target) { + checkNotNull(target, "target"); + } + + private static @NonNull IllegalArgumentException skipCountCannotBeNegative(int skip) { + return new IllegalArgumentException("skip count cannot be negative: " + skip); + } +} diff --git a/src/main/java/io/spine/reflect/Checks.java b/src/main/java/io/spine/reflect/Checks.java new file mode 100644 index 0000000..5cff8ff --- /dev/null +++ b/src/main/java/io/spine/reflect/Checks.java @@ -0,0 +1,67 @@ +/* + * Copyright 2024, TeamDev. All rights reserved. + * + * 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 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.reflect; + +import com.google.errorprone.annotations.CanIgnoreReturnValue; + +/** + * Local version of the Guava {@code Preconditions} class for simple, often used checks. + * + * @see + * Original Java code of Google Flogger + */ +class Checks { + + private Checks() { + } + + @CanIgnoreReturnValue + @SuppressWarnings({ + "ConstantValue" /* We don't want to make the parameter `value` `@Nullable`. */, + "PMD.AvoidThrowingNullPointerException" /* We throw it here by convention. */ + }) + static T checkNotNull(T value, String name) { + if (value == null) { + throw new NullPointerException(name + " must not be null"); + } + return value; + } + + private static void checkArgument(boolean condition, String message) { + if (!condition) { + throw new IllegalArgumentException(message); + } + } + + static void checkMaxDepth(int maxDepth) { + checkArgument(maxDepth == -1 || maxDepth > 0, "maxDepth must be > 0 or -1"); + } + + static void checkSkipFrames(int skipFrames) { + checkArgument(skipFrames >= 0, "skipFrames must be >= 0"); + } +} diff --git a/src/main/java/io/spine/reflect/StackGetter.java b/src/main/java/io/spine/reflect/StackGetter.java new file mode 100644 index 0000000..234fc04 --- /dev/null +++ b/src/main/java/io/spine/reflect/StackGetter.java @@ -0,0 +1,67 @@ +/* + * Copyright 2024, TeamDev. All rights reserved. + * + * 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 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.reflect; + +/** + * Interface for finding call site information. + * + * @see + * Original Java code of Google Flogger + */ +interface StackGetter { + + /** + * Returns the first caller of a method on the {@code target} class that is *not* a member of + * {@code target} class, by walking back on the stack, or null if the {@code target} class + * cannot be found or is the last element of the stack. + * + * @param target + * the class to find the caller of + * @param skipFrames + * skip this many frames before looking for the caller. + * This can be used for optimization. + */ + StackTraceElement callerOf(Class target, int skipFrames); + + /** + * Returns up to {@code maxDepth} frames of the stack starting at the stack frame that + * is a caller of a method on {@code target} class but is *not* itself a method + * on {@code target} class. + * + * @param target + * the class to get the stack from + * @param maxDepth + * the maximum depth of the stack to return. A value of -1 means to return the + * whole stack + * @param skipFrames + * skip this many stack frames before looking for the target class. + * Used for optimization. + * @throws IllegalArgumentException + * if {@code maxDepth} is 0 or < -1 or {@code skipFrames} is < 0. + */ + StackTraceElement[] getStackForCaller(Class target, int maxDepth, int skipFrames); +} diff --git a/src/main/java/io/spine/reflect/StackWalkerStackGetter.java b/src/main/java/io/spine/reflect/StackWalkerStackGetter.java new file mode 100644 index 0000000..8063dce --- /dev/null +++ b/src/main/java/io/spine/reflect/StackWalkerStackGetter.java @@ -0,0 +1,91 @@ +/* + * Copyright 2024, TeamDev. All rights reserved. + * + * 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 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.reflect; + +import java.lang.StackWalker.StackFrame; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import static io.spine.reflect.Checks.checkMaxDepth; +import static io.spine.reflect.Checks.checkSkipFrames; + +/** + * StackWalker based implementation of the {@link StackGetter} interface. + * + *

Note, that since this is using Java 9 api, it is being compiled separately from the rest of + * the source code. + * + * @see + * Original Java code from Google Flogger + */ +final class StackWalkerStackGetter implements StackGetter { + + private static final StackWalker STACK_WALKER = + StackWalker.getInstance(StackWalker.Option.SHOW_REFLECT_FRAMES); + + StackWalkerStackGetter() { + // Due to b/241269335, we check in constructor whether this implementation + // crashes in runtime, and CallerFinder should catch any Throwable caused. + StackTraceElement unused = callerOf(StackWalkerStackGetter.class, 0); + } + + @Override + public StackTraceElement callerOf(Class target, int skipFrames) { + checkSkipFrames(skipFrames); + return STACK_WALKER.walk( + s -> filterStackTraceAfterTarget(isTargetClass(target), skipFrames, s) + .findFirst() + .orElse(null)); + } + + @Override + public StackTraceElement[] getStackForCaller(Class target, int maxDepth, int skipFrames) { + checkMaxDepth(maxDepth); + checkSkipFrames(skipFrames); + return STACK_WALKER.walk( + s -> filterStackTraceAfterTarget(isTargetClass(target), skipFrames, s) + .limit(maxDepth == -1 ? Long.MAX_VALUE : maxDepth) + .toArray(StackTraceElement[]::new)); + } + + private static Predicate isTargetClass(Class target) { + var name = target.getName(); + return f -> f.getClassName() + .equals(name); + } + + private static Stream filterStackTraceAfterTarget( + Predicate isTargetClass, int skipFrames, Stream s) { + // need to skip + 1 because of the call to the method this method is being called from + return s.skip(skipFrames + 1) + // skip all classes which don't match the name we are looking for + .dropWhile(isTargetClass.negate()) + // then skip all which matches + .dropWhile(isTargetClass) + .map(StackFrame::toStackTraceElement); + } +} diff --git a/src/main/java/io/spine/reflect/ThrowableStackGetter.java b/src/main/java/io/spine/reflect/ThrowableStackGetter.java new file mode 100644 index 0000000..52a676e --- /dev/null +++ b/src/main/java/io/spine/reflect/ThrowableStackGetter.java @@ -0,0 +1,88 @@ +/* + * Copyright 2024, TeamDev. All rights reserved. + * + * 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 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.reflect; + +import javax.annotation.Nullable; + +import static io.spine.reflect.Checks.checkMaxDepth; +import static io.spine.reflect.Checks.checkSkipFrames; + +/** + * Default implementation of {@link StackGetter} using {@link Throwable#getStackTrace}. + * + * @see + * Original Java code of Google Flogger + */ +final class ThrowableStackGetter implements StackGetter { + + private static final StackTraceElement[] EMPTY_STACK_TRACE = new StackTraceElement[0]; + + @Override + @Nullable + public StackTraceElement callerOf(Class target, int skipFrames) { + checkSkipFrames(skipFrames); + var stack = new Throwable().getStackTrace(); + var callerIndex = findCallerIndex(stack, target, skipFrames + 1); + if (callerIndex != -1) { + return stack[callerIndex]; + } + + return null; + } + + @Override + public StackTraceElement[] getStackForCaller(Class target, int maxDepth, int skipFrames) { + checkMaxDepth(maxDepth); + checkSkipFrames(skipFrames); + var stack = new Throwable().getStackTrace(); + var callerIndex = findCallerIndex(stack, target, skipFrames + 1); + if (callerIndex == -1) { + return EMPTY_STACK_TRACE; + } + var elementsToAdd = stack.length - callerIndex; + if (maxDepth > 0 && maxDepth < elementsToAdd) { + elementsToAdd = maxDepth; + } + var stackTrace = new StackTraceElement[elementsToAdd]; + System.arraycopy(stack, callerIndex, stackTrace, 0, elementsToAdd); + return stackTrace; + } + + private static int findCallerIndex(StackTraceElement[] stack, Class target, int skipFrames) { + var foundCaller = false; + var targetClassName = target.getName(); + for (var frameIndex = skipFrames; frameIndex < stack.length; frameIndex++) { + if (stack[frameIndex].getClassName() + .equals(targetClassName)) { + foundCaller = true; + } else if (foundCaller) { + return frameIndex; + } + } + return -1; + } +} diff --git a/src/test/kotlin/io/spine/reflect/AbstractStackGetterSpec.kt b/src/test/kotlin/io/spine/reflect/AbstractStackGetterSpec.kt new file mode 100644 index 0000000..4ead632 --- /dev/null +++ b/src/test/kotlin/io/spine/reflect/AbstractStackGetterSpec.kt @@ -0,0 +1,105 @@ +/* + * Copyright 2024, TeamDev. All rights reserved. + * + * 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 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.reflect + +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe +import org.junit.jupiter.api.Test + +/** + * An abstract base for testing concrete implementations of [StackGetter]. + * + * @see + * Original Java code of Google Flogger + */ +internal abstract class AbstractStackGetterSpec( + private val stackGetter: StackGetter +) { + + @Test + fun `find the stack trace element of the immediate caller of the specified class`() { + // There are 2 internal methods (not including the log method itself) + // in our fake library. + val library = LoggerCode(skipCount = 2, stackGetter) + val code = UserCode(library) + code.invokeUserCode() + library.caller shouldNotBe null + library.caller!!.className shouldBe UserCode::class.java.name + library.caller!!.methodName shouldBe "loggingMethod" + } + + @Test + fun `return 'null' due to wrong skip count`() { + // If the minimum offset exceeds the number of internal methods, the find fails. + val library = LoggerCode(skipCount = 3, stackGetter) + val code = UserCode(library) + code.invokeUserCode() + library.caller shouldBe null + } +} + +/** + * Fake class that emulates some code calling a log method. + */ +internal class UserCode(private val logger: LoggerCode) { + + fun invokeUserCode() { + loggingMethod() + } + + private fun loggingMethod() { + logger.logMethod() + } +} + +/** + * A fake class that emulates the logging library, which eventually + * calls the given [StackGetter], if any, or [CallerFinder]. + */ +internal class LoggerCode( + private val skipCount: Int, + private val stackGetter: StackGetter? = null +) { + + var caller: StackTraceElement? = null + + fun logMethod() { + internalMethodOne() + } + + private fun internalMethodOne() { + internalMethodTwo() + } + + private fun internalMethodTwo() { + caller = if (stackGetter != null) { + stackGetter.callerOf(LoggerCode::class.java, skipCount) + } else { + CallerFinder.findCallerOf(LoggerCode::class.java, skipCount) + } + } +} diff --git a/src/test/kotlin/io/spine/reflect/CallerFinderSpec.kt b/src/test/kotlin/io/spine/reflect/CallerFinderSpec.kt new file mode 100644 index 0000000..d3fcb7f --- /dev/null +++ b/src/test/kotlin/io/spine/reflect/CallerFinderSpec.kt @@ -0,0 +1,83 @@ +/* + * Copyright 2024, TeamDev. All rights reserved. + * + * 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 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.reflect + +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test + +/** + * Tests for [CallerFinder]. + * + * @see + * Original Java code of Google Flogger + */ +@DisplayName("`CallerFinder` should") +internal class CallerFinderSpec { + + /** + * A sanity check if we ever discover a platform where the class name + * in the stack trace does not match [Class.getName] – this is never quite + * guaranteed by the JavaDoc in the JDK but is relied upon during log site analysis. + */ + @Test + fun `use the class name that matches one in the stack trace`() { + // Simple case for a top-level named class. + Throwable().stackTrace[0].className shouldBe CallerFinderSpec::class.java.name + + // Anonymous inner class. + val obj = object { + override fun toString(): String { + return Throwable().stackTrace[0].className + } + } + + "$obj" shouldBe obj::class.java.name + } + + @Test + fun `find the stack trace element of the immediate caller of the specified class`() { + // There are 2 internal methods (not including the log method itself) + // in our fake library. + val library = LoggerCode(skipCount = 2) + val code = UserCode(library) + code.invokeUserCode() + library.caller shouldNotBe null + library.caller!!.className shouldBe UserCode::class.java.name + library.caller!!.methodName shouldBe "loggingMethod" + } + + @Test + fun `return 'null' due to wrong skip count`() { + // If the minimum offset exceeds the number of internal methods, the find fails. + val library = LoggerCode(skipCount = 3) + val code = UserCode(library) + code.invokeUserCode() + library.caller shouldBe null + } +} diff --git a/src/test/kotlin/io/spine/reflect/StackWalkerStackGetterSpec.kt b/src/test/kotlin/io/spine/reflect/StackWalkerStackGetterSpec.kt new file mode 100644 index 0000000..f747ced --- /dev/null +++ b/src/test/kotlin/io/spine/reflect/StackWalkerStackGetterSpec.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2024, TeamDev. All rights reserved. + * + * 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 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.reflect + +import org.junit.jupiter.api.DisplayName + +/** + * Tests for [StackWalkerStackGetter]. + * + * @see + * Original Java code of Google Flogger + */ +@DisplayName("`StackWalkerStackGetter` should") +internal class StackWalkerStackGetterSpec : AbstractStackGetterSpec(StackWalkerStackGetter()) diff --git a/src/test/kotlin/io/spine/reflect/ThrowableStackGetterSpec.kt b/src/test/kotlin/io/spine/reflect/ThrowableStackGetterSpec.kt new file mode 100644 index 0000000..dee575b --- /dev/null +++ b/src/test/kotlin/io/spine/reflect/ThrowableStackGetterSpec.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2024, TeamDev. All rights reserved. + * + * 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 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.reflect + +import org.junit.jupiter.api.DisplayName + +/** + * Tests for [ThrowableStackGetter]. + * + * @see + * Original Java code of Google Flogger + */ +@DisplayName("`ThrowableStackGetter` should") +internal class ThrowableStackGetterSpec : AbstractStackGetterSpec(ThrowableStackGetter()) From 100094974918e1129b9eb770cbb3c918dde24f33 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Sun, 5 May 2024 20:15:28 +0100 Subject: [PATCH 02/23] Auto-updated by IDEA --- .idea/kotlinc.xml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index d049e06..374970d 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -3,11 +3,7 @@ - - - \ No newline at end of file From 3f110aafb0e5a5fc4eb29d98c5aca834a3ad57f1 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Sun, 5 May 2024 20:15:43 +0100 Subject: [PATCH 03/23] Bump version -> `2.0.0-SNAPSHOT.184` --- dependencies.md | 4 ++-- pom.xml | 2 +- version.gradle.kts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dependencies.md b/dependencies.md index 7fec6a2..025d9ab 100644 --- a/dependencies.md +++ b/dependencies.md @@ -1,6 +1,6 @@ -# Dependencies of `io.spine:spine-reflect:2.0.0-SNAPSHOT.183` +# Dependencies of `io.spine:spine-reflect:2.0.0-SNAPSHOT.184` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -756,4 +756,4 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Sat Nov 25 11:39:44 WET 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). \ No newline at end of file +This report was generated on **Sun May 05 20:14:15 WEST 2024** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). \ No newline at end of file diff --git a/pom.xml b/pom.xml index e67fcb0..bbac9d0 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ all modules and does not describe the project structure per-subproject. --> io.spine reflect -2.0.0-SNAPSHOT.183 +2.0.0-SNAPSHOT.184 2015 diff --git a/version.gradle.kts b/version.gradle.kts index 52f72cb..3bbeef3 100644 --- a/version.gradle.kts +++ b/version.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright 2023, TeamDev. All rights reserved. + * Copyright 2024, TeamDev. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,4 +24,4 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -val versionToPublish: String by extra("2.0.0-SNAPSHOT.183") +val versionToPublish: String by extra("2.0.0-SNAPSHOT.184") From 8abe9b1ece24aad92cf53a78692ab764d0b00045 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Sun, 5 May 2024 20:17:28 +0100 Subject: [PATCH 04/23] Update `config` --- .github/workflows/build-on-ubuntu.yml | 2 +- .github/workflows/build-on-windows.yml | 5 +- .idea/kotlinc.xml | 8 +- .idea/misc.xml | 3 +- buildSrc/build.gradle.kts | 101 +++++++++++------- buildSrc/src/main/kotlin/DokkaExts.kt | 34 +++++- .../src/main/kotlin/dokka-for-java.gradle.kts | 5 +- .../main/kotlin/dokka-for-kotlin.gradle.kts | 5 +- .../spine/internal/dependency/Coroutines.kt | 41 +++++++ .../spine/internal/dependency/GrpcKotlin.kt | 43 ++++++++ .../io/spine/internal/dependency/IntelliJ.kt | 89 +++++++++++++++ .../io/spine/internal/dependency/Jackson.kt | 7 ++ .../io/spine/internal/dependency/Kotlin.kt | 4 +- .../io/spine/internal/dependency/KotlinX.kt | 41 +++++++ .../io/spine/internal/dependency/ProtoData.kt | 53 +++++++-- .../io/spine/internal/dependency/Protobuf.kt | 2 +- .../io/spine/internal/dependency/Spine.kt | 27 ++--- .../spine/internal/dependency/Validation.kt | 35 ++++-- .../io/spine/internal/gradle/Repositories.kt | 10 ++ .../gradle/protobuf/ProtoTaskExtensions.kt | 49 ++++++++- .../internal/gradle/publish/Publications.kt | 30 ++++-- .../gradle/publish/SpinePublishing.kt | 14 +-- .../io/spine/internal/gradle/testing/Tasks.kt | 12 +-- .../src/main/kotlin/jvm-module.gradle.kts | 4 +- .../src/main/kotlin/write-manifest.gradle.kts | 2 +- config | 2 +- dependencies.md | 51 ++++++--- gradle/wrapper/gradle-wrapper.properties | 2 +- pom.xml | 14 ++- 29 files changed, 560 insertions(+), 135 deletions(-) create mode 100644 buildSrc/src/main/kotlin/io/spine/internal/dependency/Coroutines.kt create mode 100644 buildSrc/src/main/kotlin/io/spine/internal/dependency/GrpcKotlin.kt create mode 100644 buildSrc/src/main/kotlin/io/spine/internal/dependency/IntelliJ.kt create mode 100644 buildSrc/src/main/kotlin/io/spine/internal/dependency/KotlinX.kt diff --git a/.github/workflows/build-on-ubuntu.yml b/.github/workflows/build-on-ubuntu.yml index 788b891..55bb482 100644 --- a/.github/workflows/build-on-ubuntu.yml +++ b/.github/workflows/build-on-ubuntu.yml @@ -24,7 +24,7 @@ jobs: # See: https://github.com/marketplace/actions/junit-report-action - name: Publish Test Report - uses: mikepenz/action-junit-report@v3.7.6 + uses: mikepenz/action-junit-report@v4.0.3 if: always() # always run even if the previous step fails with: report_paths: '**/build/test-results/**/TEST-*.xml' diff --git a/.github/workflows/build-on-windows.yml b/.github/workflows/build-on-windows.yml index c910b20..ff947a6 100644 --- a/.github/workflows/build-on-windows.yml +++ b/.github/workflows/build-on-windows.yml @@ -18,8 +18,9 @@ jobs: distribution: zulu cache: gradle + # See: https://github.com/al-cheb/configure-pagefile-action - name: Configure Pagefile - uses: al-cheb/configure-pagefile-action@v1.2 + uses: al-cheb/configure-pagefile-action@v1.3 - name: Build project and run tests shell: cmd @@ -28,7 +29,7 @@ jobs: # See: https://github.com/marketplace/actions/junit-report-action - name: Publish Test Report - uses: mikepenz/action-junit-report@v3.7.6 + uses: mikepenz/action-junit-report@v4.0.3 if: always() # always run even if the previous step fails with: report_paths: '**/build/test-results/**/TEST-*.xml' diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index 374970d..9bfa224 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -3,7 +3,11 @@ + + - - \ No newline at end of file + diff --git a/.idea/misc.xml b/.idea/misc.xml index 3a87034..a3bc764 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,13 +1,14 @@ - + + diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 9df492c..d5e76bf 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright 2023, TeamDev. All rights reserved. + * Copyright 2024, TeamDev. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -169,49 +169,74 @@ tasks.withType { } dependencies { - implementation("com.fasterxml.jackson.core:jackson-databind:$jacksonVersion") - implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-xml:$jacksonVersion") - - @Suppress( - "VulnerableLibrariesLocal", "RedundantSuppression" /* - `artifactregistry-auth-common` has transitive dependency on Gson and Apache `commons-codec`. - - Gson from version `2.8.6` until `2.8.9` is vulnerable to Deserialization of Untrusted Data - (https://devhub.checkmarx.com/cve-details/CVE-2022-25647/). + api("com.github.jk1:gradle-license-report:$licenseReportVersion") + dependOnAuthCommon() + + listOf( + "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion", + "com.fasterxml.jackson.dataformat:jackson-dataformat-xml:$jacksonVersion", + "com.github.jk1:gradle-license-report:$licenseReportVersion", + "com.google.guava:guava:$guavaVersion", + "com.google.protobuf:protobuf-gradle-plugin:$protobufPluginVersion", + "gradle.plugin.com.github.johnrengelman:shadow:${shadowVersion}", + "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:$detektVersion", + "io.kotest:kotest-gradle-plugin:$kotestJvmPluginVersion", + // https://github.com/srikanth-lingala/zip4j + "net.lingala.zip4j:zip4j:2.10.0", + "net.ltgt.gradle:gradle-errorprone-plugin:${errorPronePluginVersion}", + "org.ajoberstar.grgit:grgit-core:${grGitVersion}", + "org.jetbrains.dokka:dokka-base:${dokkaVersion}", + "org.jetbrains.dokka:dokka-gradle-plugin:${dokkaVersion}", + "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion", + "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion", + "org.jetbrains.kotlinx:kover-gradle-plugin:$koverVersion" + ).forEach { + implementation(it) + } +} - Apache `commons-codec` before 1.13 is vulnerable to information exposure - (https://devhub.checkmarx.com/cve-details/Cxeb68d52e-5509/). +dependOnBuildSrcJar() - We use Gson `2.10.1`and we force it in `forceProductionDependencies()`. - We use `commons-code` with version `1.16.0`, forcing it in `forceProductionDependencies()`. +/** + * Adds a dependency on a `buildSrc.jar`, iff: + * 1) the `src` folder is missing, and + * 2) `buildSrc.jar` is present in `buildSrc/` folder instead. + * + * This approach is used in the scope of integration testing. + */ +fun Project.dependOnBuildSrcJar() { + val srcFolder = this.rootDir.resolve("src") + val buildSrcJar = rootDir.resolve("buildSrc.jar") + if (!srcFolder.exists() && buildSrcJar.exists()) { + logger.info("Adding the pre-compiled 'buildSrc.jar' to 'implementation' dependencies.") + dependencies { + implementation(files("buildSrc.jar")) + } + } +} - So, we should be safe with the current version `artifactregistry-auth-common` until - we migrate to a later version. */ - ) +/** + * Includes the `implementation` dependency on `artifactregistry-auth-common`, + * with the version defined in [googleAuthToolVersion]. + * + * `artifactregistry-auth-common` has transitive dependency on Gson and Apache `commons-codec`. + * Gson from version `2.8.6` until `2.8.9` is vulnerable to Deserialization of Untrusted Data + * (https://devhub.checkmarx.com/cve-details/CVE-2022-25647/). + * + * Apache `commons-codec` before 1.13 is vulnerable to information exposure + * (https://devhub.checkmarx.com/cve-details/Cxeb68d52e-5509/). + * + * We use Gson `2.10.1` and we force it in `forceProductionDependencies()`. + * We use `commons-code` with version `1.16.0`, forcing it in `forceProductionDependencies()`. + * + * So, we should be safe with the current version `artifactregistry-auth-common` until + * we migrate to a later version. + */ +fun DependencyHandlerScope.dependOnAuthCommon() { + @Suppress("VulnerableLibrariesLocal", "RedundantSuppression") implementation( "com.google.cloud.artifactregistry:artifactregistry-auth-common:$googleAuthToolVersion" ) { exclude(group = "com.google.guava") } - - implementation("com.google.guava:guava:$guavaVersion") - api("com.github.jk1:gradle-license-report:$licenseReportVersion") - implementation("org.ajoberstar.grgit:grgit-core:${grGitVersion}") - implementation("net.ltgt.gradle:gradle-errorprone-plugin:${errorPronePluginVersion}") - - // Add explicit dependency to avoid warning on different Kotlin runtime versions. - implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion") - implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") - - implementation("io.gitlab.arturbosch.detekt:detekt-gradle-plugin:$detektVersion") - implementation("com.google.protobuf:protobuf-gradle-plugin:$protobufPluginVersion") - implementation("org.jetbrains.dokka:dokka-gradle-plugin:${dokkaVersion}") - implementation("org.jetbrains.dokka:dokka-base:${dokkaVersion}") - implementation("gradle.plugin.com.github.johnrengelman:shadow:${shadowVersion}") - - // https://github.com/srikanth-lingala/zip4j - implementation("net.lingala.zip4j:zip4j:2.10.0") - - implementation("io.kotest:kotest-gradle-plugin:$kotestJvmPluginVersion") - implementation("org.jetbrains.kotlinx:kover-gradle-plugin:$koverVersion") } diff --git a/buildSrc/src/main/kotlin/DokkaExts.kt b/buildSrc/src/main/kotlin/DokkaExts.kt index 9756dc3..feb9eb0 100644 --- a/buildSrc/src/main/kotlin/DokkaExts.kt +++ b/buildSrc/src/main/kotlin/DokkaExts.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023, TeamDev. All rights reserved. + * Copyright 2024, TeamDev. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -143,6 +143,7 @@ fun TaskContainer.dokkaHtmlTask(): DokkaTask? = this.findByName("dokkaHtml") as * Dokka can properly generate documentation for either Kotlin or Java depending on * the configuration, but not both. */ +@Suppress("unused") internal fun GradleDokkaSourceSetBuilder.onlyJavaSources(): FileCollection { return sourceRoots.filter(File::isJavaSourceDirectory) } @@ -167,6 +168,19 @@ fun Project.dokkaKotlinJar(): TaskProvider = tasks.getOrCreate("dokkaKotlin } } +/** + * Tells if this task belongs to the execution graph which contains publishing tasks. + * + * The task `"publishToMavenLocal"` is excluded from the check because it is a part of + * the local testing workflow. + */ +fun DokkaTask.isInPublishingGraph(): Boolean = + project.gradle.taskGraph.allTasks.any { + with(it.name) { + startsWith("publish") && !startsWith("publishToMavenLocal") + } + } + /** * Locates or creates `dokkaJavaJar` task in this [Project]. * @@ -182,3 +196,21 @@ fun Project.dokkaJavaJar(): TaskProvider = tasks.getOrCreate("dokkaJavaJar" this@getOrCreate.dependsOn(dokkaTask) } } + +/** + * Disables Dokka and Javadoc tasks in this `Project`. + * + * This function could be useful to improve build speed when building subprojects containing + * test environments or integration test projects. + */ +@Suppress("unused") +fun Project.disableDocumentationTasks() { + gradle.taskGraph.whenReady { + tasks.forEach { task -> + val lowercaseName = task.name.toLowerCase() + if (lowercaseName.contains("dokka") || lowercaseName.contains("javadoc")) { + task.enabled = false + } + } + } +} diff --git a/buildSrc/src/main/kotlin/dokka-for-java.gradle.kts b/buildSrc/src/main/kotlin/dokka-for-java.gradle.kts index 0e5087a..49ac6d3 100644 --- a/buildSrc/src/main/kotlin/dokka-for-java.gradle.kts +++ b/buildSrc/src/main/kotlin/dokka-for-java.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright 2023, TeamDev. All rights reserved. + * Copyright 2024, TeamDev. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,4 +37,7 @@ dependencies { tasks.withType().configureEach { configureForJava() + onlyIf { + (it as DokkaTask).isInPublishingGraph() + } } diff --git a/buildSrc/src/main/kotlin/dokka-for-kotlin.gradle.kts b/buildSrc/src/main/kotlin/dokka-for-kotlin.gradle.kts index 0b12a0d..9ed226e 100644 --- a/buildSrc/src/main/kotlin/dokka-for-kotlin.gradle.kts +++ b/buildSrc/src/main/kotlin/dokka-for-kotlin.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright 2023, TeamDev. All rights reserved. + * Copyright 2024, TeamDev. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,4 +36,7 @@ dependencies { tasks.withType().configureEach { configureForKotlin() + onlyIf { + (it as DokkaTask).isInPublishingGraph() + } } diff --git a/buildSrc/src/main/kotlin/io/spine/internal/dependency/Coroutines.kt b/buildSrc/src/main/kotlin/io/spine/internal/dependency/Coroutines.kt new file mode 100644 index 0000000..3074c21 --- /dev/null +++ b/buildSrc/src/main/kotlin/io/spine/internal/dependency/Coroutines.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2024, TeamDev. All rights reserved. + * + * 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 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.internal.dependency + +/** + * Kotlin Coroutines. + * + * @see GitHub projecet + */ +@Suppress("unused") +object Coroutines { + const val version = "1.6.4" + const val jdk8 = "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:$version" + const val core = "org.jetbrains.kotlinx:kotlinx-coroutines-core:$version" + const val bom = "org.jetbrains.kotlinx:kotlinx-coroutines-bom:$version" + const val coreJvm = "org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:$version" +} diff --git a/buildSrc/src/main/kotlin/io/spine/internal/dependency/GrpcKotlin.kt b/buildSrc/src/main/kotlin/io/spine/internal/dependency/GrpcKotlin.kt new file mode 100644 index 0000000..a2fb7ef --- /dev/null +++ b/buildSrc/src/main/kotlin/io/spine/internal/dependency/GrpcKotlin.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2024, TeamDev. All rights reserved. + * + * 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 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.internal.dependency + +/** + * gRPC-Kotlin/JVM. + * + * @see GitHub project + */ +@Suppress("unused") +object GrpcKotlin { + const val version = "1.3.0" + const val stub = "io.grpc:grpc-kotlin-stub:$version" + + object ProtocPlugin { + const val id = "grpckt" + const val artifact = "io.grpc:protoc-gen-grpc-kotlin:$version:jdk8@jar" + } +} diff --git a/buildSrc/src/main/kotlin/io/spine/internal/dependency/IntelliJ.kt b/buildSrc/src/main/kotlin/io/spine/internal/dependency/IntelliJ.kt new file mode 100644 index 0000000..1062c9b --- /dev/null +++ b/buildSrc/src/main/kotlin/io/spine/internal/dependency/IntelliJ.kt @@ -0,0 +1,89 @@ +/* + * Copyright 2023, TeamDev. All rights reserved. + * + * 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 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +@file:Suppress("ConstPropertyName") + +package io.spine.internal.dependency + +/** + * The components of the IntelliJ Platform. + * + * Make sure to add the `intellijReleases` and `jetBrainsCacheRedirector` + * repositories to your project. See `kotlin/Repositories.kt` for details. + */ +@Suppress("unused") +object IntelliJ { + + /** + * The version of the IntelliJ platform. + * + * This is the version used by Kotlin compiler `1.9.21`. + * Advance this version with caution because it may break the setup of + * IntelliJ platform standalone execution. + */ + const val version = "213.7172.53" + + object Platform { + private const val group = "com.jetbrains.intellij.platform" + const val core = "$group:core:$version" + const val util = "$group:util:$version" + const val coreImpl = "$group:core-impl:$version" + const val codeStyle = "$group:code-style:$version" + const val codeStyleImpl = "$group:code-style-impl:$version" + const val projectModel = "$group:project-model:$version" + const val projectModelImpl = "$group:project-model-impl:$version" + const val lang = "$group:lang:$version" + const val langImpl = "$group:lang-impl:$version" + const val ideImpl = "$group:ide-impl:$version" + const val ideCoreImpl = "$group:ide-core-impl:$version" + const val analysisImpl = "$group:analysis-impl:$version" + const val indexingImpl = "$group:indexing-impl:$version" + } + + object Jsp { + private const val group = "com.jetbrains.intellij.jsp" + @Suppress("MemberNameEqualsClassName") + const val jsp = "$group:jsp:$version" + } + + object Xml { + private const val group = "com.jetbrains.intellij.xml" + const val xmlPsiImpl = "$group:xml-psi-impl:$version" + } + + object JavaPsi { + private const val group = "com.jetbrains.intellij.java" + const val api = "$group:java-psi:$version" + const val impl = "$group:java-psi-impl:$version" + } + + object Java { + private const val group = "com.jetbrains.intellij.java" + @Suppress("MemberNameEqualsClassName") + const val java = "$group:java:$version" + const val impl = "$group:java-impl:$version" + } +} diff --git a/buildSrc/src/main/kotlin/io/spine/internal/dependency/Jackson.kt b/buildSrc/src/main/kotlin/io/spine/internal/dependency/Jackson.kt index 8bfaa5c..1ef088c 100644 --- a/buildSrc/src/main/kotlin/io/spine/internal/dependency/Jackson.kt +++ b/buildSrc/src/main/kotlin/io/spine/internal/dependency/Jackson.kt @@ -53,4 +53,11 @@ object Jackson { // https://github.com/FasterXML/jackson-bom const val bom = "com.fasterxml.jackson:jackson-bom:${version}" + + // https://github.com/FasterXML/jackson-jr + object Junior { + const val version = Jackson.version + const val group = "com.fasterxml.jackson.jr" + const val objects = "$group:jackson-jr-objects:$version" + } } diff --git a/buildSrc/src/main/kotlin/io/spine/internal/dependency/Kotlin.kt b/buildSrc/src/main/kotlin/io/spine/internal/dependency/Kotlin.kt index 320403b..ec7f338 100644 --- a/buildSrc/src/main/kotlin/io/spine/internal/dependency/Kotlin.kt +++ b/buildSrc/src/main/kotlin/io/spine/internal/dependency/Kotlin.kt @@ -34,8 +34,8 @@ object Kotlin { /** * When changing the version, also change the version used in the `buildSrc/build.gradle.kts`. */ - @Suppress("MemberVisibilityCanBePrivate") // used directly from outside - const val version = "1.9.20" + @Suppress("MemberVisibilityCanBePrivate") // used directly from the outside. + const val version = "1.9.23" /** * The version of the JetBrains annotations library, which is a transitive diff --git a/buildSrc/src/main/kotlin/io/spine/internal/dependency/KotlinX.kt b/buildSrc/src/main/kotlin/io/spine/internal/dependency/KotlinX.kt new file mode 100644 index 0000000..f10eb45 --- /dev/null +++ b/buildSrc/src/main/kotlin/io/spine/internal/dependency/KotlinX.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2023, TeamDev. All rights reserved. + * + * 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 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.internal.dependency + +@Suppress("unused", "ConstPropertyName") +object KotlinX { + + const val group = "org.jetbrains.kotlinx" + + object Coroutines { + + // https://github.com/Kotlin/kotlinx.coroutines + const val version = "1.7.3" + const val core = "$group:kotlinx-coroutines-core:$version" + const val jdk8 = "$group:kotlinx-coroutines-jdk8:$version" + } +} diff --git a/buildSrc/src/main/kotlin/io/spine/internal/dependency/ProtoData.kt b/buildSrc/src/main/kotlin/io/spine/internal/dependency/ProtoData.kt index 20a52d6..b127c2f 100644 --- a/buildSrc/src/main/kotlin/io/spine/internal/dependency/ProtoData.kt +++ b/buildSrc/src/main/kotlin/io/spine/internal/dependency/ProtoData.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023, TeamDev. All rights reserved. + * Copyright 2024, TeamDev. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,7 +29,7 @@ package io.spine.internal.dependency /** * Dependencies on ProtoData modules. * - * In order to use locally published ProtoData version instead of the version from a public plugin + * To use a locally published ProtoData version instead of the version from a public plugin * registry, set the `PROTODATA_VERSION` and/or the `PROTODATA_DF_VERSION` environment variables * and stop the Gradle daemons so that Gradle observes the env change: * ``` @@ -40,7 +40,7 @@ package io.spine.internal.dependency * ./gradle build # Conduct the intended checks. * ``` * - * Then, in order to reset the console to run the usual versions again, remove the values of + * Then, to reset the console to run the usual versions again, remove the values of * the environment variables and stop the daemon: * ``` * export PROTODATA_VERSION="" @@ -65,7 +65,7 @@ object ProtoData { * The version of ProtoData dependencies. */ val version: String - private const val fallbackVersion = "0.14.2" + private const val fallbackVersion = "0.20.7" /** * The distinct version of ProtoData used by other build tools. @@ -74,19 +74,54 @@ object ProtoData { * transitional dependencies, this is the version used to build the project itself. */ val dogfoodingVersion: String - private const val fallbackDfVersion = "0.14.2" + private const val fallbackDfVersion = "0.20.7" /** * The artifact for the ProtoData Gradle plugin. */ val pluginLib: String + fun pluginLib(version: String): String = + "$group:gradle-plugin:$version" + + fun api(version: String): String = + "$group:protodata-api:$version" + val api - get() = "$group:protodata-api:$version" + get() = api(version) + + @Deprecated("Use `backend` instead", ReplaceWith("backend")) val compiler - get() = "$group:protodata-compiler:$version" + get() = backend + + val backend + get() = "$group:protodata-backend:$version" + + val protocPlugin + get() = "$group:protodata-protoc:$version" + + val gradleApi + get() = "$group:protodata-gradle-api:$version" + + val cliApi + get() = "$group:protodata-cli-api:$version" + + @Deprecated("Use `java()` instead", ReplaceWith("java(version)")) + fun codegenJava(version: String): String = + java(version) + + fun java(version: String): String = + "$group:protodata-java:$version" + + @Deprecated("Use `java` instead.", ReplaceWith("java")) val codegenJava - get() = "$group:protodata-codegen-java:$version" + get() = java(version) + + val java + get() = java(version) + + val fatCli + get() = "$group:protodata-fat-cli:$version" /** * An env variable storing a custom [version]. @@ -113,7 +148,7 @@ object ProtoData { version = experimentVersion ?: fallbackVersion dogfoodingVersion = experimentDfVersion ?: fallbackDfVersion - pluginLib = "${group}:gradle-plugin:$version" + pluginLib = pluginLib(version) println(""" ❗ Running an experiment with ProtoData. ❗ diff --git a/buildSrc/src/main/kotlin/io/spine/internal/dependency/Protobuf.kt b/buildSrc/src/main/kotlin/io/spine/internal/dependency/Protobuf.kt index 32c05ec..be80d2d 100644 --- a/buildSrc/src/main/kotlin/io/spine/internal/dependency/Protobuf.kt +++ b/buildSrc/src/main/kotlin/io/spine/internal/dependency/Protobuf.kt @@ -33,7 +33,7 @@ package io.spine.internal.dependency ) object Protobuf { private const val group = "com.google.protobuf" - const val version = "3.25.0" + const val version = "3.25.1" /** * The Java library containing proto definitions of Google Protobuf. */ diff --git a/buildSrc/src/main/kotlin/io/spine/internal/dependency/Spine.kt b/buildSrc/src/main/kotlin/io/spine/internal/dependency/Spine.kt index 5ae9e95..e4093e7 100644 --- a/buildSrc/src/main/kotlin/io/spine/internal/dependency/Spine.kt +++ b/buildSrc/src/main/kotlin/io/spine/internal/dependency/Spine.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023, TeamDev. All rights reserved. + * Copyright 2024, TeamDev. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,17 +45,17 @@ object Spine { * * @see spine-base */ - const val base = "2.0.0-SNAPSHOT.192" + const val base = "2.0.0-SNAPSHOT.199" /** * The version of [Spine.reflect]. * * @see spine-reflect */ - const val reflect = "2.0.0-SNAPSHOT.182" + const val reflect = "2.0.0-SNAPSHOT.183" /** - * The version of [Spine.logging]. + * The version of [Spine.Logging]. * * @see spine-logging */ @@ -75,7 +75,7 @@ object Spine { * @see [Spine.CoreJava.server] * @see core-java */ - const val core = "2.0.0-SNAPSHOT.173" + const val core = "2.0.0-SNAPSHOT.176" /** * The version of [Spine.modelCompiler]. @@ -89,14 +89,14 @@ object Spine { * * @see spine-mc-java */ - const val mcJava = "2.0.0-SNAPSHOT.172" + const val mcJava = "2.0.0-SNAPSHOT.205" /** * The version of [Spine.baseTypes]. * * @see spine-base-types */ - const val baseTypes = "2.0.0-SNAPSHOT.125" + const val baseTypes = "2.0.0-SNAPSHOT.126" /** * The version of [Spine.time]. @@ -117,14 +117,14 @@ object Spine { * * @see spine-text */ - const val text = "2.0.0-SNAPSHOT.5" + const val text = "2.0.0-SNAPSHOT.6" /** * The version of [Spine.toolBase]. * * @see spine-tool-base */ - const val toolBase = "2.0.0-SNAPSHOT.186" + const val toolBase = "2.0.0-SNAPSHOT.208" /** * The version of [Spine.javadocTools]. @@ -136,13 +136,6 @@ object Spine { const val base = "$group:spine-base:${ArtifactVersion.base}" - @Deprecated("Use `Logging.lib` instead.", ReplaceWith("Logging.lib")) - const val logging = "$group:spine-logging:${ArtifactVersion.logging}" - @Deprecated("Use `Logging.context` instead.", ReplaceWith("Logging.context")) - const val loggingContext = "$group:spine-logging-context:${ArtifactVersion.logging}" - @Deprecated("Use `Logging.backend` instead.", ReplaceWith("Logging.backend")) - const val loggingBackend = "$group:spine-logging-backend:${ArtifactVersion.logging}" - const val reflect = "$group:spine-reflect:${ArtifactVersion.reflect}" const val baseTypes = "$group:spine-base-types:${ArtifactVersion.baseTypes}" const val time = "$group:spine-time:${ArtifactVersion.time}" @@ -151,6 +144,8 @@ object Spine { const val testlib = "$toolsGroup:spine-testlib:${ArtifactVersion.testlib}" const val testUtilTime = "$toolsGroup:spine-testutil-time:${ArtifactVersion.time}" + const val psiJava = "$toolsGroup:spine-psi-java:${ArtifactVersion.toolBase}" + const val psiJavaBundle = "$toolsGroup:spine-psi-java-bundle:${ArtifactVersion.toolBase}" const val toolBase = "$toolsGroup:spine-tool-base:${ArtifactVersion.toolBase}" const val pluginBase = "$toolsGroup:spine-plugin-base:${ArtifactVersion.toolBase}" const val pluginTestlib = "$toolsGroup:spine-plugin-testlib:${ArtifactVersion.toolBase}" diff --git a/buildSrc/src/main/kotlin/io/spine/internal/dependency/Validation.kt b/buildSrc/src/main/kotlin/io/spine/internal/dependency/Validation.kt index df8847b..ded15d8 100644 --- a/buildSrc/src/main/kotlin/io/spine/internal/dependency/Validation.kt +++ b/buildSrc/src/main/kotlin/io/spine/internal/dependency/Validation.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023, TeamDev. All rights reserved. + * Copyright 2024, TeamDev. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,11 +33,32 @@ package io.spine.internal.dependency */ @Suppress("unused", "ConstPropertyName") object Validation { - const val version = "2.0.0-SNAPSHOT.110" + /** + * The version of the Validation library artifacts. + */ + const val version = "2.0.0-SNAPSHOT.132" + + /** + * The distinct version of the Validation library used by build tools during + * the transition from a previous version when breaking API changes are introduced. + * + * When Validation is used both for building the project and as a part of the project's + * transitional dependencies, this is the version used to build the project itself to + * avoid errors caused by incompatible API changes. + */ + const val dogfoodingVersion = "2.0.0-SNAPSHOT.132" + const val group = "io.spine.validation" - const val runtime = "$group:spine-validation-java-runtime:$version" - const val java = "$group:spine-validation-java:$version" - const val javaBundle = "$group:spine-validation-java-bundle:$version" - const val model = "$group:spine-validation-model:$version" - const val config = "$group:spine-validation-configuration:$version" + private const val prefix = "spine-validation" + + const val runtime = "$group:$prefix-java-runtime:$version" + const val java = "$group:$prefix-java:$version" + + /** Obtains the artifact for the `java-bundle` artifact of the given version. */ + fun javaBundle(version: String) = "$group:$prefix-java-bundle:$version" + + val javaBundle = javaBundle(version) + + const val model = "$group:$prefix-model:$version" + const val config = "$group:$prefix-configuration:$version" } diff --git a/buildSrc/src/main/kotlin/io/spine/internal/gradle/Repositories.kt b/buildSrc/src/main/kotlin/io/spine/internal/gradle/Repositories.kt index 8c5015e..6d04ae7 100644 --- a/buildSrc/src/main/kotlin/io/spine/internal/gradle/Repositories.kt +++ b/buildSrc/src/main/kotlin/io/spine/internal/gradle/Repositories.kt @@ -38,6 +38,7 @@ import org.gradle.api.Project import org.gradle.api.artifacts.dsl.RepositoryHandler import org.gradle.api.artifacts.repositories.MavenArtifactRepository import org.gradle.kotlin.dsl.ScriptHandlerScope +import org.gradle.kotlin.dsl.maven /** * Applies [standard][doApplyStandard] repositories to this [ScriptHandlerScope] @@ -214,6 +215,12 @@ fun RepositoryHandler.spineArtifacts(): MavenArtifactRepository = maven { } } +val RepositoryHandler.intellijReleases: MavenArtifactRepository + get() = maven("https://www.jetbrains.com/intellij-repository/releases") + +val RepositoryHandler.jetBrainsCacheRedirector: MavenArtifactRepository + get() = maven("https://cache-redirector.jetbrains.com/intellij-dependencies") + /** * Applies repositories commonly used by Spine Event Engine projects. */ @@ -236,6 +243,9 @@ fun RepositoryHandler.standardToSpineSdk() { } } + intellijReleases + jetBrainsCacheRedirector + maven { url = URI(Repos.sonatypeSnapshots) } diff --git a/buildSrc/src/main/kotlin/io/spine/internal/gradle/protobuf/ProtoTaskExtensions.kt b/buildSrc/src/main/kotlin/io/spine/internal/gradle/protobuf/ProtoTaskExtensions.kt index a3bc6dc..0c4c8b4 100644 --- a/buildSrc/src/main/kotlin/io/spine/internal/gradle/protobuf/ProtoTaskExtensions.kt +++ b/buildSrc/src/main/kotlin/io/spine/internal/gradle/protobuf/ProtoTaskExtensions.kt @@ -29,6 +29,8 @@ package io.spine.internal.gradle.protobuf import com.google.protobuf.gradle.GenerateProtoTask import io.spine.internal.gradle.sourceSets import java.io.File +import java.nio.file.Files +import java.nio.file.StandardOpenOption.TRUNCATE_EXISTING import org.gradle.api.Project import org.gradle.api.file.SourceDirectorySet import org.gradle.api.tasks.SourceSet @@ -112,23 +114,68 @@ fun GenerateProtoTask.setup() { /** * Tell `protoc` to generate descriptor set files under the project build dir. + * + * The name of the descriptor set file to be generated + * is made to be unique per project's Maven coordinates. + * + * As the last step of this task, writes a `desc.ref` file + * for the contextual source set, pointing to the generated descriptor set file. + * This is needed in order to allow other Spine libraries + * to locate and load the generated descriptor set files properly. + * + * Such a job is usually performed by Spine McJava plugin, + * however, it is not possible to use this plugin (or its code) + * in this repository due to cyclic dependencies. */ +@Suppress( + "TooGenericExceptionCaught" /* Handling all file-writing failures in the same way.*/) private fun GenerateProtoTask.setupDescriptorSetFileCreation() { // Tell `protoc` generate descriptor set file. + // The name of the generated file reflects project's Maven coordinates. val ssn = sourceSet.name generateDescriptorSet = true val descriptorsDir = "${project.buildDir}/descriptors/${ssn}" + val descriptorName = project.descriptorSetName(sourceSet) with(descriptorSetOptions) { - path = "$descriptorsDir/known_types_${ssn}.desc" + path = "$descriptorsDir/$descriptorName" includeImports = true includeSourceInfo = true } + // Make the descriptor set file included into the resources. project.sourceSets.named(ssn) { resources.srcDirs(descriptorsDir) } + + // Create a `desc.ref` in the same resource folder, + // with the name of the descriptor set file created above. + this.doLast { + val descRefFile = File(descriptorsDir, "desc.ref") + descRefFile.createNewFile() + try { + Files.write(descRefFile.toPath(), setOf(descriptorName), TRUNCATE_EXISTING) + } catch (e: Exception) { + project.logger.error("Error writing `${descRefFile.absolutePath}`.", e) + throw e + } + } } +/** + * Returns a name of the descriptor file for the given [sourceSet], + * reflecting the Maven coordinates of Gradle artifact, and the source set + * for which the descriptor set name is to be generated. + * + * The returned value is just a file name, and does not contain a file path. + */ +private fun Project.descriptorSetName(sourceSet: SourceSet) = + arrayOf( + group.toString(), + name, + sourceSet.name, + version.toString() + ).joinToString(separator = "_", postfix = ".desc") + /** * Copies files from the [outputBaseDir][GenerateProtoTask.outputBaseDir] into * a subdirectory of [generatedDir][Project.generatedDir] for diff --git a/buildSrc/src/main/kotlin/io/spine/internal/gradle/publish/Publications.kt b/buildSrc/src/main/kotlin/io/spine/internal/gradle/publish/Publications.kt index be3bc0e..12c0563 100644 --- a/buildSrc/src/main/kotlin/io/spine/internal/gradle/publish/Publications.kt +++ b/buildSrc/src/main/kotlin/io/spine/internal/gradle/publish/Publications.kt @@ -80,13 +80,25 @@ internal sealed class PublicationHandler( } /** - * Takes a group name and a version from the given [project] and assigns - * them to this publication. + * Copies the attributes of Gradle [Project] to this [MavenPublication]. + * + * The following project attributes are copied: + * * [group][Project.getGroup]; + * * [version][Project.getVersion]; + * * [description][Project.getDescription]. + * + * Also, this function adds the [artifactPrefix][SpinePublishing.artifactPrefix] to + * the [artifactId][MavenPublication.setArtifactId] of this publication, + * if the prefix is not added yet. */ - protected fun MavenPublication.assignMavenCoordinates() { + protected fun MavenPublication.copyProjectAttributes() { groupId = project.group.toString() - artifactId = project.spinePublishing.artifactPrefix + artifactId + val prefix = project.spinePublishing.artifactPrefix + if (!artifactId.startsWith(prefix)) { + artifactId = prefix + artifactId + } version = project.version.toString() + pom.description.set(project.description) } } @@ -110,7 +122,7 @@ private fun RepositoryHandler.register(project: Project, repository: Repository) /** * A publication for a typical Java project. * - * In Gradle, in order to publish something somewhere one should create a publication. + * In Gradle, to publish something, one should create a publication. * A publication has a name and consists of one or more artifacts plus information about * those artifacts – the metadata. * @@ -142,7 +154,7 @@ internal class StandardJavaPublicationHandler( val jars = project.artifacts(jarFlags) val publications = project.publications publications.create("mavenJava") { - assignMavenCoordinates() + copyProjectAttributes() specifyArtifacts(jars) } } @@ -191,14 +203,14 @@ internal class StandardJavaPublicationHandler( * * Such publications should be treated differently than [StandardJavaPublicationHandler], * which is created for a module. Instead, since the publications are already declared, - * this class only [assigns maven coordinates][assignMavenCoordinates]. + * this class only [assigns maven coordinates][copyProjectAttributes]. * * A module which declares custom publications must be specified in * the [SpinePublishing.modulesWithCustomPublishing] property. * * If a module with [publications] declared locally is not specified as one with custom publishing, * it may cause a name clash between an artifact produced by the [standard][MavenPublication] - * publication, and custom ones. In order to have both standard and custom publications, + * publication, and custom ones. To have both standard and custom publications, * please specify custom artifact IDs or classifiers for each custom publication. */ internal class CustomPublicationHandler(project: Project, destinations: Set) : @@ -206,7 +218,7 @@ internal class CustomPublicationHandler(project: Project, destinations: Set diff --git a/config b/config index f3d4902..bb06dc1 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit f3d4902651973cd3f40f197f452b55e6f61de0f6 +Subproject commit bb06dc1f6d6e3f687a423d08c15c74ce58950849 diff --git a/dependencies.md b/dependencies.md index 025d9ab..0c767d0 100644 --- a/dependencies.md +++ b/dependencies.md @@ -15,6 +15,14 @@ * **Project URL:** [https://errorprone.info/error_prone_annotations](https://errorprone.info/error_prone_annotations) * **License:** [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) +1. **Group** : com.google.flogger. **Name** : flogger. **Version** : 0.7.4. + * **Project URL:** [https://github.com/google/flogger](https://github.com/google/flogger) + * **License:** [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) + +1. **Group** : com.google.flogger. **Name** : flogger-system-backend. **Version** : 0.7.4. + * **Project URL:** [https://github.com/google/flogger](https://github.com/google/flogger) + * **License:** [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) + 1. **Group** : com.google.guava. **Name** : failureaccess. **Version** : 1.0.1. * **Project URL:** [https://github.com/google/guava/](https://github.com/google/guava/) * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -30,17 +38,22 @@ * **Project URL:** [https://github.com/google/j2objc/](https://github.com/google/j2objc/) * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.google.protobuf. **Name** : protobuf-java. **Version** : 3.25.0. +1. **Group** : com.google.protobuf. **Name** : protobuf-java. **Version** : 3.25.1. * **Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **License:** [BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -1. **Group** : com.google.protobuf. **Name** : protobuf-java-util. **Version** : 3.25.0. +1. **Group** : com.google.protobuf. **Name** : protobuf-java-util. **Version** : 3.25.1. * **Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **License:** [BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -1. **Group** : com.google.protobuf. **Name** : protobuf-kotlin. **Version** : 3.25.0. +1. **Group** : com.google.protobuf. **Name** : protobuf-kotlin. **Version** : 3.25.1. * **License:** [BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) +1. **Group** : org.checkerframework. **Name** : checker-compat-qual. **Version** : 2.5.3. + * **Project URL:** [https://checkerframework.org](https://checkerframework.org) + * **License:** [GNU General Public License, version 2 (GPL2), with the classpath exception](http://www.gnu.org/software/classpath/license.html) + * **License:** [The MIT License](http://opensource.org/licenses/MIT) + 1. **Group** : org.checkerframework. **Name** : checker-qual. **Version** : 3.40.0. * **Project URL:** [https://checkerframework.org/](https://checkerframework.org/) * **License:** [The MIT License](http://opensource.org/licenses/MIT) @@ -49,22 +62,28 @@ * **Project URL:** [https://github.com/JetBrains/java-annotations](https://github.com/JetBrains/java-annotations) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-reflect. **Version** : 1.9.20. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-reflect. **Version** : 1.9.23. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-stdlib. **Version** : 1.9.20. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-stdlib. **Version** : 1.9.23. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-stdlib-jdk7. **Version** : 1.9.20. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-stdlib-common. **Version** : 1.9.23.**No license information found** +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-stdlib-jdk7. **Version** : 1.9.23. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-stdlib-jdk8. **Version** : 1.9.20. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-stdlib-jdk8. **Version** : 1.9.23. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) +1. **Group** : org.ow2.asm. **Name** : asm. **Version** : 9.2. + * **Project URL:** [http://asm.ow2.io/](http://asm.ow2.io/) + * **License:** [BSD-3-Clause](https://asm.ow2.io/license.html) + * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) + ## Compile, tests, and tooling 1. **Group** : aopalliance. **Name** : aopalliance. **Version** : 1.0. * **Project URL:** [http://aopalliance.sourceforge.net](http://aopalliance.sourceforge.net) @@ -198,15 +217,15 @@ * **Project URL:** [https://github.com/google/j2objc/](https://github.com/google/j2objc/) * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.google.protobuf. **Name** : protobuf-java. **Version** : 3.25.0. +1. **Group** : com.google.protobuf. **Name** : protobuf-java. **Version** : 3.25.1. * **Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **License:** [BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -1. **Group** : com.google.protobuf. **Name** : protobuf-java-util. **Version** : 3.25.0. +1. **Group** : com.google.protobuf. **Name** : protobuf-java-util. **Version** : 3.25.1. * **Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **License:** [BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -1. **Group** : com.google.protobuf. **Name** : protobuf-kotlin. **Version** : 3.25.0. +1. **Group** : com.google.protobuf. **Name** : protobuf-kotlin. **Version** : 3.25.1. * **License:** [BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) 1. **Group** : com.google.truth. **Name** : truth. **Version** : 1.1.5. @@ -592,7 +611,7 @@ * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-reflect. **Version** : 1.9.20. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-reflect. **Version** : 1.9.23. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -620,16 +639,16 @@ * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-stdlib. **Version** : 1.9.20. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-stdlib. **Version** : 1.9.23. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-stdlib-common. **Version** : 1.9.20.**No license information found** -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-stdlib-jdk7. **Version** : 1.9.20. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-stdlib-common. **Version** : 1.9.23.**No license information found** +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-stdlib-jdk7. **Version** : 1.9.23. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-stdlib-jdk8. **Version** : 1.9.20. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-stdlib-jdk8. **Version** : 1.9.23. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -756,4 +775,4 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Sun May 05 20:14:15 WEST 2024** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). \ No newline at end of file +This report was generated on **Sun May 05 20:16:31 WEST 2024** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index b1624c4..c7d437b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/pom.xml b/pom.xml index bbac9d0..7606aea 100644 --- a/pom.xml +++ b/pom.xml @@ -32,25 +32,31 @@ all modules and does not describe the project structure per-subproject. com.google.protobuf protobuf-java - 3.25.0 + 3.25.1 compile com.google.protobuf protobuf-java-util - 3.25.0 + 3.25.1 compile com.google.protobuf protobuf-kotlin - 3.25.0 + 3.25.1 + compile + + + io.spine + spine-logging + 2.0.0-SNAPSHOT.233 compile org.jetbrains.kotlin kotlin-reflect - 1.9.20 + 1.9.23 compile From bdbe997d369c86ff8ce2972479242094e5126db0 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Sun, 5 May 2024 20:37:57 +0100 Subject: [PATCH 05/23] Rename .java to .kt --- .../StackGetter.java => kotlin/io/spine/reflect/StackGetter.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/main/{java/io/spine/reflect/StackGetter.java => kotlin/io/spine/reflect/StackGetter.kt} (100%) diff --git a/src/main/java/io/spine/reflect/StackGetter.java b/src/main/kotlin/io/spine/reflect/StackGetter.kt similarity index 100% rename from src/main/java/io/spine/reflect/StackGetter.java rename to src/main/kotlin/io/spine/reflect/StackGetter.kt From 3e5b171409707f339ff7f896edcc8ea57317a77c Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Sun, 5 May 2024 20:37:57 +0100 Subject: [PATCH 06/23] Convert `StackGetter` to Kotlin --- .../kotlin/io/spine/reflect/StackGetter.kt | 43 +++++++++++-------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/src/main/kotlin/io/spine/reflect/StackGetter.kt b/src/main/kotlin/io/spine/reflect/StackGetter.kt index 234fc04..ee768c7 100644 --- a/src/main/kotlin/io/spine/reflect/StackGetter.kt +++ b/src/main/kotlin/io/spine/reflect/StackGetter.kt @@ -23,45 +23,50 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - -package io.spine.reflect; +package io.spine.reflect /** * Interface for finding call site information. * - * @see - * Original Java code of Google Flogger + * @see [ + * Original Java code of Google Flogger](https://github.com/google/flogger/blob/cb9e836a897d36a78309ee8badf5cad4e6a2d3d8/api/src/main/java/com/google/common/flogger/util/StackGetter.java) */ -interface StackGetter { - +internal interface StackGetter { /** - * Returns the first caller of a method on the {@code target} class that is *not* a member of - * {@code target} class, by walking back on the stack, or null if the {@code target} class - * cannot be found or is the last element of the stack. + * Returns the first caller of a method on the [target] class that is *not* a member of + * the `target` class. + * + * The caller is obtained by walking back on the stack. * * @param target - * the class to find the caller of + * the class to find the caller of. * @param skipFrames * skip this many frames before looking for the caller. * This can be used for optimization. + * @return the first caller of the method or `null` if the `target` class + * cannot be found or is the last element of the stack. */ - StackTraceElement callerOf(Class target, int skipFrames); + fun callerOf(target: Class<*>, skipFrames: Int): StackTraceElement? /** - * Returns up to {@code maxDepth} frames of the stack starting at the stack frame that - * is a caller of a method on {@code target} class but is *not* itself a method - * on {@code target} class. + * Returns up to `maxDepth` frames of the stack starting at the stack frame that + * is a caller of a method on `target` class but is *not* itself a method + * on `target` class. * * @param target - * the class to get the stack from + * the class to get the stack from. * @param maxDepth - * the maximum depth of the stack to return. A value of -1 means to return the - * whole stack + * the maximum depth of the stack to return. + * A value of -1 means to return the whole stack. * @param skipFrames * skip this many stack frames before looking for the target class. * Used for optimization. * @throws IllegalArgumentException - * if {@code maxDepth} is 0 or < -1 or {@code skipFrames} is < 0. + * if `maxDepth` is 0 or < -1 or `skipFrames` is < 0. */ - StackTraceElement[] getStackForCaller(Class target, int maxDepth, int skipFrames); + fun getStackForCaller( + target: Class<*>, + maxDepth: Int, + skipFrames: Int + ): Array } From 2dcd851cb6931ed8ecb7ed6ec7d0091fe89ec67b Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Sun, 5 May 2024 20:38:08 +0100 Subject: [PATCH 07/23] Move stub classed under the `given` package --- .../io/spine/reflect/given/LoggerCode.kt | 58 +++++++++++++++++++ .../kotlin/io/spine/reflect/given/UserCode.kt | 41 +++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 src/test/kotlin/io/spine/reflect/given/LoggerCode.kt create mode 100644 src/test/kotlin/io/spine/reflect/given/UserCode.kt diff --git a/src/test/kotlin/io/spine/reflect/given/LoggerCode.kt b/src/test/kotlin/io/spine/reflect/given/LoggerCode.kt new file mode 100644 index 0000000..5cee3a2 --- /dev/null +++ b/src/test/kotlin/io/spine/reflect/given/LoggerCode.kt @@ -0,0 +1,58 @@ +/* + * Copyright 2024, TeamDev. All rights reserved. + * + * 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 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.reflect.given + +import io.spine.reflect.CallerFinder +import io.spine.reflect.StackGetter + +/** + * A fake class that emulates the logging library, which eventually + * calls the given [StackGetter], if any, or [CallerFinder]. + */ +internal class LoggerCode( + private val skipCount: Int, + private val stackGetter: StackGetter? = null +) { + + var caller: StackTraceElement? = null + + fun logMethod() { + internalMethodOne() + } + + private fun internalMethodOne() { + internalMethodTwo() + } + + private fun internalMethodTwo() { + caller = if (stackGetter != null) { + stackGetter.callerOf(LoggerCode::class.java, skipCount) + } else { + CallerFinder.findCallerOf(LoggerCode::class.java, skipCount) + } + } +} diff --git a/src/test/kotlin/io/spine/reflect/given/UserCode.kt b/src/test/kotlin/io/spine/reflect/given/UserCode.kt new file mode 100644 index 0000000..fcb1a01 --- /dev/null +++ b/src/test/kotlin/io/spine/reflect/given/UserCode.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2024, TeamDev. All rights reserved. + * + * 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 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.reflect.given + +/** + * Fake class that emulates some code calling a log method. + */ +internal class UserCode(private val logger: LoggerCode) { + + fun invokeUserCode() { + loggingMethod() + } + + private fun loggingMethod() { + logger.logMethod() + } +} From 4fa686c73fba2a60aed4f3fa10168253eae0d43c Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Sun, 5 May 2024 20:38:37 +0100 Subject: [PATCH 08/23] Move stub classed under the `given` package --- .../java/io/spine/reflect/CallerFinder.java | 2 +- .../spine/reflect/StackWalkerStackGetter.java | 3 +- .../spine/reflect/AbstractStackGetterSpec.kt | 52 +++---------------- .../io/spine/reflect/CallerFinderSpec.kt | 10 ++-- 4 files changed, 17 insertions(+), 50 deletions(-) diff --git a/src/main/java/io/spine/reflect/CallerFinder.java b/src/main/java/io/spine/reflect/CallerFinder.java index a5cd02a..8933f51 100644 --- a/src/main/java/io/spine/reflect/CallerFinder.java +++ b/src/main/java/io/spine/reflect/CallerFinder.java @@ -89,7 +89,7 @@ public static StackTraceElement findCallerOf(Class target, int skip) { * Returns a synthetic stack trace starting at the immediate caller of the specified target. * * @param target - * the class who caller the returned stack trace will start at. + * the class who is the caller the returned stack trace will start at. * @param maxDepth * the maximum size of the returned stack (pass -1 for the complete stack). * @param skip diff --git a/src/main/java/io/spine/reflect/StackWalkerStackGetter.java b/src/main/java/io/spine/reflect/StackWalkerStackGetter.java index 8063dce..62cd9ed 100644 --- a/src/main/java/io/spine/reflect/StackWalkerStackGetter.java +++ b/src/main/java/io/spine/reflect/StackWalkerStackGetter.java @@ -50,7 +50,8 @@ final class StackWalkerStackGetter implements StackGetter { StackWalkerStackGetter() { // Due to b/241269335, we check in constructor whether this implementation // crashes in runtime, and CallerFinder should catch any Throwable caused. - StackTraceElement unused = callerOf(StackWalkerStackGetter.class, 0); + @SuppressWarnings("unused") + var unused = callerOf(StackWalkerStackGetter.class, 0); } @Override diff --git a/src/test/kotlin/io/spine/reflect/AbstractStackGetterSpec.kt b/src/test/kotlin/io/spine/reflect/AbstractStackGetterSpec.kt index 4ead632..fe2a320 100644 --- a/src/test/kotlin/io/spine/reflect/AbstractStackGetterSpec.kt +++ b/src/test/kotlin/io/spine/reflect/AbstractStackGetterSpec.kt @@ -28,6 +28,8 @@ package io.spine.reflect import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe +import io.spine.reflect.given.LoggerCode +import io.spine.reflect.given.UserCode import org.junit.jupiter.api.Test /** @@ -47,9 +49,11 @@ internal abstract class AbstractStackGetterSpec( val library = LoggerCode(skipCount = 2, stackGetter) val code = UserCode(library) code.invokeUserCode() - library.caller shouldNotBe null - library.caller!!.className shouldBe UserCode::class.java.name - library.caller!!.methodName shouldBe "loggingMethod" + library.run { + caller shouldNotBe null + caller!!.className shouldBe UserCode::class.java.name + caller!!.methodName shouldBe "loggingMethod" + } } @Test @@ -61,45 +65,3 @@ internal abstract class AbstractStackGetterSpec( library.caller shouldBe null } } - -/** - * Fake class that emulates some code calling a log method. - */ -internal class UserCode(private val logger: LoggerCode) { - - fun invokeUserCode() { - loggingMethod() - } - - private fun loggingMethod() { - logger.logMethod() - } -} - -/** - * A fake class that emulates the logging library, which eventually - * calls the given [StackGetter], if any, or [CallerFinder]. - */ -internal class LoggerCode( - private val skipCount: Int, - private val stackGetter: StackGetter? = null -) { - - var caller: StackTraceElement? = null - - fun logMethod() { - internalMethodOne() - } - - private fun internalMethodOne() { - internalMethodTwo() - } - - private fun internalMethodTwo() { - caller = if (stackGetter != null) { - stackGetter.callerOf(LoggerCode::class.java, skipCount) - } else { - CallerFinder.findCallerOf(LoggerCode::class.java, skipCount) - } - } -} diff --git a/src/test/kotlin/io/spine/reflect/CallerFinderSpec.kt b/src/test/kotlin/io/spine/reflect/CallerFinderSpec.kt index d3fcb7f..c5301f5 100644 --- a/src/test/kotlin/io/spine/reflect/CallerFinderSpec.kt +++ b/src/test/kotlin/io/spine/reflect/CallerFinderSpec.kt @@ -28,6 +28,8 @@ package io.spine.reflect import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe +import io.spine.reflect.given.LoggerCode +import io.spine.reflect.given.UserCode import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test @@ -67,9 +69,11 @@ internal class CallerFinderSpec { val library = LoggerCode(skipCount = 2) val code = UserCode(library) code.invokeUserCode() - library.caller shouldNotBe null - library.caller!!.className shouldBe UserCode::class.java.name - library.caller!!.methodName shouldBe "loggingMethod" + library.run { + caller shouldNotBe null + caller!!.className shouldBe UserCode::class.java.name + caller!!.methodName shouldBe "loggingMethod" + } } @Test From 4d8c6a4267be431979404e5598ae480977bdf50e Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Sun, 5 May 2024 21:41:30 +0100 Subject: [PATCH 09/23] Rename .java to .kt --- .../reflect/Checks.java => kotlin/io/spine/reflect/Checks.kt} | 0 .../io/spine/reflect/StackWalkerStackGetter.kt} | 0 .../io/spine/reflect/ThrowableStackGetter.kt} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename src/main/{java/io/spine/reflect/Checks.java => kotlin/io/spine/reflect/Checks.kt} (100%) rename src/main/{java/io/spine/reflect/StackWalkerStackGetter.java => kotlin/io/spine/reflect/StackWalkerStackGetter.kt} (100%) rename src/main/{java/io/spine/reflect/ThrowableStackGetter.java => kotlin/io/spine/reflect/ThrowableStackGetter.kt} (100%) diff --git a/src/main/java/io/spine/reflect/Checks.java b/src/main/kotlin/io/spine/reflect/Checks.kt similarity index 100% rename from src/main/java/io/spine/reflect/Checks.java rename to src/main/kotlin/io/spine/reflect/Checks.kt diff --git a/src/main/java/io/spine/reflect/StackWalkerStackGetter.java b/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt similarity index 100% rename from src/main/java/io/spine/reflect/StackWalkerStackGetter.java rename to src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt diff --git a/src/main/java/io/spine/reflect/ThrowableStackGetter.java b/src/main/kotlin/io/spine/reflect/ThrowableStackGetter.kt similarity index 100% rename from src/main/java/io/spine/reflect/ThrowableStackGetter.java rename to src/main/kotlin/io/spine/reflect/ThrowableStackGetter.kt From aed1fee654dabbf18f94a9252e66f32a4dedc464 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Sun, 5 May 2024 21:41:30 +0100 Subject: [PATCH 10/23] Convert stack getter impl to Kotlin --- .../java/io/spine/reflect/CallerFinder.java | 10 +- src/main/kotlin/io/spine/reflect/Checks.kt | 44 ++------- .../spine/reflect/StackWalkerStackGetter.kt | 98 ++++++++++--------- .../io/spine/reflect/ThrowableStackGetter.kt | 90 ++++++++--------- 4 files changed, 105 insertions(+), 137 deletions(-) diff --git a/src/main/java/io/spine/reflect/CallerFinder.java b/src/main/java/io/spine/reflect/CallerFinder.java index 8933f51..a2729e1 100644 --- a/src/main/java/io/spine/reflect/CallerFinder.java +++ b/src/main/java/io/spine/reflect/CallerFinder.java @@ -29,13 +29,11 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; -import static io.spine.reflect.Checks.checkNotNull; - /** * A helper class for determining callers of a specified class currently on the stack. * * @see - * Original Java code from Google Flogger + * Original Java code of Google Flogger */ public final class CallerFinder { @@ -78,7 +76,6 @@ private static StackGetter createBestStackGetter() { */ @Nullable public static StackTraceElement findCallerOf(Class target, int skip) { - checkTarget(target); if (skip < 0) { throw skipCountCannotBeNegative(skip); } @@ -99,7 +96,6 @@ public static StackTraceElement findCallerOf(Class target, int skip) { * use of JNI). */ public static StackTraceElement[] getStackForCallerOf(Class target, int maxDepth, int skip) { - checkTarget(target); if (maxDepth <= 0 && maxDepth != -1) { throw new IllegalArgumentException("invalid maximum depth: " + maxDepth); } @@ -109,10 +105,6 @@ public static StackTraceElement[] getStackForCallerOf(Class target, int maxDe return STACK_GETTER.getStackForCaller(target, maxDepth, skip + 1); } - private static void checkTarget(Class target) { - checkNotNull(target, "target"); - } - private static @NonNull IllegalArgumentException skipCountCannotBeNegative(int skip) { return new IllegalArgumentException("skip count cannot be negative: " + skip); } diff --git a/src/main/kotlin/io/spine/reflect/Checks.kt b/src/main/kotlin/io/spine/reflect/Checks.kt index 5cff8ff..8224a9d 100644 --- a/src/main/kotlin/io/spine/reflect/Checks.kt +++ b/src/main/kotlin/io/spine/reflect/Checks.kt @@ -24,44 +24,14 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package io.spine.reflect; +@file:JvmName("Checks") -import com.google.errorprone.annotations.CanIgnoreReturnValue; +package io.spine.reflect -/** - * Local version of the Guava {@code Preconditions} class for simple, often used checks. - * - * @see - * Original Java code of Google Flogger - */ -class Checks { - - private Checks() { - } - - @CanIgnoreReturnValue - @SuppressWarnings({ - "ConstantValue" /* We don't want to make the parameter `value` `@Nullable`. */, - "PMD.AvoidThrowingNullPointerException" /* We throw it here by convention. */ - }) - static T checkNotNull(T value, String name) { - if (value == null) { - throw new NullPointerException(name + " must not be null"); - } - return value; - } - - private static void checkArgument(boolean condition, String message) { - if (!condition) { - throw new IllegalArgumentException(message); - } - } - - static void checkMaxDepth(int maxDepth) { - checkArgument(maxDepth == -1 || maxDepth > 0, "maxDepth must be > 0 or -1"); - } +internal fun checkMaxDepth(maxDepth: Int) { + require(maxDepth == -1 || maxDepth > 0) { "maxDepth must be > 0 or -1" } +} - static void checkSkipFrames(int skipFrames) { - checkArgument(skipFrames >= 0, "skipFrames must be >= 0"); - } +internal fun checkSkipFrames(skipFrames: Int) { + require(skipFrames >= 0) { "skipFrames must be >= 0" } } diff --git a/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt b/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt index 62cd9ed..944bd93 100644 --- a/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt +++ b/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt @@ -23,70 +23,74 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +package io.spine.reflect -package io.spine.reflect; - -import java.lang.StackWalker.StackFrame; -import java.util.function.Predicate; -import java.util.stream.Stream; - -import static io.spine.reflect.Checks.checkMaxDepth; -import static io.spine.reflect.Checks.checkSkipFrames; +import java.lang.StackWalker.Option.SHOW_REFLECT_FRAMES +import java.lang.StackWalker.StackFrame +import java.util.function.Predicate +import java.util.stream.Stream +import kotlin.Long.Companion.MAX_VALUE +import kotlin.collections.toTypedArray +import kotlin.streams.toList /** - * StackWalker based implementation of the {@link StackGetter} interface. - * - *

Note, that since this is using Java 9 api, it is being compiled separately from the rest of - * the source code. + * StackWalker based implementation of the [StackGetter] interface. * * @see - * Original Java code from Google Flogger + * Original Java code of Google Flogger */ -final class StackWalkerStackGetter implements StackGetter { - - private static final StackWalker STACK_WALKER = - StackWalker.getInstance(StackWalker.Option.SHOW_REFLECT_FRAMES); +internal class StackWalkerStackGetter : StackGetter { - StackWalkerStackGetter() { + init { // Due to b/241269335, we check in constructor whether this implementation // crashes in runtime, and CallerFinder should catch any Throwable caused. - @SuppressWarnings("unused") - var unused = callerOf(StackWalkerStackGetter.class, 0); + @Suppress("UNUSED_VARIABLE") + val unused = callerOf(StackWalkerStackGetter::class.java, 0) } - @Override - public StackTraceElement callerOf(Class target, int skipFrames) { - checkSkipFrames(skipFrames); - return STACK_WALKER.walk( - s -> filterStackTraceAfterTarget(isTargetClass(target), skipFrames, s) - .findFirst() - .orElse(null)); + override fun callerOf(target: Class<*>, skipFrames: Int): StackTraceElement? { + checkSkipFrames(skipFrames) + return STACK_WALKER.walk { stream -> + filterStackTraceAfterTarget(isTargetClass(target), skipFrames, stream) + .findFirst() + .orElse(null) + } } - @Override - public StackTraceElement[] getStackForCaller(Class target, int maxDepth, int skipFrames) { - checkMaxDepth(maxDepth); - checkSkipFrames(skipFrames); - return STACK_WALKER.walk( - s -> filterStackTraceAfterTarget(isTargetClass(target), skipFrames, s) - .limit(maxDepth == -1 ? Long.MAX_VALUE : maxDepth) - .toArray(StackTraceElement[]::new)); + override fun getStackForCaller( + target: Class<*>, + maxDepth: Int, + skipFrames: Int + ): Array { + checkMaxDepth(maxDepth) + checkSkipFrames(skipFrames) + return STACK_WALKER.walk { stream -> + filterStackTraceAfterTarget(isTargetClass(target), skipFrames, stream) + .limit(if (maxDepth == -1) MAX_VALUE else maxDepth.toLong()) + .toList() + .toTypedArray() + } } - private static Predicate isTargetClass(Class target) { - var name = target.getName(); - return f -> f.getClassName() - .equals(name); - } + companion object { + + private val STACK_WALKER: StackWalker = StackWalker.getInstance(SHOW_REFLECT_FRAMES) - private static Stream filterStackTraceAfterTarget( - Predicate isTargetClass, int skipFrames, Stream s) { - // need to skip + 1 because of the call to the method this method is being called from - return s.skip(skipFrames + 1) - // skip all classes which don't match the name we are looking for + private fun filterStackTraceAfterTarget( + isTargetClass: Predicate, + skipFrames: Int, + s: Stream + ): Stream { + // need to skip + 1 because of the call to the method this method is being called from. + return s.skip((skipFrames + 1).toLong()) + // Skip all classes which don't match the name we are looking for. .dropWhile(isTargetClass.negate()) - // then skip all which matches + // Then skip all which matches. .dropWhile(isTargetClass) - .map(StackFrame::toStackTraceElement); + .map { frame -> frame.toStackTraceElement() } + } } } + +private fun isTargetClass(target: Class<*>): Predicate = + Predicate { frame -> (frame.className == target.name) } diff --git a/src/main/kotlin/io/spine/reflect/ThrowableStackGetter.kt b/src/main/kotlin/io/spine/reflect/ThrowableStackGetter.kt index 52a676e..26a03bc 100644 --- a/src/main/kotlin/io/spine/reflect/ThrowableStackGetter.kt +++ b/src/main/kotlin/io/spine/reflect/ThrowableStackGetter.kt @@ -23,66 +23,68 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - -package io.spine.reflect; - -import javax.annotation.Nullable; - -import static io.spine.reflect.Checks.checkMaxDepth; -import static io.spine.reflect.Checks.checkSkipFrames; +package io.spine.reflect /** - * Default implementation of {@link StackGetter} using {@link Throwable#getStackTrace}. + * Default implementation of [StackGetter] using [Throwable.getStackTrace]. * * @see - * Original Java code of Google Flogger + * Original Java code of Google Flogger */ -final class ThrowableStackGetter implements StackGetter { - - private static final StackTraceElement[] EMPTY_STACK_TRACE = new StackTraceElement[0]; +@Suppress("ThrowingExceptionsWithoutMessageOrCause") // For obtaining current stacktrace. +internal class ThrowableStackGetter : StackGetter { - @Override - @Nullable - public StackTraceElement callerOf(Class target, int skipFrames) { - checkSkipFrames(skipFrames); - var stack = new Throwable().getStackTrace(); - var callerIndex = findCallerIndex(stack, target, skipFrames + 1); + override fun callerOf(target: Class<*>, skipFrames: Int): StackTraceElement? { + checkSkipFrames(skipFrames) + val stack = Throwable().stackTrace + val callerIndex = findCallerIndex(stack, target, skipFrames + 1) if (callerIndex != -1) { - return stack[callerIndex]; + return stack[callerIndex] } - - return null; + return null } - @Override - public StackTraceElement[] getStackForCaller(Class target, int maxDepth, int skipFrames) { - checkMaxDepth(maxDepth); - checkSkipFrames(skipFrames); - var stack = new Throwable().getStackTrace(); - var callerIndex = findCallerIndex(stack, target, skipFrames + 1); + override fun getStackForCaller( + target: Class<*>, + maxDepth: Int, + skipFrames: Int + ): Array { + checkMaxDepth(maxDepth) + checkSkipFrames(skipFrames) + val stack = Throwable().stackTrace + val callerIndex = findCallerIndex(stack, target, skipFrames + 1) if (callerIndex == -1) { - return EMPTY_STACK_TRACE; + return EMPTY_STACK_TRACE } - var elementsToAdd = stack.length - callerIndex; - if (maxDepth > 0 && maxDepth < elementsToAdd) { - elementsToAdd = maxDepth; + var elementsToAdd = stack.size - callerIndex + if (maxDepth in 1.. target, int skipFrames) { - var foundCaller = false; - var targetClassName = target.getName(); - for (var frameIndex = skipFrames; frameIndex < stack.length; frameIndex++) { - if (stack[frameIndex].getClassName() - .equals(targetClassName)) { - foundCaller = true; - } else if (foundCaller) { - return frameIndex; + companion object { + + private val EMPTY_STACK_TRACE = arrayOf() + + private fun findCallerIndex( + stack: Array, + target: Class<*>, + skipFrames: Int + ): Int { + var foundCaller = false + val targetClassName = target.name + for (frameIndex in skipFrames.. Date: Sun, 5 May 2024 23:32:13 +0100 Subject: [PATCH 11/23] Rename .java to .kt --- .../CallerFinder.java => kotlin/io/spine/reflect/CallerFinder.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/main/{java/io/spine/reflect/CallerFinder.java => kotlin/io/spine/reflect/CallerFinder.kt} (100%) diff --git a/src/main/java/io/spine/reflect/CallerFinder.java b/src/main/kotlin/io/spine/reflect/CallerFinder.kt similarity index 100% rename from src/main/java/io/spine/reflect/CallerFinder.java rename to src/main/kotlin/io/spine/reflect/CallerFinder.kt From acae8c8196b3b971aad365ea7d0006165d6f3283 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Sun, 5 May 2024 23:32:13 +0100 Subject: [PATCH 12/23] Convert `CallFinder` to Kotlin Also: * Improve method names. --- .../kotlin/io/spine/reflect/CallerFinder.kt | 65 ++++++++----------- .../kotlin/io/spine/reflect/StackGetter.kt | 2 +- .../spine/reflect/StackWalkerStackGetter.kt | 2 +- .../io/spine/reflect/ThrowableStackGetter.kt | 2 +- 4 files changed, 30 insertions(+), 41 deletions(-) diff --git a/src/main/kotlin/io/spine/reflect/CallerFinder.kt b/src/main/kotlin/io/spine/reflect/CallerFinder.kt index a2729e1..446e283 100644 --- a/src/main/kotlin/io/spine/reflect/CallerFinder.kt +++ b/src/main/kotlin/io/spine/reflect/CallerFinder.kt @@ -23,38 +23,30 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - -package io.spine.reflect; - -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; +package io.spine.reflect /** - * A helper class for determining callers of a specified class currently on the stack. + * A helper object for determining callers of a specified class currently on the stack. * * @see - * Original Java code of Google Flogger + * Original Java code of Google Flogger */ -public final class CallerFinder { +public object CallerFinder { - private static final StackGetter STACK_GETTER = createBestStackGetter(); - - /** Prevents instantiation of this utility class. */ - private CallerFinder() { - } + private val STACK_GETTER = createBestStackGetter() /** - * Returns the first available class implementing the {@link StackGetter} methods. + * Returns the first available class implementing the [StackGetter] methods. * The implementation returned is dependent on the current Java version. */ - private static StackGetter createBestStackGetter() { - try { - return new StackWalkerStackGetter(); - } catch (Throwable ignored) { + private fun createBestStackGetter(): StackGetter { + return try { + StackWalkerStackGetter() + } catch (ignored: Throwable) { // We may not be able to create `StackWalkerStackGetter` sometimes, // for example, on Android. This is not a problem because we have // `ThrowableStackGetter` as a fallback option. - return new ThrowableStackGetter(); + ThrowableStackGetter() } } @@ -62,7 +54,7 @@ public final class CallerFinder { * Returns the stack trace element of the immediate caller of the specified class. * * @param target - * the target class whose callers we are looking for + * the target class whose callers we are looking for. * @param skip * the minimum number of calls known to have occurred between the first call to the * target class and the point at which the specified throwable was created. @@ -71,15 +63,12 @@ public final class CallerFinder { * cannot know whether a tool such as Proguard has merged methods or classes and * reduced the number of intermediate stack frames. * @return the stack trace element representing the immediate caller of the specified class, or - * null if no caller was found (due to incorrect target, wrong skip count or - * use of JNI). + * `null` if no caller was found (due to incorrect target, wrong skip count or + * use of JNI). */ - @Nullable - public static StackTraceElement findCallerOf(Class target, int skip) { - if (skip < 0) { - throw skipCountCannotBeNegative(skip); - } - return STACK_GETTER.callerOf(target, skip + 1); + public fun findCallerOf(target: Class<*>?, skip: Int): StackTraceElement? { + checkSkipCount(skip) + return STACK_GETTER.callerOf(target!!, skip + 1) } /** @@ -95,17 +84,17 @@ public final class CallerFinder { * the empty array if no caller was found (due to incorrect target, wrong skip count or * use of JNI). */ - public static StackTraceElement[] getStackForCallerOf(Class target, int maxDepth, int skip) { - if (maxDepth <= 0 && maxDepth != -1) { - throw new IllegalArgumentException("invalid maximum depth: " + maxDepth); - } - if (skip < 0) { - throw skipCountCannotBeNegative(skip); - } - return STACK_GETTER.getStackForCaller(target, maxDepth, skip + 1); + public fun stackForCallerOf( + target: Class<*>, + maxDepth: Int, + skip: Int + ): Array { + require(!(maxDepth <= 0 && maxDepth != -1)) { "invalid maximum depth: $maxDepth." } + checkSkipCount(skip) + return STACK_GETTER.stackForCaller(target, maxDepth, skip + 1) } - private static @NonNull IllegalArgumentException skipCountCannotBeNegative(int skip) { - return new IllegalArgumentException("skip count cannot be negative: " + skip); + private fun checkSkipCount(skip: Int) { + require(skip >= 0) { "skip can't be negative: $skip." } } } diff --git a/src/main/kotlin/io/spine/reflect/StackGetter.kt b/src/main/kotlin/io/spine/reflect/StackGetter.kt index ee768c7..0aa530c 100644 --- a/src/main/kotlin/io/spine/reflect/StackGetter.kt +++ b/src/main/kotlin/io/spine/reflect/StackGetter.kt @@ -64,7 +64,7 @@ internal interface StackGetter { * @throws IllegalArgumentException * if `maxDepth` is 0 or < -1 or `skipFrames` is < 0. */ - fun getStackForCaller( + fun stackForCaller( target: Class<*>, maxDepth: Int, skipFrames: Int diff --git a/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt b/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt index 944bd93..1a85185 100644 --- a/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt +++ b/src/main/kotlin/io/spine/reflect/StackWalkerStackGetter.kt @@ -57,7 +57,7 @@ internal class StackWalkerStackGetter : StackGetter { } } - override fun getStackForCaller( + override fun stackForCaller( target: Class<*>, maxDepth: Int, skipFrames: Int diff --git a/src/main/kotlin/io/spine/reflect/ThrowableStackGetter.kt b/src/main/kotlin/io/spine/reflect/ThrowableStackGetter.kt index 26a03bc..e6a2879 100644 --- a/src/main/kotlin/io/spine/reflect/ThrowableStackGetter.kt +++ b/src/main/kotlin/io/spine/reflect/ThrowableStackGetter.kt @@ -44,7 +44,7 @@ internal class ThrowableStackGetter : StackGetter { return null } - override fun getStackForCaller( + override fun stackForCaller( target: Class<*>, maxDepth: Int, skipFrames: Int From 76f7aab16b21e61b2da768bb2485366478385623 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Mon, 6 May 2024 00:29:11 +0100 Subject: [PATCH 13/23] Add a simple test for checking the caller of self --- .../kotlin/io/spine/reflect/CallerFinder.kt | 34 ++++++------ .../io/spine/reflect/CallerFinderSpec.kt | 8 +++ .../io/spine/reflect/given/CallingElvis.kt | 52 +++++++++++++++++++ 3 files changed, 78 insertions(+), 16 deletions(-) create mode 100644 src/test/kotlin/io/spine/reflect/given/CallingElvis.kt diff --git a/src/main/kotlin/io/spine/reflect/CallerFinder.kt b/src/main/kotlin/io/spine/reflect/CallerFinder.kt index 446e283..6b33221 100644 --- a/src/main/kotlin/io/spine/reflect/CallerFinder.kt +++ b/src/main/kotlin/io/spine/reflect/CallerFinder.kt @@ -33,21 +33,8 @@ package io.spine.reflect */ public object CallerFinder { - private val STACK_GETTER = createBestStackGetter() - - /** - * Returns the first available class implementing the [StackGetter] methods. - * The implementation returned is dependent on the current Java version. - */ - private fun createBestStackGetter(): StackGetter { - return try { - StackWalkerStackGetter() - } catch (ignored: Throwable) { - // We may not be able to create `StackWalkerStackGetter` sometimes, - // for example, on Android. This is not a problem because we have - // `ThrowableStackGetter` as a fallback option. - ThrowableStackGetter() - } + private val STACK_GETTER by lazy { + createBestStackGetter() } /** @@ -89,11 +76,26 @@ public object CallerFinder { maxDepth: Int, skip: Int ): Array { - require(!(maxDepth <= 0 && maxDepth != -1)) { "invalid maximum depth: $maxDepth." } + require((maxDepth > 0 || maxDepth == -1)) { "invalid maximum depth: $maxDepth." } checkSkipCount(skip) return STACK_GETTER.stackForCaller(target, maxDepth, skip + 1) } + /** + * Returns the first available class implementing the [StackGetter] methods. + * The implementation returned is dependent on the current Java version. + */ + private fun createBestStackGetter(): StackGetter { + return try { + StackWalkerStackGetter() + } catch (ignored: Throwable) { + // We may not be able to create `StackWalkerStackGetter` sometimes, + // for example, on Android. This is not a problem because we have + // `ThrowableStackGetter` as a fallback option. + ThrowableStackGetter() + } + } + private fun checkSkipCount(skip: Int) { require(skip >= 0) { "skip can't be negative: $skip." } } diff --git a/src/test/kotlin/io/spine/reflect/CallerFinderSpec.kt b/src/test/kotlin/io/spine/reflect/CallerFinderSpec.kt index c5301f5..efff2c3 100644 --- a/src/test/kotlin/io/spine/reflect/CallerFinderSpec.kt +++ b/src/test/kotlin/io/spine/reflect/CallerFinderSpec.kt @@ -28,6 +28,8 @@ package io.spine.reflect import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe +import io.spine.reflect.given.AnybodyHome +import io.spine.reflect.given.Elvis import io.spine.reflect.given.LoggerCode import io.spine.reflect.given.UserCode import org.junit.jupiter.api.DisplayName @@ -84,4 +86,10 @@ internal class CallerFinderSpec { code.invokeUserCode() library.caller shouldBe null } + + @Test + fun `obtain the caller of a class`() { + Elvis.sign() shouldBe this::class.java + AnybodyHome.call() shouldBe AnybodyHome::class.java + } } diff --git a/src/test/kotlin/io/spine/reflect/given/CallingElvis.kt b/src/test/kotlin/io/spine/reflect/given/CallingElvis.kt new file mode 100644 index 0000000..5ffcf10 --- /dev/null +++ b/src/test/kotlin/io/spine/reflect/given/CallingElvis.kt @@ -0,0 +1,52 @@ +/* + * Copyright 2024, TeamDev. All rights reserved. + * + * 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 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.reflect.given + +import io.spine.reflect.CallerFinder.findCallerOf + +object Elvis { + + /** + * Obtains the caller of this class. + */ + fun whosCalling(): Class<*>? { + val callingClass = findCallerOf(this::class.java, 0)?.let { + Class.forName(it.className) + } + return callingClass + } + + fun sign(): Class<*>? { + return whosCalling() + } +} + +object AnybodyHome { + fun call(): Class<*>? { + return Elvis.whosCalling() + } +} From 937a4718e804d589f67547d038ad0cf5b7d8153d Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Mon, 6 May 2024 00:29:18 +0100 Subject: [PATCH 14/23] Update build time --- dependencies.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.md b/dependencies.md index 0c767d0..df2dc2d 100644 --- a/dependencies.md +++ b/dependencies.md @@ -775,4 +775,4 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Sun May 05 20:16:31 WEST 2024** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). \ No newline at end of file +This report was generated on **Sun May 05 23:42:40 WEST 2024** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). \ No newline at end of file From d2a18a8b1ea31018d4f03272ee883e75e305b34f Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Mon, 6 May 2024 00:40:06 +0100 Subject: [PATCH 15/23] Improve method name and docs --- src/test/kotlin/io/spine/reflect/CallerFinderSpec.kt | 4 ++++ src/test/kotlin/io/spine/reflect/given/CallingElvis.kt | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/test/kotlin/io/spine/reflect/CallerFinderSpec.kt b/src/test/kotlin/io/spine/reflect/CallerFinderSpec.kt index efff2c3..0c70087 100644 --- a/src/test/kotlin/io/spine/reflect/CallerFinderSpec.kt +++ b/src/test/kotlin/io/spine/reflect/CallerFinderSpec.kt @@ -87,6 +87,10 @@ internal class CallerFinderSpec { library.caller shouldBe null } + /** + * This is a test of obtaining the caller from inside a class that is + * interested in knowing the caller. + */ @Test fun `obtain the caller of a class`() { Elvis.sign() shouldBe this::class.java diff --git a/src/test/kotlin/io/spine/reflect/given/CallingElvis.kt b/src/test/kotlin/io/spine/reflect/given/CallingElvis.kt index 5ffcf10..dd919f8 100644 --- a/src/test/kotlin/io/spine/reflect/given/CallingElvis.kt +++ b/src/test/kotlin/io/spine/reflect/given/CallingElvis.kt @@ -33,7 +33,7 @@ object Elvis { /** * Obtains the caller of this class. */ - fun whosCalling(): Class<*>? { + fun whoIsCalling(): Class<*>? { val callingClass = findCallerOf(this::class.java, 0)?.let { Class.forName(it.className) } @@ -41,12 +41,12 @@ object Elvis { } fun sign(): Class<*>? { - return whosCalling() + return whoIsCalling() } } object AnybodyHome { fun call(): Class<*>? { - return Elvis.whosCalling() + return Elvis.whoIsCalling() } } From aa7dd0ec4ada3c37e0e72c89bc0bbcd572ce2bf0 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Mon, 6 May 2024 01:15:12 +0100 Subject: [PATCH 16/23] Move check functions into the same file with the interface This way they better describe the parameters. --- src/main/kotlin/io/spine/reflect/Checks.kt | 37 ------------------- .../kotlin/io/spine/reflect/StackGetter.kt | 12 +++++- 2 files changed, 10 insertions(+), 39 deletions(-) delete mode 100644 src/main/kotlin/io/spine/reflect/Checks.kt diff --git a/src/main/kotlin/io/spine/reflect/Checks.kt b/src/main/kotlin/io/spine/reflect/Checks.kt deleted file mode 100644 index 8224a9d..0000000 --- a/src/main/kotlin/io/spine/reflect/Checks.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2024, TeamDev. All rights reserved. - * - * 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 - * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -@file:JvmName("Checks") - -package io.spine.reflect - -internal fun checkMaxDepth(maxDepth: Int) { - require(maxDepth == -1 || maxDepth > 0) { "maxDepth must be > 0 or -1" } -} - -internal fun checkSkipFrames(skipFrames: Int) { - require(skipFrames >= 0) { "skipFrames must be >= 0" } -} diff --git a/src/main/kotlin/io/spine/reflect/StackGetter.kt b/src/main/kotlin/io/spine/reflect/StackGetter.kt index 0aa530c..6c5ff16 100644 --- a/src/main/kotlin/io/spine/reflect/StackGetter.kt +++ b/src/main/kotlin/io/spine/reflect/StackGetter.kt @@ -28,8 +28,8 @@ package io.spine.reflect /** * Interface for finding call site information. * - * @see [ - * Original Java code of Google Flogger](https://github.com/google/flogger/blob/cb9e836a897d36a78309ee8badf5cad4e6a2d3d8/api/src/main/java/com/google/common/flogger/util/StackGetter.java) + * @see + * Original Java code of Google Flogger */ internal interface StackGetter { /** @@ -70,3 +70,11 @@ internal interface StackGetter { skipFrames: Int ): Array } + +internal fun checkMaxDepth(maxDepth: Int) { + require(maxDepth == -1 || maxDepth > 0) { "maxDepth must be > 0 or -1" } +} + +internal fun checkSkipFrames(skipFrames: Int) { + require(skipFrames >= 0) { "skipFrames must be >= 0" } +} From c3580450d741702fac743ee819254584de566b64 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Mon, 6 May 2024 01:15:20 +0100 Subject: [PATCH 17/23] Update build time --- dependencies.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.md b/dependencies.md index df2dc2d..ec3be99 100644 --- a/dependencies.md +++ b/dependencies.md @@ -775,4 +775,4 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Sun May 05 23:42:40 WEST 2024** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). \ No newline at end of file +This report was generated on **Mon May 06 01:14:25 WEST 2024** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). \ No newline at end of file From e91b7b37149f8ad8169a10e1c42135634f9c357d Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Mon, 6 May 2024 11:43:29 +0100 Subject: [PATCH 18/23] Test `CallerFinder.stackForCallerOf()` --- .../io/spine/reflect/CallerFinderSpec.kt | 19 +++++ .../io/spine/reflect/given/LogContext.kt | 80 +++++++++++++++++++ .../io/spine/reflect/given/LoggerCode.kt | 2 + .../kotlin/io/spine/reflect/given/UserCode.kt | 8 ++ 4 files changed, 109 insertions(+) create mode 100644 src/test/kotlin/io/spine/reflect/given/LogContext.kt diff --git a/src/test/kotlin/io/spine/reflect/CallerFinderSpec.kt b/src/test/kotlin/io/spine/reflect/CallerFinderSpec.kt index 0c70087..16e413b 100644 --- a/src/test/kotlin/io/spine/reflect/CallerFinderSpec.kt +++ b/src/test/kotlin/io/spine/reflect/CallerFinderSpec.kt @@ -96,4 +96,23 @@ internal class CallerFinderSpec { Elvis.sign() shouldBe this::class.java AnybodyHome.call() shouldBe AnybodyHome::class.java } + + /** + * Please see [io.spine.reflect.given.LogContext] stub for details. + */ + @Test + fun `obtain trimmed call stack for a class`() { + // We don't care about the skip count here since we call stub `LogContext` directly. + val library = LoggerCode(skipCount = 0) + val code = UserCode(library) + code.someMethod() + + val stack = library.logContext.callerStack + stack shouldNotBe null + stack!! + // This is the element we want to be the caller. + stack[0].className shouldBe UserCode::class.java.name + // This means that logging internals are skipped in the stack trace. + stack[1].className shouldBe CallerFinderSpec::class.java.name + } } diff --git a/src/test/kotlin/io/spine/reflect/given/LogContext.kt b/src/test/kotlin/io/spine/reflect/given/LogContext.kt new file mode 100644 index 0000000..8c46dbb --- /dev/null +++ b/src/test/kotlin/io/spine/reflect/given/LogContext.kt @@ -0,0 +1,80 @@ +/* + * Copyright 2024, TeamDev. All rights reserved. + * + * 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 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.reflect.given + +import io.spine.reflect.CallerFinder.stackForCallerOf + +/** + * A stub mimicking the behavior of a real `LogContext` for the purpose of tests. + * + * Real [`LogContext`](https://github.com/SpineEventEngine/logging/blob/master/flogger/middleware/src/main/java/io/spine/logging/flogger/LogContext.java) + * class is the consumer of + * [CallerFinder.stackForCallerOf][io.spine.reflect.CallerFinder.stackForCallerOf] method. + * + * We recreate the behavior described in comments inside the real `LogContext.postProcess()` method + * to match the expected behavior. + */ +internal abstract class LogContext { + + var message: String? = null + var callerStack: Array? = null + + fun log(message: String) { + if (shouldLog()) { + this.message = message + } + } + + private fun shouldLog(): Boolean { + val shouldLog = postProcess() + return shouldLog + } + + protected open fun postProcess(): Boolean { + // Remember the call stack as if we get it in real `LogContext`. + // We pass `maxDepth` at maximum as the most demanding case. + callerStack = stackForCallerOf(LogContext::class.java, maxDepth = -1, skip = 1) + return true + } +} + +internal open class ChildContext: LogContext() { + + override fun postProcess(): Boolean { + val fromSuper = super.postProcess() + // Simulate overriding. + return fromSuper + } +} + +internal class OtherChildContext: ChildContext() { + override fun postProcess(): Boolean { + val deeperWeGo = super.postProcess() + // Override yet more. + return deeperWeGo + } +} diff --git a/src/test/kotlin/io/spine/reflect/given/LoggerCode.kt b/src/test/kotlin/io/spine/reflect/given/LoggerCode.kt index 5cee3a2..5715db3 100644 --- a/src/test/kotlin/io/spine/reflect/given/LoggerCode.kt +++ b/src/test/kotlin/io/spine/reflect/given/LoggerCode.kt @@ -40,6 +40,8 @@ internal class LoggerCode( var caller: StackTraceElement? = null + val logContext: LogContext = OtherChildContext() + fun logMethod() { internalMethodOne() } diff --git a/src/test/kotlin/io/spine/reflect/given/UserCode.kt b/src/test/kotlin/io/spine/reflect/given/UserCode.kt index fcb1a01..c77d121 100644 --- a/src/test/kotlin/io/spine/reflect/given/UserCode.kt +++ b/src/test/kotlin/io/spine/reflect/given/UserCode.kt @@ -38,4 +38,12 @@ internal class UserCode(private val logger: LoggerCode) { private fun loggingMethod() { logger.logMethod() } + + /** + * Please see [LogContext] stub for details. + */ + fun someMethod() { + val logContext = logger.logContext + logContext.log("INFO: at `someMethod()`") + } } From edcb1a2bdfca3dbf1c28f6793bd5451b361a26a6 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Mon, 6 May 2024 12:09:59 +0100 Subject: [PATCH 19/23] Update build time --- dependencies.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.md b/dependencies.md index ec3be99..86dfb38 100644 --- a/dependencies.md +++ b/dependencies.md @@ -775,4 +775,4 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon May 06 01:14:25 WEST 2024** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). \ No newline at end of file +This report was generated on **Mon May 06 12:09:56 WEST 2024** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). \ No newline at end of file From 071bd27c2b86377877a5046e71f94824b8aa9f3e Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Mon, 6 May 2024 13:13:03 +0100 Subject: [PATCH 20/23] Annotate methods with `@JvmStatic` --- src/main/kotlin/io/spine/reflect/CallerFinder.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/kotlin/io/spine/reflect/CallerFinder.kt b/src/main/kotlin/io/spine/reflect/CallerFinder.kt index 6b33221..c626bf3 100644 --- a/src/main/kotlin/io/spine/reflect/CallerFinder.kt +++ b/src/main/kotlin/io/spine/reflect/CallerFinder.kt @@ -23,6 +23,9 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + +@file:JvmName("CallerFinder") + package io.spine.reflect /** @@ -53,6 +56,7 @@ public object CallerFinder { * `null` if no caller was found (due to incorrect target, wrong skip count or * use of JNI). */ + @JvmStatic public fun findCallerOf(target: Class<*>?, skip: Int): StackTraceElement? { checkSkipCount(skip) return STACK_GETTER.callerOf(target!!, skip + 1) @@ -71,6 +75,7 @@ public object CallerFinder { * the empty array if no caller was found (due to incorrect target, wrong skip count or * use of JNI). */ + @JvmStatic public fun stackForCallerOf( target: Class<*>, maxDepth: Int, From 21991376fba35e969928fd8821585f3cf73ce572 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Mon, 6 May 2024 13:13:11 +0100 Subject: [PATCH 21/23] Update build time --- dependencies.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.md b/dependencies.md index 86dfb38..13dd463 100644 --- a/dependencies.md +++ b/dependencies.md @@ -775,4 +775,4 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon May 06 12:09:56 WEST 2024** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). \ No newline at end of file +This report was generated on **Mon May 06 12:42:00 WEST 2024** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). \ No newline at end of file From ea4f2a96a7d905fa1259644d1c53b1f8ad936372 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Mon, 6 May 2024 13:59:28 +0100 Subject: [PATCH 22/23] Remove dependency on Logging --- buildSrc/src/main/kotlin/jvm-module.gradle.kts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/jvm-module.gradle.kts b/buildSrc/src/main/kotlin/jvm-module.gradle.kts index 4f7b9d0..0d0b264 100644 --- a/buildSrc/src/main/kotlin/jvm-module.gradle.kts +++ b/buildSrc/src/main/kotlin/jvm-module.gradle.kts @@ -148,7 +148,9 @@ fun Module.addDependencies() = dependencies { compileOnlyApi(JavaX.annotations) ErrorProne.annotations.forEach { compileOnlyApi(it) } - implementation(Spine.Logging.lib) + // Logging is not required for `reflect` library. + // Adding it causes a cyclic dependency. + //implementation(Spine.Logging.lib) testImplementation(Guava.testLib) testImplementation(JUnit.runner) From 8191d8d1463493292b00b97d083a667685b5625c Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Mon, 6 May 2024 14:01:19 +0100 Subject: [PATCH 23/23] Update dependency reports --- dependencies.md | 21 +-------------------- pom.xml | 6 ------ 2 files changed, 1 insertion(+), 26 deletions(-) diff --git a/dependencies.md b/dependencies.md index 13dd463..a8d87f7 100644 --- a/dependencies.md +++ b/dependencies.md @@ -15,14 +15,6 @@ * **Project URL:** [https://errorprone.info/error_prone_annotations](https://errorprone.info/error_prone_annotations) * **License:** [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.google.flogger. **Name** : flogger. **Version** : 0.7.4. - * **Project URL:** [https://github.com/google/flogger](https://github.com/google/flogger) - * **License:** [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group** : com.google.flogger. **Name** : flogger-system-backend. **Version** : 0.7.4. - * **Project URL:** [https://github.com/google/flogger](https://github.com/google/flogger) - * **License:** [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group** : com.google.guava. **Name** : failureaccess. **Version** : 1.0.1. * **Project URL:** [https://github.com/google/guava/](https://github.com/google/guava/) * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -49,11 +41,6 @@ 1. **Group** : com.google.protobuf. **Name** : protobuf-kotlin. **Version** : 3.25.1. * **License:** [BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -1. **Group** : org.checkerframework. **Name** : checker-compat-qual. **Version** : 2.5.3. - * **Project URL:** [https://checkerframework.org](https://checkerframework.org) - * **License:** [GNU General Public License, version 2 (GPL2), with the classpath exception](http://www.gnu.org/software/classpath/license.html) - * **License:** [The MIT License](http://opensource.org/licenses/MIT) - 1. **Group** : org.checkerframework. **Name** : checker-qual. **Version** : 3.40.0. * **Project URL:** [https://checkerframework.org/](https://checkerframework.org/) * **License:** [The MIT License](http://opensource.org/licenses/MIT) @@ -70,7 +57,6 @@ * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-stdlib-common. **Version** : 1.9.23.**No license information found** 1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-stdlib-jdk7. **Version** : 1.9.23. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -79,11 +65,6 @@ * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.ow2.asm. **Name** : asm. **Version** : 9.2. - * **Project URL:** [http://asm.ow2.io/](http://asm.ow2.io/) - * **License:** [BSD-3-Clause](https://asm.ow2.io/license.html) - * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - ## Compile, tests, and tooling 1. **Group** : aopalliance. **Name** : aopalliance. **Version** : 1.0. * **Project URL:** [http://aopalliance.sourceforge.net](http://aopalliance.sourceforge.net) @@ -775,4 +756,4 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon May 06 12:42:00 WEST 2024** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). \ No newline at end of file +This report was generated on **Mon May 06 14:01:01 WEST 2024** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). \ No newline at end of file diff --git a/pom.xml b/pom.xml index 7606aea..68e86b1 100644 --- a/pom.xml +++ b/pom.xml @@ -47,12 +47,6 @@ all modules and does not describe the project structure per-subproject. 3.25.1 compile - - io.spine - spine-logging - 2.0.0-SNAPSHOT.233 - compile - org.jetbrains.kotlin kotlin-reflect