From 437dd84c53e3bdb9f0311ab2e66a9df98184d3b9 Mon Sep 17 00:00:00 2001 From: "serhii.lekariev" Date: Wed, 10 Jun 2020 16:57:02 +0300 Subject: [PATCH 01/43] Allow registering env types using their `class` - `@Internal` use only. Bring back missing `@Immutable`s. Bump version. --- .../main/java/io/spine/base/Environment.java | 54 +++++++++++++++++++ .../main/java/io/spine/base/Production.java | 3 ++ base/src/main/java/io/spine/base/Tests.java | 4 +- version.gradle.kts | 2 +- 4 files changed, 61 insertions(+), 2 deletions(-) diff --git a/base/src/main/java/io/spine/base/Environment.java b/base/src/main/java/io/spine/base/Environment.java index bfa8702caa..a32ad9b91e 100644 --- a/base/src/main/java/io/spine/base/Environment.java +++ b/base/src/main/java/io/spine/base/Environment.java @@ -22,12 +22,19 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; +import com.google.common.reflect.Invokable; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import io.spine.annotation.Internal; import io.spine.annotation.SPI; import org.checkerframework.checker.nullness.qual.Nullable; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + import static com.google.common.base.Preconditions.checkNotNull; +import static io.spine.util.Exceptions.newIllegalArgumentException; import static io.spine.util.Exceptions.newIllegalStateException; +import static java.lang.String.format; /** * Provides information about the environment (current platform used, etc.). @@ -159,6 +166,34 @@ public Environment register(EnvironmentType environmentType) { return this; } + /** + * Remembers the specified environment type, allowing {@linkplain #is(Class) to + * determine whether it's enabled} later. + * + *

The specified {@code type} must have a package-private constructor. + * + *

Otherwise, behaves like {@link #register(EnvironmentType)}. + * + * @param type + * environment type to register + * @return this instance of {@code Environment} + */ + @Internal + @CanIgnoreReturnValue + Environment register(Class type) { + try { + Constructor noArgsCtor = type.getConstructor(); + checkCtorAccessLevel(noArgsCtor); + EnvironmentType envTypeInstance = noArgsCtor.newInstance(); + return register(envTypeInstance); + } catch (NoSuchMethodException | IllegalAccessException | + InstantiationException | InvocationTargetException e) { + String message = "Could not register environment type `%s` by class. You may try " + + "creating an `EnvironmentType` instance."; + throw newIllegalStateException(e, message, type); + } + } + /** Returns the singleton instance. */ public static Environment instance() { return INSTANCE; @@ -310,4 +345,23 @@ private EnvironmentType currentType() { throw newIllegalStateException("`Environment` could not find an active environment type."); } + + private static void + checkCtorAccessLevel(Constructor constructor) { + Invokable ctor = + Invokable.from(constructor); + + if (!ctor.isPackagePrivate()) { + Class envType = constructor.getDeclaringClass(); + String message = format( + "`%s` constructor must be package-private to be registered in `Environment`.", + constructor.getDeclaringClass()); + if (ctor.isPublic()) { + message += format( + " As `%s` has a public constructor, you may use `Environment.register(envInstance)`.", + envType); + } + throw newIllegalArgumentException(message); + } + } } diff --git a/base/src/main/java/io/spine/base/Production.java b/base/src/main/java/io/spine/base/Production.java index a512549049..ca8a352cdc 100644 --- a/base/src/main/java/io/spine/base/Production.java +++ b/base/src/main/java/io/spine/base/Production.java @@ -20,11 +20,14 @@ package io.spine.base; +import com.google.errorprone.annotations.Immutable; + /** * A non-testing environment. * *

If the system is not in the {@link Tests} environment, it is in the production environment. */ +@Immutable public final class Production extends EnvironmentType { /** diff --git a/base/src/main/java/io/spine/base/Tests.java b/base/src/main/java/io/spine/base/Tests.java index 6b04200843..24a2d69b6d 100644 --- a/base/src/main/java/io/spine/base/Tests.java +++ b/base/src/main/java/io/spine/base/Tests.java @@ -23,6 +23,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; +import com.google.errorprone.annotations.Immutable; import java.util.regex.Pattern; @@ -33,6 +34,7 @@ * *

This option is mutually exclusive with {@link Production}, i.e. one of them is always enabled. */ +@Immutable @SuppressWarnings("AccessOfSystemProperties" /* is necessary for this class to function */) public final class Tests extends EnvironmentType { @@ -56,7 +58,7 @@ public final class Tests extends EnvironmentType { * *

All {@code Tests} instances are immutable and equivalent. */ - public Tests() { + Tests() { super(); } diff --git a/version.gradle.kts b/version.gradle.kts index 3e6bc8f84f..b7711b1c45 100644 --- a/version.gradle.kts +++ b/version.gradle.kts @@ -25,7 +25,7 @@ * as we want to manage the versions in a single source. */ -val SPINE_VERSION = "1.5.17" +val SPINE_VERSION = "1.5.18" project.extra.apply { this["spineVersion"] = SPINE_VERSION From 456a0396705f4f59ac5b53727ed2eaff5036071f Mon Sep 17 00:00:00 2001 From: "serhii.lekariev" Date: Wed, 10 Jun 2020 17:02:06 +0300 Subject: [PATCH 02/43] Decrease `Production` env type access level. --- base/src/main/java/io/spine/base/Production.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/src/main/java/io/spine/base/Production.java b/base/src/main/java/io/spine/base/Production.java index ca8a352cdc..a8a966e309 100644 --- a/base/src/main/java/io/spine/base/Production.java +++ b/base/src/main/java/io/spine/base/Production.java @@ -35,7 +35,7 @@ public final class Production extends EnvironmentType { * *

All {@code Production} instances are immutable and equivalent. */ - public Production() { + Production() { super(); } From 443fdeb021bccd1aa99317ce4d88cef1f41eaca9 Mon Sep 17 00:00:00 2001 From: "serhii.lekariev" Date: Wed, 10 Jun 2020 17:15:34 +0300 Subject: [PATCH 03/43] Fix exception type, add tests. --- .../main/java/io/spine/base/Environment.java | 2 +- .../java/io/spine/base/EnvironmentTest.java | 51 +++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/base/src/main/java/io/spine/base/Environment.java b/base/src/main/java/io/spine/base/Environment.java index a32ad9b91e..e821d108cc 100644 --- a/base/src/main/java/io/spine/base/Environment.java +++ b/base/src/main/java/io/spine/base/Environment.java @@ -190,7 +190,7 @@ Environment register(Class type) { InstantiationException | InvocationTargetException e) { String message = "Could not register environment type `%s` by class. You may try " + "creating an `EnvironmentType` instance."; - throw newIllegalStateException(e, message, type); + throw newIllegalArgumentException(e, message, type); } } diff --git a/base/src/test/java/io/spine/base/EnvironmentTest.java b/base/src/test/java/io/spine/base/EnvironmentTest.java index d6e27579f5..6f2d4033db 100644 --- a/base/src/test/java/io/spine/base/EnvironmentTest.java +++ b/base/src/test/java/io/spine/base/EnvironmentTest.java @@ -35,6 +35,7 @@ import static com.google.common.truth.Truth.assertThat; import static io.spine.base.Tests.ENV_KEY_TESTS; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; @DisplayName("Environment utility class should") @SuppressWarnings("AccessOfSystemProperties") @@ -236,6 +237,56 @@ void returnCustomInstnace() { assertThat(environment.type()).isInstanceOf(Local.class); } + @Nested + @DisplayName("disallow to register types by class if") + class ProhibitRegistering { + + @Test + @DisplayName("they have a public ctor") + void registerCustomPublicCtor() { + assertThrows(IllegalArgumentException.class, () -> environment.register(Local.class)); + } + + @Test + @DisplayName("they have a private ctor") + void registerCustomPrivateCtor() { + assertThrows(IllegalArgumentException.class, + () -> environment.register(HiddenEnvironment.class)); + } + + @Test + @DisplayName("they have a protected ctor") + void registerCustomPackagePrivateCtor() { + assertThrows(IllegalArgumentException.class, + () -> environment.register(ProtectedEnvironment.class)); + } + + private class HiddenEnvironment extends EnvironmentType { + + private HiddenEnvironment() { + } + + @Override + protected boolean enabled() { + return false; + } + } + + private class ProtectedEnvironment extends EnvironmentType { + + protected ProtectedEnvironment() { + } + + @Override + protected boolean enabled() { + return false; + } + } + } + + @Test + @DisplayName("disallow to ") + private static void register(EnvironmentType... types) { for (EnvironmentType type : types) { Environment.instance() From 6760997753d780a3130d67ded2db470ffe192982 Mon Sep 17 00:00:00 2001 From: "serhii.lekariev" Date: Wed, 10 Jun 2020 17:25:52 +0300 Subject: [PATCH 04/43] Check whether the env type has a parameterless ctor. --- .../main/java/io/spine/base/Environment.java | 13 ++++++++++++ .../java/io/spine/base/EnvironmentTest.java | 21 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/base/src/main/java/io/spine/base/Environment.java b/base/src/main/java/io/spine/base/Environment.java index e821d108cc..0ca408e0de 100644 --- a/base/src/main/java/io/spine/base/Environment.java +++ b/base/src/main/java/io/spine/base/Environment.java @@ -182,6 +182,7 @@ public Environment register(EnvironmentType environmentType) { @CanIgnoreReturnValue Environment register(Class type) { try { + checkHasParameterlessCtor(type); Constructor noArgsCtor = type.getConstructor(); checkCtorAccessLevel(noArgsCtor); EnvironmentType envTypeInstance = noArgsCtor.newInstance(); @@ -364,4 +365,16 @@ private EnvironmentType currentType() { throw newIllegalArgumentException(message); } } + + private static void checkHasParameterlessCtor(Class type) { + for (Constructor constructor : type.getDeclaredConstructors()) { + if (constructor.getParameterCount() == 0) { + return; + } + } + + throw newIllegalArgumentException("To register `%` by class, it must " + + "have a parameterless package-private constructor.", + type.getSimpleName()); + } } diff --git a/base/src/test/java/io/spine/base/EnvironmentTest.java b/base/src/test/java/io/spine/base/EnvironmentTest.java index 6f2d4033db..2482c66284 100644 --- a/base/src/test/java/io/spine/base/EnvironmentTest.java +++ b/base/src/test/java/io/spine/base/EnvironmentTest.java @@ -261,6 +261,27 @@ void registerCustomPackagePrivateCtor() { () -> environment.register(ProtectedEnvironment.class)); } + @Test + @DisplayName("they do not have a parameterless ctor") + void registerCustomCtorWithParameters() { + assertThrows(IllegalArgumentException.class, + () -> environment.register(ValueDependantEnvironment.class)); + } + + private class ValueDependantEnvironment extends EnvironmentType{ + + private final boolean enabled; + + ValueDependantEnvironment(boolean enabled) { + this.enabled = enabled; + } + + @Override + protected boolean enabled() { + return enabled; + } + } + private class HiddenEnvironment extends EnvironmentType { private HiddenEnvironment() { From 9b3e0778610165d08aeee8af4f59527513d60025 Mon Sep 17 00:00:00 2001 From: "serhii.lekariev" Date: Wed, 10 Jun 2020 17:31:03 +0300 Subject: [PATCH 05/43] Fix PMD. --- base/src/main/java/io/spine/base/Environment.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/base/src/main/java/io/spine/base/Environment.java b/base/src/main/java/io/spine/base/Environment.java index 0ca408e0de..3891fac479 100644 --- a/base/src/main/java/io/spine/base/Environment.java +++ b/base/src/main/java/io/spine/base/Environment.java @@ -354,15 +354,16 @@ private EnvironmentType currentType() { if (!ctor.isPackagePrivate()) { Class envType = constructor.getDeclaringClass(); - String message = format( + StringBuilder message = new StringBuilder(); + message.append(format( "`%s` constructor must be package-private to be registered in `Environment`.", - constructor.getDeclaringClass()); + constructor.getDeclaringClass())); if (ctor.isPublic()) { - message += format( + message.append(format( " As `%s` has a public constructor, you may use `Environment.register(envInstance)`.", - envType); + envType)); } - throw newIllegalArgumentException(message); + throw newIllegalArgumentException(message.toString()); } } From 5453ce2fc697f2140b5e1efd3a5ac32283959b17 Mon Sep 17 00:00:00 2001 From: "serhii.lekariev" Date: Wed, 10 Jun 2020 17:32:47 +0300 Subject: [PATCH 06/43] Fix documentation. --- base/src/main/java/io/spine/base/Environment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/src/main/java/io/spine/base/Environment.java b/base/src/main/java/io/spine/base/Environment.java index 3891fac479..532e3e6678 100644 --- a/base/src/main/java/io/spine/base/Environment.java +++ b/base/src/main/java/io/spine/base/Environment.java @@ -170,7 +170,7 @@ public Environment register(EnvironmentType environmentType) { * Remembers the specified environment type, allowing {@linkplain #is(Class) to * determine whether it's enabled} later. * - *

The specified {@code type} must have a package-private constructor. + *

The specified {@code type} must have a parameterless package-private constructor. * *

Otherwise, behaves like {@link #register(EnvironmentType)}. * From b4233550678f63cdd183f5f074d07215a58ed163 Mon Sep 17 00:00:00 2001 From: "serhii.lekariev" Date: Wed, 10 Jun 2020 17:40:07 +0300 Subject: [PATCH 07/43] Fix a wrong method call. Tweak the tests so that they check that the registration by class works. --- .../main/java/io/spine/base/Environment.java | 2 +- .../java/io/spine/base/EnvironmentTest.java | 35 +++++++++++-------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/base/src/main/java/io/spine/base/Environment.java b/base/src/main/java/io/spine/base/Environment.java index 532e3e6678..5c15f6e248 100644 --- a/base/src/main/java/io/spine/base/Environment.java +++ b/base/src/main/java/io/spine/base/Environment.java @@ -183,7 +183,7 @@ public Environment register(EnvironmentType environmentType) { Environment register(Class type) { try { checkHasParameterlessCtor(type); - Constructor noArgsCtor = type.getConstructor(); + Constructor noArgsCtor = type.getDeclaredConstructor(); checkCtorAccessLevel(noArgsCtor); EnvironmentType envTypeInstance = noArgsCtor.newInstance(); return register(envTypeInstance); diff --git a/base/src/test/java/io/spine/base/EnvironmentTest.java b/base/src/test/java/io/spine/base/EnvironmentTest.java index 2482c66284..346b4e75b5 100644 --- a/base/src/test/java/io/spine/base/EnvironmentTest.java +++ b/base/src/test/java/io/spine/base/EnvironmentTest.java @@ -183,7 +183,8 @@ class CustomEnvTypes { @Test @DisplayName("allow to provide user defined environment types") void provideCustomTypes() { - register(new Staging(), new Local()); + environment.register(new Staging()) + .register(new Local()); // Now that `Environment` knows about `LOCAL`, it should use it as fallback. assertThat(environment.is(Local.class)).isTrue(); @@ -202,7 +203,7 @@ void fallBack() { @Test @DisplayName("follow assignment-compatibility when determining the type") void polymorphicEnv() { - register(new AppEngineStandard()); + environment.register(new AppEngineStandard()); AppEngineStandard.enable(); assertThat(environment.is(AppEngine.class)).isTrue(); @@ -218,7 +219,8 @@ void determineUsingType() { @Test @DisplayName("detect the current custom environment in presence of custom types") void determineUsingTypeInPresenceOfCustom() { - register(new Staging(), new Local()); + environment.register(new Staging()) + .register(new Local()); assertThat(environment.is(Local.class)).isTrue(); } @@ -231,8 +233,9 @@ void returnInstance() { @Test @DisplayName("return the instance of a custom environment type") - void returnCustomInstnace() { - register(new Local(), new Staging()); + void returnCustomInstance() { + environment.register(Local.class) + .register(Staging.class); assertThat(environment.type()).isInstanceOf(Local.class); } @@ -268,7 +271,7 @@ void registerCustomCtorWithParameters() { () -> environment.register(ValueDependantEnvironment.class)); } - private class ValueDependantEnvironment extends EnvironmentType{ + private class ValueDependantEnvironment extends EnvironmentType { private final boolean enabled; @@ -305,17 +308,13 @@ protected boolean enabled() { } } - @Test - @DisplayName("disallow to ") + static final class Local extends EnvironmentType { - private static void register(EnvironmentType... types) { - for (EnvironmentType type : types) { - Environment.instance() - .register(type); + /** + * A package-private parameterless ctor allows to register this type by class. + */ + Local() { } - } - - static final class Local extends EnvironmentType { @Override public boolean enabled() { @@ -328,6 +327,12 @@ static final class Staging extends EnvironmentType { static final String STAGING_ENV_TYPE_KEY = "io.spine.base.EnvironmentTest.is_staging"; + /** + * A package-private parameterless ctor allows to register this type by class. + */ + Staging() { + } + @Override public boolean enabled() { return String.valueOf(true) From b29fc3559144d069e617777f65e481d1393767b1 Mon Sep 17 00:00:00 2001 From: "serhii.lekariev" Date: Wed, 10 Jun 2020 17:42:45 +0300 Subject: [PATCH 08/43] Tweak doc wording. --- base/src/main/java/io/spine/base/Environment.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/src/main/java/io/spine/base/Environment.java b/base/src/main/java/io/spine/base/Environment.java index 5c15f6e248..7e14c82800 100644 --- a/base/src/main/java/io/spine/base/Environment.java +++ b/base/src/main/java/io/spine/base/Environment.java @@ -357,11 +357,11 @@ private EnvironmentType currentType() { StringBuilder message = new StringBuilder(); message.append(format( "`%s` constructor must be package-private to be registered in `Environment`.", - constructor.getDeclaringClass())); + envType.getSimpleName())); if (ctor.isPublic()) { message.append(format( " As `%s` has a public constructor, you may use `Environment.register(envInstance)`.", - envType)); + envType.getSimpleName())); } throw newIllegalArgumentException(message.toString()); } From 9b725ed1575405e6b102d3d4be132e155e0f3412 Mon Sep 17 00:00:00 2001 From: "serhii.lekariev" Date: Wed, 10 Jun 2020 17:45:56 +0300 Subject: [PATCH 09/43] Fix tests. --- base/src/test/java/io/spine/base/EnvironmentTest.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/base/src/test/java/io/spine/base/EnvironmentTest.java b/base/src/test/java/io/spine/base/EnvironmentTest.java index 346b4e75b5..f942d1a607 100644 --- a/base/src/test/java/io/spine/base/EnvironmentTest.java +++ b/base/src/test/java/io/spine/base/EnvironmentTest.java @@ -247,7 +247,7 @@ class ProhibitRegistering { @Test @DisplayName("they have a public ctor") void registerCustomPublicCtor() { - assertThrows(IllegalArgumentException.class, () -> environment.register(Local.class)); + assertThrows(IllegalArgumentException.class, () -> environment.register(Travis.class)); } @Test @@ -344,6 +344,10 @@ public boolean enabled() { @SuppressWarnings("unused" /* The only variant is used. */) static final class Travis extends EnvironmentType { + @SuppressWarnings("WeakerAccess" /* Classes with public ctors shouldn't be registrable by their classes, this class tests for it. */) + public Travis() { + } + @Override public boolean enabled() { return false; From 0027d3cf0b71997b577309659a4a9084873aa992 Mon Sep 17 00:00:00 2001 From: "serhii.lekariev" Date: Wed, 10 Jun 2020 17:50:57 +0300 Subject: [PATCH 10/43] Fix warning suppression wording. --- base/src/test/java/io/spine/base/EnvironmentTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/src/test/java/io/spine/base/EnvironmentTest.java b/base/src/test/java/io/spine/base/EnvironmentTest.java index f942d1a607..740663f2ea 100644 --- a/base/src/test/java/io/spine/base/EnvironmentTest.java +++ b/base/src/test/java/io/spine/base/EnvironmentTest.java @@ -344,7 +344,7 @@ public boolean enabled() { @SuppressWarnings("unused" /* The only variant is used. */) static final class Travis extends EnvironmentType { - @SuppressWarnings("WeakerAccess" /* Classes with public ctors shouldn't be registrable by their classes, this class tests for it. */) + @SuppressWarnings("WeakerAccess" /* Environment types with public ctors shouldn't be registrable by their classes, this one tests for it. */) public Travis() { } From e7f15bf6dc614af8b786eaa02e959c90993c8307 Mon Sep 17 00:00:00 2001 From: "serhii.lekariev" Date: Thu, 11 Jun 2020 10:58:25 +0300 Subject: [PATCH 11/43] Extract a utility. --- .../main/java/io/spine/base/Environment.java | 45 +----- .../java/io/spine/base/EnvironmentTypes.java | 88 +++++++++++ .../java/io/spine/base/EnvironmentTest.java | 68 --------- .../io/spine/base/EnvironmentTypesTest.java | 137 ++++++++++++++++++ 4 files changed, 231 insertions(+), 107 deletions(-) create mode 100644 base/src/main/java/io/spine/base/EnvironmentTypes.java create mode 100644 base/src/test/java/io/spine/base/EnvironmentTypesTest.java diff --git a/base/src/main/java/io/spine/base/Environment.java b/base/src/main/java/io/spine/base/Environment.java index 7e14c82800..9d85e5e061 100644 --- a/base/src/main/java/io/spine/base/Environment.java +++ b/base/src/main/java/io/spine/base/Environment.java @@ -22,7 +22,6 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; -import com.google.common.reflect.Invokable; import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.spine.annotation.Internal; import io.spine.annotation.SPI; @@ -34,7 +33,6 @@ import static com.google.common.base.Preconditions.checkNotNull; import static io.spine.util.Exceptions.newIllegalArgumentException; import static io.spine.util.Exceptions.newIllegalStateException; -import static java.lang.String.format; /** * Provides information about the environment (current platform used, etc.). @@ -182,15 +180,15 @@ public Environment register(EnvironmentType environmentType) { @CanIgnoreReturnValue Environment register(Class type) { try { - checkHasParameterlessCtor(type); - Constructor noArgsCtor = type.getDeclaredConstructor(); - checkCtorAccessLevel(noArgsCtor); - EnvironmentType envTypeInstance = noArgsCtor.newInstance(); + EnvironmentTypes.checkCanRegisterByClass(type); + Constructor ctor = type.getDeclaredConstructor(); + EnvironmentType envTypeInstance = ctor.newInstance(); return register(envTypeInstance); } catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) { String message = "Could not register environment type `%s` by class. You may try " + - "creating an `EnvironmentType` instance."; + "creating an `EnvironmentType` instance. To register an instance by class, " + + "its constructor must be parameterless and package-private."; throw newIllegalArgumentException(e, message, type); } } @@ -334,6 +332,7 @@ private EnvironmentType cachedOrCalculated() { EnvironmentType result = currentEnvType != null ? currentEnvType : currentType(); + this.currentEnvType = result; return result; } @@ -346,36 +345,4 @@ private EnvironmentType currentType() { throw newIllegalStateException("`Environment` could not find an active environment type."); } - - private static void - checkCtorAccessLevel(Constructor constructor) { - Invokable ctor = - Invokable.from(constructor); - - if (!ctor.isPackagePrivate()) { - Class envType = constructor.getDeclaringClass(); - StringBuilder message = new StringBuilder(); - message.append(format( - "`%s` constructor must be package-private to be registered in `Environment`.", - envType.getSimpleName())); - if (ctor.isPublic()) { - message.append(format( - " As `%s` has a public constructor, you may use `Environment.register(envInstance)`.", - envType.getSimpleName())); - } - throw newIllegalArgumentException(message.toString()); - } - } - - private static void checkHasParameterlessCtor(Class type) { - for (Constructor constructor : type.getDeclaredConstructors()) { - if (constructor.getParameterCount() == 0) { - return; - } - } - - throw newIllegalArgumentException("To register `%` by class, it must " + - "have a parameterless package-private constructor.", - type.getSimpleName()); - } } diff --git a/base/src/main/java/io/spine/base/EnvironmentTypes.java b/base/src/main/java/io/spine/base/EnvironmentTypes.java new file mode 100644 index 0000000000..f53ca94a10 --- /dev/null +++ b/base/src/main/java/io/spine/base/EnvironmentTypes.java @@ -0,0 +1,88 @@ +/* + * Copyright 2020, TeamDev. All rights reserved. + * + * 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.base; + +import com.google.common.reflect.Invokable; +import com.google.errorprone.annotations.CanIgnoreReturnValue; + +import java.lang.reflect.Constructor; + +import static io.spine.util.Exceptions.newIllegalArgumentException; +import static java.lang.String.format; + +/** + * A utility class for working with and verifying {@link EnvironmentType} extenders. + */ +final class EnvironmentTypes { + + private EnvironmentTypes() { + } + + /** + * Checks whether the specified environment type may be registered using it's class. + * + *

To register the type by its class it must have a package-private parameterless + * constructor. + * + * @param type + * environment to register + */ + @CanIgnoreReturnValue + static > C checkCanRegisterByClass(C type) { + Constructor parameterlessCtor = checkHasParameterlessCtor(type); + checkCtorAccessLevel(parameterlessCtor); + return type; + } + + private static void + checkCtorAccessLevel(Constructor constructor) { + Invokable ctor = + Invokable.from(constructor); + + if (!ctor.isPackagePrivate()) { + Class envType = constructor.getDeclaringClass(); + StringBuilder message = new StringBuilder(); + message.append(format( + "`%s` constructor must be package-private to be registered in `Environment`.", + envType.getSimpleName())); + if (ctor.isPublic()) { + message.append(format( + " As `%s` has a public constructor, you may use `Environment.register(envInstance)`.", + envType.getSimpleName())); + } + throw newIllegalArgumentException(message.toString()); + } + } + + @SuppressWarnings("unchecked") + private static Constructor + checkHasParameterlessCtor(Class type) { + for (Constructor constructor : type.getDeclaredConstructors()) { + if (constructor.getParameterCount() == 0) { + return (Constructor) constructor; + } + } + + throw newIllegalArgumentException("To register `%s` by class, it must " + + "have a parameterless package-private constructor.", + type.getSimpleName()); + } +} diff --git a/base/src/test/java/io/spine/base/EnvironmentTest.java b/base/src/test/java/io/spine/base/EnvironmentTest.java index 740663f2ea..fd814fd50d 100644 --- a/base/src/test/java/io/spine/base/EnvironmentTest.java +++ b/base/src/test/java/io/spine/base/EnvironmentTest.java @@ -240,74 +240,6 @@ void returnCustomInstance() { assertThat(environment.type()).isInstanceOf(Local.class); } - @Nested - @DisplayName("disallow to register types by class if") - class ProhibitRegistering { - - @Test - @DisplayName("they have a public ctor") - void registerCustomPublicCtor() { - assertThrows(IllegalArgumentException.class, () -> environment.register(Travis.class)); - } - - @Test - @DisplayName("they have a private ctor") - void registerCustomPrivateCtor() { - assertThrows(IllegalArgumentException.class, - () -> environment.register(HiddenEnvironment.class)); - } - - @Test - @DisplayName("they have a protected ctor") - void registerCustomPackagePrivateCtor() { - assertThrows(IllegalArgumentException.class, - () -> environment.register(ProtectedEnvironment.class)); - } - - @Test - @DisplayName("they do not have a parameterless ctor") - void registerCustomCtorWithParameters() { - assertThrows(IllegalArgumentException.class, - () -> environment.register(ValueDependantEnvironment.class)); - } - - private class ValueDependantEnvironment extends EnvironmentType { - - private final boolean enabled; - - ValueDependantEnvironment(boolean enabled) { - this.enabled = enabled; - } - - @Override - protected boolean enabled() { - return enabled; - } - } - - private class HiddenEnvironment extends EnvironmentType { - - private HiddenEnvironment() { - } - - @Override - protected boolean enabled() { - return false; - } - } - - private class ProtectedEnvironment extends EnvironmentType { - - protected ProtectedEnvironment() { - } - - @Override - protected boolean enabled() { - return false; - } - } - } - static final class Local extends EnvironmentType { /** diff --git a/base/src/test/java/io/spine/base/EnvironmentTypesTest.java b/base/src/test/java/io/spine/base/EnvironmentTypesTest.java new file mode 100644 index 0000000000..7d63c5eb96 --- /dev/null +++ b/base/src/test/java/io/spine/base/EnvironmentTypesTest.java @@ -0,0 +1,137 @@ +/* + * Copyright 2020, TeamDev. All rights reserved. + * + * 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.base; + +import io.spine.testing.UtilityClassTest; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import static com.google.common.truth.Truth.assertThat; +import static io.spine.base.EnvironmentTypes.checkCanRegisterByClass; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@DisplayName("`EnvironmentTypes` should") +class EnvironmentTypesTest extends UtilityClassTest { + + EnvironmentTypesTest() { + super(EnvironmentTypes.class); + } + + @Nested + @DisplayName("disallow to register types by class if") + class ProhibitRegistering { + + @Test + @DisplayName("they have a public ctor") + void registerCustomPublicCtor() { + assertThrows(IllegalArgumentException.class, + () -> checkCanRegisterByClass(VisibleEnvironment.class)); + } + + @Test + @DisplayName("they have a private ctor") + void registerCustomPrivateCtor() { + assertThrows(IllegalArgumentException.class, + () -> checkCanRegisterByClass(HiddenEnvironment.class)); + } + + @Test + @DisplayName("they have a protected ctor") + void registerCustomPackagePrivateCtor() { + assertThrows(IllegalArgumentException.class, + () -> checkCanRegisterByClass(ProtectedEnvironment.class)); + } + + @Test + @DisplayName("they do not have a parameterless ctor") + void registerCustomCtorWithParameters() { + assertThrows(IllegalArgumentException.class, + () -> checkCanRegisterByClass(ValueDependantEnvironment.class)); + } + + private class ValueDependantEnvironment extends EnvironmentType { + + private final boolean enabled; + + ValueDependantEnvironment(boolean enabled) { + this.enabled = enabled; + } + + @Override + protected boolean enabled() { + return enabled; + } + } + + private class HiddenEnvironment extends EnvironmentType { + + private HiddenEnvironment() { + } + + @Override + protected boolean enabled() { + return false; + } + } + + private class ProtectedEnvironment extends EnvironmentType { + + protected ProtectedEnvironment() { + } + + @Override + protected boolean enabled() { + return false; + } + } + + private class VisibleEnvironment extends EnvironmentType { + + @SuppressWarnings("PublicConstructorInNonPublicClass") + public VisibleEnvironment() { + } + + @Override + protected boolean enabled() { + return true; + } + } + } + + @Test + @DisplayName("allow to register an env with a package-private parameterless ctor") + void allow() { + Class local = Local.class; + assertThat(checkCanRegisterByClass(Local.class)).isSameInstanceAs(local); + } + + private static class Local extends EnvironmentType { + + Local() { + } + + @Override + protected boolean enabled() { + return true; + } + } +} From c1da10f08c0172070496f794ae69d6e8f1edcae0 Mon Sep 17 00:00:00 2001 From: "serhii.lekariev" Date: Thu, 11 Jun 2020 11:00:52 +0300 Subject: [PATCH 12/43] Fix formatting. --- base/src/main/java/io/spine/base/Environment.java | 4 ++-- .../test/java/io/spine/base/EnvironmentTest.java | 14 +++++--------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/base/src/main/java/io/spine/base/Environment.java b/base/src/main/java/io/spine/base/Environment.java index 9d85e5e061..0eacc97654 100644 --- a/base/src/main/java/io/spine/base/Environment.java +++ b/base/src/main/java/io/spine/base/Environment.java @@ -187,8 +187,8 @@ Environment register(Class type) { } catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) { String message = "Could not register environment type `%s` by class. You may try " + - "creating an `EnvironmentType` instance. To register an instance by class, " + - "its constructor must be parameterless and package-private."; + "creating an `EnvironmentType` instance. To register an environment type by" + + " class, its constructor must be parameterless and package-private."; throw newIllegalArgumentException(e, message, type); } } diff --git a/base/src/test/java/io/spine/base/EnvironmentTest.java b/base/src/test/java/io/spine/base/EnvironmentTest.java index fd814fd50d..90c00f79f6 100644 --- a/base/src/test/java/io/spine/base/EnvironmentTest.java +++ b/base/src/test/java/io/spine/base/EnvironmentTest.java @@ -35,7 +35,6 @@ import static com.google.common.truth.Truth.assertThat; import static io.spine.base.Tests.ENV_KEY_TESTS; import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; @DisplayName("Environment utility class should") @SuppressWarnings("AccessOfSystemProperties") @@ -219,8 +218,8 @@ void determineUsingType() { @Test @DisplayName("detect the current custom environment in presence of custom types") void determineUsingTypeInPresenceOfCustom() { - environment.register(new Staging()) - .register(new Local()); + environment.register(Local.class) + .register(Staging.class); assertThat(environment.is(Local.class)).isTrue(); } @@ -240,11 +239,9 @@ void returnCustomInstance() { assertThat(environment.type()).isInstanceOf(Local.class); } + @Immutable static final class Local extends EnvironmentType { - /** - * A package-private parameterless ctor allows to register this type by class. - */ Local() { } @@ -255,6 +252,7 @@ public boolean enabled() { } } + @Immutable static final class Staging extends EnvironmentType { static final String STAGING_ENV_TYPE_KEY = "io.spine.base.EnvironmentTest.is_staging"; @@ -273,11 +271,9 @@ public boolean enabled() { } @Immutable - @SuppressWarnings("unused" /* The only variant is used. */) static final class Travis extends EnvironmentType { - @SuppressWarnings("WeakerAccess" /* Environment types with public ctors shouldn't be registrable by their classes, this one tests for it. */) - public Travis() { + Travis() { } @Override From c5cae0c9b780b4fcdfff8bc028d3bc4c4754ee08 Mon Sep 17 00:00:00 2001 From: "serhii.lekariev" Date: Thu, 11 Jun 2020 11:20:51 +0300 Subject: [PATCH 13/43] Test env type caching. --- .../main/java/io/spine/base/Environment.java | 18 +++++++++++++++ .../java/io/spine/base/EnvironmentTest.java | 23 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/base/src/main/java/io/spine/base/Environment.java b/base/src/main/java/io/spine/base/Environment.java index 0eacc97654..fce73c8176 100644 --- a/base/src/main/java/io/spine/base/Environment.java +++ b/base/src/main/java/io/spine/base/Environment.java @@ -109,6 +109,24 @@ * } * * + *

Caching

+ *

Be aware that {@code Environment} caches the {@code EnvironmentType} once its calculated. + * This means that if one environment type has been found to be active, it gets cached. If later it + * becomes logically inactive, e.g. the environment variable that's used to check the env type + * changes, {@code Environment} is still going to return the cached value. For example: + *

+ *     EnvironmentType awsLambda = new AwsLambda();
+ *     assertThat(Environment.instance().is(AwsLambda.class)).isTrue();
+ *
+ *     System.clearProperty(AwsLambda.AWS_ENV_VARIABLE);
+ *
+ *     // Even though `AwsLambda` is technically not active, we have cached the value,
+ *     // and `is(AwsLambda)` is `true`.
+ *     assertThat(Environment.instance().is(AwsLambda.class)).isTrue();
+ *
+ * 
+ * + * *

When registering custom types, please ensure their mutual exclusivity. * If two or more environment types {@linkplain EnvironmentType#enabled() consider themselves * enabled} at the same time, the behaviour of {@link #is(Class)}} is undefined. diff --git a/base/src/test/java/io/spine/base/EnvironmentTest.java b/base/src/test/java/io/spine/base/EnvironmentTest.java index 90c00f79f6..25a8e0a613 100644 --- a/base/src/test/java/io/spine/base/EnvironmentTest.java +++ b/base/src/test/java/io/spine/base/EnvironmentTest.java @@ -20,6 +20,7 @@ package io.spine.base; +import com.google.common.base.Throwables; import com.google.errorprone.annotations.Immutable; import io.spine.base.given.AppEngine; import io.spine.base.given.AppEngineStandard; @@ -32,6 +33,8 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import java.util.concurrent.atomic.AtomicBoolean; + import static com.google.common.truth.Truth.assertThat; import static io.spine.base.Tests.ENV_KEY_TESTS; import static org.junit.jupiter.api.Assertions.assertNull; @@ -239,6 +242,26 @@ void returnCustomInstance() { assertThat(environment.type()).isInstanceOf(Local.class); } + @Test + @DisplayName("cache the environment type") + void cacheEnvType() throws InterruptedException { + AtomicBoolean envCached = new AtomicBoolean(false); + assertThat(environment.is(Tests.class)); + Thread thread = new Thread(() -> { + /* + * Here, the stack trace does not contain mentions of testing frameworks, because + * we check from a different thread. We also explicitly clear the variable. + */ + Tests.clearTestingEnvVariable(); + assertThat(environment.is(Tests.class)).isTrue(); + assertThat(new Tests().enabled()).isFalse(); + envCached.set(true); + }); + thread.start(); + thread.join(); + assertThat(envCached.get()).isTrue(); + } + @Immutable static final class Local extends EnvironmentType { From a3e56020d84817847045f46d6c720085dbee80b5 Mon Sep 17 00:00:00 2001 From: "serhii.lekariev" Date: Thu, 11 Jun 2020 11:33:07 +0300 Subject: [PATCH 14/43] Add a simpler env type caching test. Also `license-report` and `pom`. --- .../java/io/spine/base/EnvironmentTest.java | 31 ++++++++-- license-report.md | 60 +++++++++---------- pom.xml | 4 +- 3 files changed, 59 insertions(+), 36 deletions(-) diff --git a/base/src/test/java/io/spine/base/EnvironmentTest.java b/base/src/test/java/io/spine/base/EnvironmentTest.java index 25a8e0a613..b8c0ac8b53 100644 --- a/base/src/test/java/io/spine/base/EnvironmentTest.java +++ b/base/src/test/java/io/spine/base/EnvironmentTest.java @@ -243,14 +243,29 @@ void returnCustomInstance() { } @Test - @DisplayName("cache the environment type") - void cacheEnvType() throws InterruptedException { + @DisplayName("cache a custom environment type") + void cacheCustom() { + environment.register(Staging.class); + + Staging.set(); + assertThat(environment.is(Staging.class)).isTrue(); + + Staging.reset(); + assertThat(new Staging().enabled()).isFalse(); + assertThat(environment.is(Staging.class)).isTrue(); + } + + @Test + @DisplayName("cache the `Tests` environment type") + void cacheTests() throws InterruptedException { AtomicBoolean envCached = new AtomicBoolean(false); assertThat(environment.is(Tests.class)); Thread thread = new Thread(() -> { /* - * Here, the stack trace does not contain mentions of testing frameworks, because - * we check from a different thread. We also explicitly clear the variable. + * Here the stack trace does not contain mentions of testing frameworks, because + * we check from a new thread. + * + * We also explicitly clear the variable. */ Tests.clearTestingEnvVariable(); assertThat(environment.is(Tests.class)).isTrue(); @@ -291,6 +306,14 @@ public boolean enabled() { return String.valueOf(true) .equalsIgnoreCase(System.getProperty(STAGING_ENV_TYPE_KEY)); } + + static void set(){ + System.setProperty(STAGING_ENV_TYPE_KEY, String.valueOf(true)); + } + + static void reset() { + System.clearProperty(STAGING_ENV_TYPE_KEY); + } } @Immutable diff --git a/license-report.md b/license-report.md index eaf95d621e..c866fa0ad5 100644 --- a/license-report.md +++ b/license-report.md @@ -1,6 +1,6 @@ -# Dependencies of `io.spine:spine-base:1.5.15` +# Dependencies of `io.spine:spine-base:1.5.18` ## Runtime 1. **Group:** com.google.code.findbugs **Name:** jsr305 **Version:** 3.0.2 @@ -328,12 +328,12 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jun 09 16:28:08 EEST 2020** 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). +This report was generated on **Thu Jun 11 11:32:19 EEST 2020** 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). -# Dependencies of `io.spine.tools:spine-errorprone-checks:1.5.15` +# Dependencies of `io.spine.tools:spine-errorprone-checks:1.5.18` ## Runtime 1. **Group:** com.github.ben-manes.caffeine **Name:** caffeine **Version:** 2.7.0 @@ -773,12 +773,12 @@ This report was generated on **Tue Jun 09 16:28:08 EEST 2020** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jun 09 16:28:08 EEST 2020** 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). +This report was generated on **Thu Jun 11 11:32:19 EEST 2020** 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). -# Dependencies of `io.spine.tools:spine-javadoc-filter:1.5.15` +# Dependencies of `io.spine.tools:spine-javadoc-filter:1.5.18` ## Runtime 1. **Group:** com.google.android **Name:** annotations **Version:** 4.1.1.4 @@ -1156,12 +1156,12 @@ This report was generated on **Tue Jun 09 16:28:08 EEST 2020** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jun 09 16:28:09 EEST 2020** 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). +This report was generated on **Thu Jun 11 11:32:20 EEST 2020** 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). -# Dependencies of `io.spine.tools:spine-javadoc-prettifier:1.5.15` +# Dependencies of `io.spine.tools:spine-javadoc-prettifier:1.5.18` ## Runtime 1. **Group:** com.google.code.findbugs **Name:** jsr305 **Version:** 3.0.2 @@ -1521,12 +1521,12 @@ This report was generated on **Tue Jun 09 16:28:09 EEST 2020** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jun 09 16:28:09 EEST 2020** 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). +This report was generated on **Thu Jun 11 11:32:21 EEST 2020** 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). -# Dependencies of `io.spine.tools:spine-model-compiler:1.5.15` +# Dependencies of `io.spine.tools:spine-model-compiler:1.5.18` ## Runtime 1. **Group:** com.google.code.findbugs **Name:** jsr305 **Version:** 3.0.2 @@ -1902,12 +1902,12 @@ This report was generated on **Tue Jun 09 16:28:09 EEST 2020** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jun 09 16:28:09 EEST 2020** 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). +This report was generated on **Thu Jun 11 11:32:21 EEST 2020** 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). -# Dependencies of `io.spine.tools:spine-mute-logging:1.5.15` +# Dependencies of `io.spine.tools:spine-mute-logging:1.5.18` ## Runtime 1. **Group:** com.google.auto.value **Name:** auto-value-annotations **Version:** 1.6.3 @@ -2281,12 +2281,12 @@ This report was generated on **Tue Jun 09 16:28:09 EEST 2020** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jun 09 16:28:10 EEST 2020** 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). +This report was generated on **Thu Jun 11 11:32:22 EEST 2020** 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). -# Dependencies of `io.spine.tools:spine-plugin-base:1.5.15` +# Dependencies of `io.spine.tools:spine-plugin-base:1.5.18` ## Runtime 1. **Group:** com.google.code.findbugs **Name:** jsr305 **Version:** 3.0.2 @@ -2646,12 +2646,12 @@ This report was generated on **Tue Jun 09 16:28:10 EEST 2020** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jun 09 16:28:10 EEST 2020** 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). +This report was generated on **Thu Jun 11 11:32:22 EEST 2020** 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). -# Dependencies of `io.spine.tools:spine-plugin-testlib:1.5.15` +# Dependencies of `io.spine.tools:spine-plugin-testlib:1.5.18` ## Runtime 1. **Group:** com.google.auto.value **Name:** auto-value-annotations **Version:** 1.6.3 @@ -3065,12 +3065,12 @@ This report was generated on **Tue Jun 09 16:28:10 EEST 2020** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jun 09 16:28:10 EEST 2020** 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). +This report was generated on **Thu Jun 11 11:32:23 EEST 2020** 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). -# Dependencies of `io.spine.tools:spine-proto-dart-plugin:1.5.15` +# Dependencies of `io.spine.tools:spine-proto-dart-plugin:1.5.18` ## Runtime 1. **Group:** com.google.code.findbugs **Name:** jsr305 **Version:** 3.0.2 @@ -3430,12 +3430,12 @@ This report was generated on **Tue Jun 09 16:28:10 EEST 2020** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jun 09 16:28:11 EEST 2020** 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). +This report was generated on **Thu Jun 11 11:32:23 EEST 2020** 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). -# Dependencies of `io.spine.tools:spine-proto-js-plugin:1.5.15` +# Dependencies of `io.spine.tools:spine-proto-js-plugin:1.5.18` ## Runtime 1. **Group:** com.google.code.findbugs **Name:** jsr305 **Version:** 3.0.2 @@ -3795,12 +3795,12 @@ This report was generated on **Tue Jun 09 16:28:11 EEST 2020** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jun 09 16:28:11 EEST 2020** 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). +This report was generated on **Thu Jun 11 11:32:24 EEST 2020** 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). -# Dependencies of `io.spine.tools:spine-protoc-api:1.5.15` +# Dependencies of `io.spine.tools:spine-protoc-api:1.5.18` ## Runtime 1. **Group:** com.google.code.findbugs **Name:** jsr305 **Version:** 3.0.2 @@ -4120,12 +4120,12 @@ This report was generated on **Tue Jun 09 16:28:11 EEST 2020** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jun 09 16:28:11 EEST 2020** 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). +This report was generated on **Thu Jun 11 11:32:24 EEST 2020** 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). -# Dependencies of `io.spine.tools:spine-protoc-plugin:1.5.15` +# Dependencies of `io.spine.tools:spine-protoc-plugin:1.5.18` ## Runtime 1. **Group:** com.google.code.findbugs **Name:** jsr305 **Version:** 3.0.2 @@ -4453,12 +4453,12 @@ This report was generated on **Tue Jun 09 16:28:11 EEST 2020** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jun 09 16:28:12 EEST 2020** 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). +This report was generated on **Thu Jun 11 11:32:24 EEST 2020** 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). -# Dependencies of `io.spine:spine-testlib:1.5.15` +# Dependencies of `io.spine:spine-testlib:1.5.18` ## Runtime 1. **Group:** com.google.auto.value **Name:** auto-value-annotations **Version:** 1.6.3 @@ -4832,12 +4832,12 @@ This report was generated on **Tue Jun 09 16:28:12 EEST 2020** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jun 09 16:28:12 EEST 2020** 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). +This report was generated on **Thu Jun 11 11:32:25 EEST 2020** 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). -# Dependencies of `io.spine.tools:spine-tool-base:1.5.15` +# Dependencies of `io.spine.tools:spine-tool-base:1.5.18` ## Runtime 1. **Group:** com.google.code.findbugs **Name:** jsr305 **Version:** 3.0.2 @@ -5165,12 +5165,12 @@ This report was generated on **Tue Jun 09 16:28:12 EEST 2020** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jun 09 16:28:12 EEST 2020** 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). +This report was generated on **Thu Jun 11 11:32:25 EEST 2020** 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). -# Dependencies of `io.spine.tools:spine-validation-generator:1.5.15` +# Dependencies of `io.spine.tools:spine-validation-generator:1.5.18` ## Runtime 1. **Group:** com.google.code.findbugs **Name:** jsr305 **Version:** 3.0.2 @@ -5498,4 +5498,4 @@ This report was generated on **Tue Jun 09 16:28:12 EEST 2020** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jun 09 16:28:12 EEST 2020** 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 **Thu Jun 11 11:32:25 EEST 2020** 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 f9ccd5521d..2ac7eb63a3 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ all modules and does not describe the project structure per-subproject. io.spine spine-base -1.5.16 +1.5.18 2015 @@ -154,7 +154,7 @@ all modules and does not describe the project structure per-subproject. io.spine.tools spine-protoc-plugin - 1.5.16 + 1.5.18 test From dd596074b634a9333681220cdf23c9d2aff76a3e Mon Sep 17 00:00:00 2001 From: "serhii.lekariev" Date: Thu, 11 Jun 2020 11:39:21 +0300 Subject: [PATCH 15/43] Fix formatting, wording, test declaration ordering. Explain a warning suppression. --- .../main/java/io/spine/base/Environment.java | 1 - .../java/io/spine/base/EnvironmentTypes.java | 9 +++-- .../io/spine/base/EnvironmentTypesTest.java | 36 +++++++++---------- 3 files changed, 25 insertions(+), 21 deletions(-) diff --git a/base/src/main/java/io/spine/base/Environment.java b/base/src/main/java/io/spine/base/Environment.java index fce73c8176..aff4f38a10 100644 --- a/base/src/main/java/io/spine/base/Environment.java +++ b/base/src/main/java/io/spine/base/Environment.java @@ -126,7 +126,6 @@ * * * - * *

When registering custom types, please ensure their mutual exclusivity. * If two or more environment types {@linkplain EnvironmentType#enabled() consider themselves * enabled} at the same time, the behaviour of {@link #is(Class)}} is undefined. diff --git a/base/src/main/java/io/spine/base/EnvironmentTypes.java b/base/src/main/java/io/spine/base/EnvironmentTypes.java index f53ca94a10..c45bc56ab9 100644 --- a/base/src/main/java/io/spine/base/EnvironmentTypes.java +++ b/base/src/main/java/io/spine/base/EnvironmentTypes.java @@ -37,7 +37,7 @@ private EnvironmentTypes() { } /** - * Checks whether the specified environment type may be registered using it's class. + * Checks whether the specified environment type can be registered using it's class. * *

To register the type by its class it must have a package-private parameterless * constructor. @@ -72,7 +72,12 @@ static > C checkCanRegisterByClass(C } } - @SuppressWarnings("unchecked") + @SuppressWarnings("unchecked" /* + * Casting from `Constructor` to + * `Constructor checkHasParameterlessCtor(Class type) { for (Constructor constructor : type.getDeclaredConstructors()) { diff --git a/base/src/test/java/io/spine/base/EnvironmentTypesTest.java b/base/src/test/java/io/spine/base/EnvironmentTypesTest.java index 7d63c5eb96..ceb069285c 100644 --- a/base/src/test/java/io/spine/base/EnvironmentTypesTest.java +++ b/base/src/test/java/io/spine/base/EnvironmentTypesTest.java @@ -36,6 +36,24 @@ class EnvironmentTypesTest extends UtilityClassTest { super(EnvironmentTypes.class); } + @Test + @DisplayName("allow to register an env with a package-private parameterless ctor") + void allow() { + Class local = Local.class; + assertThat(checkCanRegisterByClass(Local.class)).isSameInstanceAs(local); + } + + private static class Local extends EnvironmentType { + + Local() { + } + + @Override + protected boolean enabled() { + return true; + } + } + @Nested @DisplayName("disallow to register types by class if") class ProhibitRegistering { @@ -116,22 +134,4 @@ protected boolean enabled() { } } } - - @Test - @DisplayName("allow to register an env with a package-private parameterless ctor") - void allow() { - Class local = Local.class; - assertThat(checkCanRegisterByClass(Local.class)).isSameInstanceAs(local); - } - - private static class Local extends EnvironmentType { - - Local() { - } - - @Override - protected boolean enabled() { - return true; - } - } } From 252817d41fd3a7128e91f134109947a43bd355a2 Mon Sep 17 00:00:00 2001 From: "serhii.lekariev" Date: Thu, 11 Jun 2020 12:45:31 +0300 Subject: [PATCH 16/43] Extract the instantiation utility. --- .../main/java/io/spine/base/Environment.java | 34 ++--- .../java/io/spine/base/EnvironmentTypes.java | 38 ++++- .../io/spine/base/EnvironmentTypesTest.java | 142 +++++++++--------- 3 files changed, 124 insertions(+), 90 deletions(-) diff --git a/base/src/main/java/io/spine/base/Environment.java b/base/src/main/java/io/spine/base/Environment.java index aff4f38a10..6ca3f9520c 100644 --- a/base/src/main/java/io/spine/base/Environment.java +++ b/base/src/main/java/io/spine/base/Environment.java @@ -27,11 +27,7 @@ import io.spine.annotation.SPI; import org.checkerframework.checker.nullness.qual.Nullable; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; - import static com.google.common.base.Preconditions.checkNotNull; -import static io.spine.util.Exceptions.newIllegalArgumentException; import static io.spine.util.Exceptions.newIllegalStateException; /** @@ -44,10 +40,10 @@ * Two environment types exist out of the box: * *

* *

The framework users may define their custom settings depending on the current environment @@ -126,6 +122,9 @@ * * * + *

{@linkplain #setTo(EnvironmentType) explicitly setting} the environment type overrides + * the cached value. + * *

When registering custom types, please ensure their mutual exclusivity. * If two or more environment types {@linkplain EnvironmentType#enabled() consider themselves * enabled} at the same time, the behaviour of {@link #is(Class)}} is undefined. @@ -196,18 +195,10 @@ public Environment register(EnvironmentType environmentType) { @Internal @CanIgnoreReturnValue Environment register(Class type) { - try { - EnvironmentTypes.checkCanRegisterByClass(type); - Constructor ctor = type.getDeclaredConstructor(); - EnvironmentType envTypeInstance = ctor.newInstance(); - return register(envTypeInstance); - } catch (NoSuchMethodException | IllegalAccessException | - InstantiationException | InvocationTargetException e) { - String message = "Could not register environment type `%s` by class. You may try " + - "creating an `EnvironmentType` instance. To register an environment type by" + - " class, its constructor must be parameterless and package-private."; - throw newIllegalArgumentException(e, message, type); - } + EnvironmentTypes.checkCanRegisterByClass(type); + EnvironmentType envTypeInstance = EnvironmentTypes.instantiate(type); + return register(envTypeInstance); + } /** Returns the singleton instance. */ @@ -307,6 +298,11 @@ public void setTo(EnvironmentType type) { this.currentEnvType = checkNotNull(type); } + @VisibleForTesting + public void setTo(Class type) { + + } + /** * Turns the test mode on. * diff --git a/base/src/main/java/io/spine/base/EnvironmentTypes.java b/base/src/main/java/io/spine/base/EnvironmentTypes.java index c45bc56ab9..1ec81ea0d7 100644 --- a/base/src/main/java/io/spine/base/EnvironmentTypes.java +++ b/base/src/main/java/io/spine/base/EnvironmentTypes.java @@ -24,8 +24,10 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import static io.spine.util.Exceptions.newIllegalArgumentException; +import static io.spine.util.Exceptions.newIllegalStateException; import static java.lang.String.format; /** @@ -52,6 +54,31 @@ static > C checkCanRegisterByClass(C return type; } + /** + * Tries to instantiate the specified environment type. + * + *

It can only be instantiated if it has a package-private parameterless constructor. + * + *

If the constructor is not package-private or has at least 1 parameter or a + * reflection-related error occurs, an {@code IllegalStateException} is thrown. + * + * @param type env type to instantiate + * @return a new {@code EnvironmentType} instance + */ + static EnvironmentType instantiate(Class type) { + Constructor ctor = checkHasParameterlessCtor(type); + checkCtorAccessLevel(ctor); + try { + EnvironmentType result = ctor.newInstance(); + return result; + } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { + String message = "To `register` or `setTo` an environment type `%s` by class, " + + "the class must have a package-private parameterless ctor. You may also " + + "`register` and `setTo` using an env type instance."; + throw newIllegalStateException(e, message, type.getSimpleName()); + } + } + private static void checkCtorAccessLevel(Constructor constructor) { Invokable ctor = @@ -61,11 +88,13 @@ static > C checkCanRegisterByClass(C Class envType = constructor.getDeclaringClass(); StringBuilder message = new StringBuilder(); message.append(format( - "`%s` constructor must be package-private to be registered in `Environment`.", + "`%s` constructor must be package-private to be registered and used in " + + "`setTo` in `Environment`.", envType.getSimpleName())); if (ctor.isPublic()) { message.append(format( - " As `%s` has a public constructor, you may use `Environment.register(envInstance)`.", + " As `%s` has a public constructor, you may use `Environment.register(envInstance)`." + + "And `environment.setTo(envInstance)`.", envType.getSimpleName())); } throw newIllegalArgumentException(message.toString()); @@ -86,8 +115,9 @@ static > C checkCanRegisterByClass(C } } - throw newIllegalArgumentException("To register `%s` by class, it must " + - "have a parameterless package-private constructor.", + throw newIllegalArgumentException("To `register` `%s` or use it in `setTo` by class, " + + "it must have a parameterless package-private " + + "constructor.", type.getSimpleName()); } } diff --git a/base/src/test/java/io/spine/base/EnvironmentTypesTest.java b/base/src/test/java/io/spine/base/EnvironmentTypesTest.java index ceb069285c..c54da22a5f 100644 --- a/base/src/test/java/io/spine/base/EnvironmentTypesTest.java +++ b/base/src/test/java/io/spine/base/EnvironmentTypesTest.java @@ -22,11 +22,16 @@ import io.spine.testing.UtilityClassTest; import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; import static com.google.common.truth.Truth.assertThat; import static io.spine.base.EnvironmentTypes.checkCanRegisterByClass; +import static io.spine.base.EnvironmentTypes.instantiate; import static org.junit.jupiter.api.Assertions.assertThrows; @DisplayName("`EnvironmentTypes` should") @@ -37,101 +42,104 @@ class EnvironmentTypesTest extends UtilityClassTest { } @Test - @DisplayName("allow to register an env with a package-private parameterless ctor") - void allow() { + @DisplayName("allow to register an env type with a package-private parameterless ctor") + void allowToRegister() { Class local = Local.class; assertThat(checkCanRegisterByClass(Local.class)).isSameInstanceAs(local); } - private static class Local extends EnvironmentType { + @Test + @DisplayName("allow to instantiate an env type with a package-private parameterless ctor") + void allowToInstantiate() { + Class local = Local.class; + EnvironmentType localEnv = instantiate(local); + assertThat(localEnv.enabled()).isTrue(); + } - Local() { - } + @DisplayName("Disallow to register env types by classes if they do not have" + + "package-private parameterless constructors") + @ParameterizedTest + @MethodSource("envTypesAndMethods") + void checkCanRegister(Class environmentType) { + assertThrows(IllegalArgumentException.class, + () -> EnvironmentTypes.checkCanRegisterByClass(environmentType)); + } - @Override - protected boolean enabled() { - return true; - } + @DisplayName("Disallow to instantaite env types by classes if they do not have" + + "package-private parameterless constructors") + @ParameterizedTest + @MethodSource("envTypesAndMethods") + void checkCanInstantiate(Class environmentType) { + assertThrows(IllegalArgumentException.class, + () -> EnvironmentTypes.instantiate(environmentType)); } - @Nested - @DisplayName("disallow to register types by class if") - class ProhibitRegistering { + private static Stream envTypesAndMethods() { + Stream> envTypes = Stream.of( + ValueDependantEnvironment.class, + HiddenEnvironment.class, + ProtectedEnvironment.class, + VisibleEnvironment.class + ); + return envTypes.map(Arguments::arguments); + } - @Test - @DisplayName("they have a public ctor") - void registerCustomPublicCtor() { - assertThrows(IllegalArgumentException.class, - () -> checkCanRegisterByClass(VisibleEnvironment.class)); - } + private class ValueDependantEnvironment extends EnvironmentType { - @Test - @DisplayName("they have a private ctor") - void registerCustomPrivateCtor() { - assertThrows(IllegalArgumentException.class, - () -> checkCanRegisterByClass(HiddenEnvironment.class)); - } + private final boolean enabled; - @Test - @DisplayName("they have a protected ctor") - void registerCustomPackagePrivateCtor() { - assertThrows(IllegalArgumentException.class, - () -> checkCanRegisterByClass(ProtectedEnvironment.class)); + ValueDependantEnvironment(boolean enabled) { + this.enabled = enabled; } - @Test - @DisplayName("they do not have a parameterless ctor") - void registerCustomCtorWithParameters() { - assertThrows(IllegalArgumentException.class, - () -> checkCanRegisterByClass(ValueDependantEnvironment.class)); + @Override + protected boolean enabled() { + return enabled; } + } - private class ValueDependantEnvironment extends EnvironmentType { - - private final boolean enabled; + private class HiddenEnvironment extends EnvironmentType { - ValueDependantEnvironment(boolean enabled) { - this.enabled = enabled; - } + private HiddenEnvironment() { + } - @Override - protected boolean enabled() { - return enabled; - } + @Override + protected boolean enabled() { + return false; } + } - private class HiddenEnvironment extends EnvironmentType { + private class ProtectedEnvironment extends EnvironmentType { - private HiddenEnvironment() { - } + protected ProtectedEnvironment() { + } - @Override - protected boolean enabled() { - return false; - } + @Override + protected boolean enabled() { + return false; } + } - private class ProtectedEnvironment extends EnvironmentType { + private class VisibleEnvironment extends EnvironmentType { - protected ProtectedEnvironment() { - } + @SuppressWarnings("PublicConstructorInNonPublicClass") + public VisibleEnvironment() { + } - @Override - protected boolean enabled() { - return false; - } + @Override + protected boolean enabled() { + return true; } + } - private class VisibleEnvironment extends EnvironmentType { + private static class Local extends EnvironmentType { - @SuppressWarnings("PublicConstructorInNonPublicClass") - public VisibleEnvironment() { - } + Local() { + } - @Override - protected boolean enabled() { - return true; - } + @Override + protected boolean enabled() { + return true; } } } From 323faeb67f377efd04633c1e931eaaad15ef6cb7 Mon Sep 17 00:00:00 2001 From: "serhii.lekariev" Date: Thu, 11 Jun 2020 12:56:10 +0300 Subject: [PATCH 17/43] Expose a `setTo(Class)`. --- base/src/main/java/io/spine/base/Environment.java | 10 +++++++++- .../main/java/io/spine/base/EnvironmentTypes.java | 3 +++ .../test/java/io/spine/base/EnvironmentTest.java | 14 +++++++++++++- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/base/src/main/java/io/spine/base/Environment.java b/base/src/main/java/io/spine/base/Environment.java index 6ca3f9520c..60b0f23921 100644 --- a/base/src/main/java/io/spine/base/Environment.java +++ b/base/src/main/java/io/spine/base/Environment.java @@ -298,9 +298,17 @@ public void setTo(EnvironmentType type) { this.currentEnvType = checkNotNull(type); } + /** + * Forces the specified environment type to be the current one. + * + *

The specified type must have a package-private parameterless constructor, otherwise + * an {@code IllegalStateException} is thrown. + */ @VisibleForTesting public void setTo(Class type) { - + checkNotNull(type); + EnvironmentType result = EnvironmentTypes.instantiate(type); + setTo(result); } /** diff --git a/base/src/main/java/io/spine/base/EnvironmentTypes.java b/base/src/main/java/io/spine/base/EnvironmentTypes.java index 1ec81ea0d7..a95c814211 100644 --- a/base/src/main/java/io/spine/base/EnvironmentTypes.java +++ b/base/src/main/java/io/spine/base/EnvironmentTypes.java @@ -26,6 +26,7 @@ import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; +import static com.google.common.base.Preconditions.checkNotNull; import static io.spine.util.Exceptions.newIllegalArgumentException; import static io.spine.util.Exceptions.newIllegalStateException; import static java.lang.String.format; @@ -49,6 +50,7 @@ private EnvironmentTypes() { */ @CanIgnoreReturnValue static > C checkCanRegisterByClass(C type) { + checkNotNull(type); Constructor parameterlessCtor = checkHasParameterlessCtor(type); checkCtorAccessLevel(parameterlessCtor); return type; @@ -66,6 +68,7 @@ static > C checkCanRegisterByClass(C * @return a new {@code EnvironmentType} instance */ static EnvironmentType instantiate(Class type) { + checkNotNull(type); Constructor ctor = checkHasParameterlessCtor(type); checkCtorAccessLevel(ctor); try { diff --git a/base/src/test/java/io/spine/base/EnvironmentTest.java b/base/src/test/java/io/spine/base/EnvironmentTest.java index b8c0ac8b53..8b1191e7cc 100644 --- a/base/src/test/java/io/spine/base/EnvironmentTest.java +++ b/base/src/test/java/io/spine/base/EnvironmentTest.java @@ -161,6 +161,18 @@ void turnProductionOn() { assertThat(environment.is(Production.class)).isTrue(); } + @Test + @DisplayName("turn a custom mode on") + void turnCustomTypeOn() { + environment.register(Staging.class); + + Staging.reset(); + assertThat(environment.is(Staging.class)).isFalse(); + + environment.setTo(Staging.class); + assertThat(environment.is(Staging.class)).isTrue(); + } + @Test @DisplayName("turn production mode on using a deprecated method") @SuppressWarnings("deprecation") @@ -179,7 +191,7 @@ void clearOnReset() { } @Nested - @DisplayName("when assigning custom environment types") + @DisplayName("when registering custom environment types") class CustomEnvTypes { @Test From 90e20b285c3107f1d931507f128df388f9daedeb Mon Sep 17 00:00:00 2001 From: "serhii.lekariev" Date: Thu, 11 Jun 2020 13:00:29 +0300 Subject: [PATCH 18/43] `@Internalize` a method. --- base/src/main/java/io/spine/base/Environment.java | 1 + 1 file changed, 1 insertion(+) diff --git a/base/src/main/java/io/spine/base/Environment.java b/base/src/main/java/io/spine/base/Environment.java index 60b0f23921..d8ab38bdd8 100644 --- a/base/src/main/java/io/spine/base/Environment.java +++ b/base/src/main/java/io/spine/base/Environment.java @@ -304,6 +304,7 @@ public void setTo(EnvironmentType type) { *

The specified type must have a package-private parameterless constructor, otherwise * an {@code IllegalStateException} is thrown. */ + @Internal @VisibleForTesting public void setTo(Class type) { checkNotNull(type); From a26cd0c997ac055aaf192cb5c40d067833103efb Mon Sep 17 00:00:00 2001 From: "serhii.lekariev" Date: Thu, 11 Jun 2020 13:09:02 +0300 Subject: [PATCH 19/43] Fix documentation and naming errors. --- base/src/main/java/io/spine/base/Environment.java | 15 ++++++++------- .../main/java/io/spine/base/EnvironmentTypes.java | 5 +++-- .../java/io/spine/base/EnvironmentTypesTest.java | 8 ++++---- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/base/src/main/java/io/spine/base/Environment.java b/base/src/main/java/io/spine/base/Environment.java index d8ab38bdd8..0208ae042c 100644 --- a/base/src/main/java/io/spine/base/Environment.java +++ b/base/src/main/java/io/spine/base/Environment.java @@ -40,10 +40,10 @@ * Two environment types exist out of the box: * *

* *

The framework users may define their custom settings depending on the current environment @@ -107,11 +107,13 @@ * *

Caching

*

Be aware that {@code Environment} caches the {@code EnvironmentType} once its calculated. - * This means that if one environment type has been found to be active, it gets cached. If later it - * becomes logically inactive, e.g. the environment variable that's used to check the env type - * changes, {@code Environment} is still going to return the cached value. For example: + * This means that if one environment type has been found to be active, its instance is saved. + * If later it becomes logically inactive, e.g. the environment variable that's used to check the + * environment type changes, {@code Environment} is still going to return the cached value. + * For example: *

  *     EnvironmentType awsLambda = new AwsLambda();
+ *     Environment.instance().register(awsLambda);
  *     assertThat(Environment.instance().is(AwsLambda.class)).isTrue();
  *
  *     System.clearProperty(AwsLambda.AWS_ENV_VARIABLE);
@@ -198,7 +200,6 @@ Environment register(Class type) {
         EnvironmentTypes.checkCanRegisterByClass(type);
         EnvironmentType envTypeInstance = EnvironmentTypes.instantiate(type);
         return register(envTypeInstance);
-
     }
 
     /** Returns the singleton instance. */
diff --git a/base/src/main/java/io/spine/base/EnvironmentTypes.java b/base/src/main/java/io/spine/base/EnvironmentTypes.java
index a95c814211..467fc2bc52 100644
--- a/base/src/main/java/io/spine/base/EnvironmentTypes.java
+++ b/base/src/main/java/io/spine/base/EnvironmentTypes.java
@@ -96,8 +96,9 @@ static EnvironmentType instantiate(Class type) {
                     envType.getSimpleName()));
             if (ctor.isPublic()) {
                 message.append(format(
-                        " As `%s` has a public constructor, you may use `Environment.register(envInstance)`." +
-                                "And `environment.setTo(envInstance)`.",
+                        " As `%s` has a public constructor, you may use " +
+                                "`Environment.register(envInstance)` and " +
+                                "`environment.setTo(envInstance)`.",
                         envType.getSimpleName()));
             }
             throw newIllegalArgumentException(message.toString());
diff --git a/base/src/test/java/io/spine/base/EnvironmentTypesTest.java b/base/src/test/java/io/spine/base/EnvironmentTypesTest.java
index c54da22a5f..e2e54bf382 100644
--- a/base/src/test/java/io/spine/base/EnvironmentTypesTest.java
+++ b/base/src/test/java/io/spine/base/EnvironmentTypesTest.java
@@ -56,8 +56,8 @@ void allowToInstantiate() {
         assertThat(localEnv.enabled()).isTrue();
     }
 
-    @DisplayName("Disallow to register env types by classes if they do not have" +
-            "package-private parameterless constructors")
+    @DisplayName("Disallow to register env types by classes if they do not have a" +
+            "package-private parameterless constructor")
     @ParameterizedTest
     @MethodSource("envTypesAndMethods")
     void checkCanRegister(Class environmentType) {
@@ -65,8 +65,8 @@ void checkCanRegister(Class environmentType) {
                      () -> EnvironmentTypes.checkCanRegisterByClass(environmentType));
     }
 
-    @DisplayName("Disallow to instantaite env types by classes if they do not have" +
-            "package-private parameterless constructors")
+    @DisplayName("Disallow to instantaite env types by classes if they do not have a" +
+            "package-private parameterless constructor")
     @ParameterizedTest
     @MethodSource("envTypesAndMethods")
     void checkCanInstantiate(Class environmentType) {

From 7f9655b620545e1f2800cd8545db8846c783ed23 Mon Sep 17 00:00:00 2001
From: "serhii.lekariev" 
Date: Thu, 11 Jun 2020 13:31:37 +0300
Subject: [PATCH 20/43] Cache the class of the env type instead of its
 instance.

---
 .../main/java/io/spine/base/Environment.java  | 38 +++++++++----------
 .../java/io/spine/base/EnvironmentTest.java   |  7 ++--
 .../io/spine/base/EnvironmentTypesTest.java   |  9 -----
 .../io/spine/base/environment/Staging.java    |  2 +-
 4 files changed, 23 insertions(+), 33 deletions(-)

diff --git a/base/src/main/java/io/spine/base/Environment.java b/base/src/main/java/io/spine/base/Environment.java
index 0208ae042c..9c7bc0f08e 100644
--- a/base/src/main/java/io/spine/base/Environment.java
+++ b/base/src/main/java/io/spine/base/Environment.java
@@ -35,8 +35,9 @@
  *
  * 

Environment Type Detection

* - *

Current implementation allows to {@linkplain #is(Class) check} the type of the current - * environment, or {@linkplain #type() get the instance of the current environment}. + *

Current implementation allows t {@linkplain #type() obtain the type} of the current + * environment, or to check whether current environment type {@linkplain #is(Class) matches + * another type}. * Two environment types exist out of the box: * *

    @@ -144,7 +145,7 @@ public final class Environment { private static final Environment INSTANCE = new Environment(); private ImmutableList knownEnvTypes; - private @Nullable EnvironmentType currentEnvType; + private @Nullable Class currentEnvType; private Environment() { this.knownEnvTypes = BASE_TYPES; @@ -243,14 +244,14 @@ public Environment createCopy() { * @return whether the current environment type matches the specified one */ public boolean is(Class type) { - EnvironmentType currentEnv = cachedOrCalculated(); - boolean result = type.isInstance(currentEnv); + Class currentEnv = cachedOrCalculated(); + boolean result = type.isAssignableFrom(currentEnv); return result; } - /** Returns the instance of the current environment. */ - public EnvironmentType type() { - EnvironmentType currentEnv = cachedOrCalculated(); + /** Returns the type of the current environment. */ + public Class type() { + Class currentEnv = cachedOrCalculated(); return currentEnv; } @@ -296,7 +297,7 @@ public void restoreFrom(Environment copy) { */ @VisibleForTesting public void setTo(EnvironmentType type) { - this.currentEnvType = checkNotNull(type); + this.currentEnvType = checkNotNull(type).getClass(); } /** @@ -309,8 +310,7 @@ public void setTo(EnvironmentType type) { @VisibleForTesting public void setTo(Class type) { checkNotNull(type); - EnvironmentType result = EnvironmentTypes.instantiate(type); - setTo(result); + this.currentEnvType = type; } /** @@ -323,7 +323,7 @@ public void setTo(Class type) { @Deprecated @VisibleForTesting public void setToTests() { - this.currentEnvType = new Tests(); + this.currentEnvType = Tests.class; Tests.enable(); } @@ -337,7 +337,7 @@ public void setToTests() { @Deprecated @VisibleForTesting public void setToProduction() { - this.currentEnvType = new Production(); + this.currentEnvType = Production.class; Tests.clearTestingEnvVariable(); } @@ -351,18 +351,18 @@ public void reset() { Tests.clearTestingEnvVariable(); } - private EnvironmentType cachedOrCalculated() { - EnvironmentType result = currentEnvType != null - ? currentEnvType - : currentType(); + private Class cachedOrCalculated() { + Class result = currentEnvType != null + ? currentEnvType + : currentType(); this.currentEnvType = result; return result; } - private EnvironmentType currentType() { + private Class currentType() { for (EnvironmentType type : knownEnvTypes) { if (type.enabled()) { - return type; + return type.getClass(); } } diff --git a/base/src/test/java/io/spine/base/EnvironmentTest.java b/base/src/test/java/io/spine/base/EnvironmentTest.java index 8b1191e7cc..2a1849d9c3 100644 --- a/base/src/test/java/io/spine/base/EnvironmentTest.java +++ b/base/src/test/java/io/spine/base/EnvironmentTest.java @@ -20,7 +20,6 @@ package io.spine.base; -import com.google.common.base.Throwables; import com.google.errorprone.annotations.Immutable; import io.spine.base.given.AppEngine; import io.spine.base.given.AppEngineStandard; @@ -242,7 +241,7 @@ void determineUsingTypeInPresenceOfCustom() { @Test @DisplayName("return the instance of the default environment type") void returnInstance() { - assertThat(environment.type()).isInstanceOf(Tests.class); + assertThat(environment.type()).isSameInstanceAs(Tests.class); } @Test @@ -251,7 +250,7 @@ void returnCustomInstance() { environment.register(Local.class) .register(Staging.class); - assertThat(environment.type()).isInstanceOf(Local.class); + assertThat(environment.type()).isSameInstanceAs(Local.class); } @Test @@ -319,7 +318,7 @@ public boolean enabled() { .equalsIgnoreCase(System.getProperty(STAGING_ENV_TYPE_KEY)); } - static void set(){ + static void set() { System.setProperty(STAGING_ENV_TYPE_KEY, String.valueOf(true)); } diff --git a/base/src/test/java/io/spine/base/EnvironmentTypesTest.java b/base/src/test/java/io/spine/base/EnvironmentTypesTest.java index e2e54bf382..cd7b396697 100644 --- a/base/src/test/java/io/spine/base/EnvironmentTypesTest.java +++ b/base/src/test/java/io/spine/base/EnvironmentTypesTest.java @@ -65,15 +65,6 @@ void checkCanRegister(Class environmentType) { () -> EnvironmentTypes.checkCanRegisterByClass(environmentType)); } - @DisplayName("Disallow to instantaite env types by classes if they do not have a" + - "package-private parameterless constructor") - @ParameterizedTest - @MethodSource("envTypesAndMethods") - void checkCanInstantiate(Class environmentType) { - assertThrows(IllegalArgumentException.class, - () -> EnvironmentTypes.instantiate(environmentType)); - } - private static Stream envTypesAndMethods() { Stream> envTypes = Stream.of( ValueDependantEnvironment.class, diff --git a/base/src/test/java/io/spine/base/environment/Staging.java b/base/src/test/java/io/spine/base/environment/Staging.java index b49ea07486..59460befd9 100644 --- a/base/src/test/java/io/spine/base/environment/Staging.java +++ b/base/src/test/java/io/spine/base/environment/Staging.java @@ -28,7 +28,7 @@ * *

    This implementations relies on a static {@code boolean} flag for detection. */ -public final class Staging extends EnvironmentType { +final class Staging extends EnvironmentType { private static boolean enabled = false; From 5d79b22945e9da7d4b20b0c93c936697f01b6dd2 Mon Sep 17 00:00:00 2001 From: "serhii.lekariev" Date: Thu, 11 Jun 2020 14:28:40 +0300 Subject: [PATCH 21/43] Fix documentation errors. --- base/src/main/java/io/spine/base/Environment.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/src/main/java/io/spine/base/Environment.java b/base/src/main/java/io/spine/base/Environment.java index 9c7bc0f08e..bfed6bdce7 100644 --- a/base/src/main/java/io/spine/base/Environment.java +++ b/base/src/main/java/io/spine/base/Environment.java @@ -35,7 +35,7 @@ * *

    Environment Type Detection

    * - *

    Current implementation allows t {@linkplain #type() obtain the type} of the current + *

    Current implementation allows to {@linkplain #type() obtain the type} of the current * environment, or to check whether current environment type {@linkplain #is(Class) matches * another type}. * Two environment types exist out of the box: @@ -107,7 +107,7 @@ *

* *

Caching

- *

Be aware that {@code Environment} caches the {@code EnvironmentType} once its calculated. + *

{@code Environment} caches the {@code EnvironmentType} once its calculated. * This means that if one environment type has been found to be active, its instance is saved. * If later it becomes logically inactive, e.g. the environment variable that's used to check the * environment type changes, {@code Environment} is still going to return the cached value. From b31771471eb00a7aeffff6edffc78d15d6ddf3f5 Mon Sep 17 00:00:00 2001 From: "serhii.lekariev" Date: Thu, 11 Jun 2020 15:58:28 +0300 Subject: [PATCH 22/43] Allow non package-private ctors. --- .../main/java/io/spine/base/Environment.java | 4 +- .../java/io/spine/base/EnvironmentTypes.java | 96 +++++-------- .../io/spine/base/EnvironmentTypesTest.java | 131 ++++++++++-------- .../java/io/spine/base/given/AwsLambda.java | 31 +++++ .../given/VariableControlledEnvironment.java | 48 +++++++ 5 files changed, 189 insertions(+), 121 deletions(-) create mode 100644 base/src/test/java/io/spine/base/given/AwsLambda.java create mode 100644 base/src/test/java/io/spine/base/given/VariableControlledEnvironment.java diff --git a/base/src/main/java/io/spine/base/Environment.java b/base/src/main/java/io/spine/base/Environment.java index bfed6bdce7..034d713a71 100644 --- a/base/src/main/java/io/spine/base/Environment.java +++ b/base/src/main/java/io/spine/base/Environment.java @@ -187,7 +187,7 @@ public Environment register(EnvironmentType environmentType) { * Remembers the specified environment type, allowing {@linkplain #is(Class) to * determine whether it's enabled} later. * - *

The specified {@code type} must have a parameterless package-private constructor. + *

The specified {@code type} must have a parameterless constructor. * *

Otherwise, behaves like {@link #register(EnvironmentType)}. * @@ -198,7 +198,7 @@ public Environment register(EnvironmentType environmentType) { @Internal @CanIgnoreReturnValue Environment register(Class type) { - EnvironmentTypes.checkCanRegisterByClass(type); + EnvironmentTypes.ensureParameterlessCtor(type); EnvironmentType envTypeInstance = EnvironmentTypes.instantiate(type); return register(envTypeInstance); } diff --git a/base/src/main/java/io/spine/base/EnvironmentTypes.java b/base/src/main/java/io/spine/base/EnvironmentTypes.java index 467fc2bc52..28df03d833 100644 --- a/base/src/main/java/io/spine/base/EnvironmentTypes.java +++ b/base/src/main/java/io/spine/base/EnvironmentTypes.java @@ -28,8 +28,6 @@ import static com.google.common.base.Preconditions.checkNotNull; import static io.spine.util.Exceptions.newIllegalArgumentException; -import static io.spine.util.Exceptions.newIllegalStateException; -import static java.lang.String.format; /** * A utility class for working with and verifying {@link EnvironmentType} extenders. @@ -39,89 +37,69 @@ final class EnvironmentTypes { private EnvironmentTypes() { } - /** - * Checks whether the specified environment type can be registered using it's class. - * - *

To register the type by its class it must have a package-private parameterless - * constructor. - * - * @param type - * environment to register - */ - @CanIgnoreReturnValue - static > C checkCanRegisterByClass(C type) { - checkNotNull(type); - Constructor parameterlessCtor = checkHasParameterlessCtor(type); - checkCtorAccessLevel(parameterlessCtor); - return type; - } - /** * Tries to instantiate the specified environment type. * - *

It can only be instantiated if it has a package-private parameterless constructor. + *

It can only be instantiated if it has a parameterless constructor, an {@code + * IllegalArgumentException} is thrown otherwise. * - *

If the constructor is not package-private or has at least 1 parameter or a - * reflection-related error occurs, an {@code IllegalStateException} is thrown. - * - * @param type env type to instantiate + * @param type + * env type to instantiate * @return a new {@code EnvironmentType} instance */ static EnvironmentType instantiate(Class type) { checkNotNull(type); - Constructor ctor = checkHasParameterlessCtor(type); - checkCtorAccessLevel(ctor); + Constructor ctor = ensureParameterlessCtor(type); + Invokable invokable = + Invokable.from(ctor); + boolean isAccessible = invokable.isAccessible(); try { - EnvironmentType result = ctor.newInstance(); + EnvironmentType result = isAccessible + ? ctor.newInstance() + : instantiatePreservingAccessibility(ctor); return result; } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { - String message = "To `register` or `setTo` an environment type `%s` by class, " + - "the class must have a package-private parameterless ctor. You may also " + - "`register` and `setTo` using an env type instance."; - throw newIllegalStateException(e, message, type.getSimpleName()); - } - } - - private static void - checkCtorAccessLevel(Constructor constructor) { - Invokable ctor = - Invokable.from(constructor); - - if (!ctor.isPackagePrivate()) { - Class envType = constructor.getDeclaringClass(); - StringBuilder message = new StringBuilder(); - message.append(format( - "`%s` constructor must be package-private to be registered and used in " + - "`setTo` in `Environment`.", - envType.getSimpleName())); - if (ctor.isPublic()) { - message.append(format( - " As `%s` has a public constructor, you may use " + - "`Environment.register(envInstance)` and " + - "`environment.setTo(envInstance)`.", - envType.getSimpleName())); - } - throw newIllegalArgumentException(message.toString()); + invokable.setAccessible(isAccessible); + String message = "Could not instantiate `%s`."; + throw newIllegalArgumentException(e, message, type.getSimpleName()); } } + /** + * If the specified type has a constructor with 0 arguments, returns it. + * + *

Otherwise, throws an {@code IllegalArgumentException}. + * + * @param type + * type to check + * @return the specified instance, if it has a parameterless constructor + */ + @CanIgnoreReturnValue @SuppressWarnings("unchecked" /* * Casting from `Constructor` to * `Constructor - checkHasParameterlessCtor(Class type) { + static Constructor + ensureParameterlessCtor(Class type) { for (Constructor constructor : type.getDeclaredConstructors()) { if (constructor.getParameterCount() == 0) { return (Constructor) constructor; } } - throw newIllegalArgumentException("To `register` `%s` or use it in `setTo` by class, " + - "it must have a parameterless package-private " + - "constructor.", + throw newIllegalArgumentException("No parameterless ctor found in class `%s`.", type.getSimpleName()); } + + private static EnvironmentType + instantiatePreservingAccessibility(Constructor ctor) + throws IllegalAccessException, InvocationTargetException, InstantiationException { + boolean accessible = ctor.isAccessible(); + ctor.setAccessible(true); + EnvironmentType result = ctor.newInstance(); + ctor.setAccessible(accessible); + return result; + } } diff --git a/base/src/test/java/io/spine/base/EnvironmentTypesTest.java b/base/src/test/java/io/spine/base/EnvironmentTypesTest.java index cd7b396697..557ccde493 100644 --- a/base/src/test/java/io/spine/base/EnvironmentTypesTest.java +++ b/base/src/test/java/io/spine/base/EnvironmentTypesTest.java @@ -20,17 +20,14 @@ package io.spine.base; +import io.spine.base.given.AwsLambda; +import io.spine.base.given.VariableControlledEnvironment; import io.spine.testing.UtilityClassTest; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import java.util.stream.Stream; import static com.google.common.truth.Truth.assertThat; -import static io.spine.base.EnvironmentTypes.checkCanRegisterByClass; import static io.spine.base.EnvironmentTypes.instantiate; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -41,96 +38,110 @@ class EnvironmentTypesTest extends UtilityClassTest { super(EnvironmentTypes.class); } - @Test - @DisplayName("allow to register an env type with a package-private parameterless ctor") - void allowToRegister() { - Class local = Local.class; - assertThat(checkCanRegisterByClass(Local.class)).isSameInstanceAs(local); - } + @Nested + @DisplayName("not instantiate an env type") + class NotInstantiate { - @Test - @DisplayName("allow to instantiate an env type with a package-private parameterless ctor") - void allowToInstantiate() { - Class local = Local.class; - EnvironmentType localEnv = instantiate(local); - assertThat(localEnv.enabled()).isTrue(); - } + @Test + @DisplayName("if it doesn't have a parameterless constructor") + void noMatchingCtor() { + assertThrows(IllegalArgumentException.class, + () -> EnvironmentTypes.instantiate(ValueDependantEnvironment.class)); + } - @DisplayName("Disallow to register env types by classes if they do not have a" + - "package-private parameterless constructor") - @ParameterizedTest - @MethodSource("envTypesAndMethods") - void checkCanRegister(Class environmentType) { - assertThrows(IllegalArgumentException.class, - () -> EnvironmentTypes.checkCanRegisterByClass(environmentType)); - } + @Test + @DisplayName("if it is a nested class") + void nestedClass() { + assertThrows(IllegalArgumentException.class, + () -> EnvironmentTypes.instantiate(NestedEnvironment.class)); + } + + @Test + @DisplayName("if it is an abstract class") + void abstractClass() { + assertThrows(IllegalArgumentException.class, + () -> EnvironmentTypes.instantiate(VariableControlledEnvironment.class)); + } + + private final class NestedEnvironment extends EnvironmentType { - private static Stream envTypesAndMethods() { - Stream> envTypes = Stream.of( - ValueDependantEnvironment.class, - HiddenEnvironment.class, - ProtectedEnvironment.class, - VisibleEnvironment.class - ); - return envTypes.map(Arguments::arguments); + @Override + protected boolean enabled() { + return false; + } + } } - private class ValueDependantEnvironment extends EnvironmentType { + @Nested + @DisplayName("instantiate an env type") + class Instantiate { - private final boolean enabled; + @Test + @DisplayName("instantiate an env type with a parameterless ctor") + void allowToInstantiate() { + Class local = Local.class; + EnvironmentType localEnv = instantiate(local); + assertThat(localEnv.enabled()).isTrue(); + } - ValueDependantEnvironment(boolean enabled) { - this.enabled = enabled; + @Test + @DisplayName("instantiate an env type that has a parameterless ctor among declared ctors") + void allowToInstantiateMoreThan1Ctor() { + Class env = ConfigurableEnvironment.class; + EnvironmentType envType = instantiate(env); + assertThat(envType.enabled()).isFalse(); } - @Override - protected boolean enabled() { - return enabled; + @Test + @DisplayName("instantiate an env type that extends a class that has no parameterless ctor") + void allowToInstantiateExtends() { + Class awsLambda = AwsLambda.class; + EnvironmentType environment = instantiate(awsLambda); + assertThat(environment).isInstanceOf(AwsLambda.class); } } - private class HiddenEnvironment extends EnvironmentType { + private static final class Local extends EnvironmentType { - private HiddenEnvironment() { + Local() { } @Override protected boolean enabled() { - return false; + return true; } } - private class ProtectedEnvironment extends EnvironmentType { + @SuppressWarnings("unused" /* need ctors to tests reflection-using functionality. */) + private static final class ConfigurableEnvironment extends EnvironmentType { - protected ProtectedEnvironment() { - } + private final boolean enabled; - @Override - protected boolean enabled() { - return false; + ConfigurableEnvironment(boolean enabled) { + this.enabled = enabled; } - } - private class VisibleEnvironment extends EnvironmentType { - - @SuppressWarnings("PublicConstructorInNonPublicClass") - public VisibleEnvironment() { + ConfigurableEnvironment() { + this.enabled = false; } @Override protected boolean enabled() { - return true; + return enabled; } } - private static class Local extends EnvironmentType { + private static final class ValueDependantEnvironment extends EnvironmentType { - Local() { + private final boolean enabled; + + ValueDependantEnvironment(boolean enabled) { + this.enabled = enabled; } @Override protected boolean enabled() { - return true; + return enabled; } } } diff --git a/base/src/test/java/io/spine/base/given/AwsLambda.java b/base/src/test/java/io/spine/base/given/AwsLambda.java new file mode 100644 index 0000000000..1f612f12ac --- /dev/null +++ b/base/src/test/java/io/spine/base/given/AwsLambda.java @@ -0,0 +1,31 @@ +/* + * Copyright 2020, TeamDev. All rights reserved. + * + * 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.base.given; + +/** + * AWS Lambda environment is enabled if a specific env variable is set. + */ +public class AwsLambda extends VariableControlledEnvironment { + + public AwsLambda() { + super("AWS_LAMBDA_FUNCTION_NAME"); + } +} diff --git a/base/src/test/java/io/spine/base/given/VariableControlledEnvironment.java b/base/src/test/java/io/spine/base/given/VariableControlledEnvironment.java new file mode 100644 index 0000000000..dd2439c62c --- /dev/null +++ b/base/src/test/java/io/spine/base/given/VariableControlledEnvironment.java @@ -0,0 +1,48 @@ +/* + * Copyright 2020, TeamDev. All rights reserved. + * + * 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.base.given; + +import io.spine.base.EnvironmentType; + +/** + * An environment {@linkplain #enabled() that is enabled} based on the value of an env variable + * specified to the constructor. + */ +@SuppressWarnings("AbstractClassWithoutAbstractMethods") +public abstract class VariableControlledEnvironment extends EnvironmentType { + + private final String envVariable; + + VariableControlledEnvironment(String variable) { + this.envVariable = variable; + } + + @SuppressWarnings("unused" /* invoked via reflection. */) + VariableControlledEnvironment() { + this.envVariable = ""; + } + + @SuppressWarnings("AccessOfSystemProperties") + @Override + public final boolean enabled() { + return System.getProperty(envVariable) != null; + } +} From a8127346000d7e116f94c896d854547cdb2c93dc Mon Sep 17 00:00:00 2001 From: "serhii.lekariev" Date: Thu, 11 Jun 2020 16:04:21 +0300 Subject: [PATCH 23/43] Correct documentation errors. --- base/src/main/java/io/spine/base/Environment.java | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/base/src/main/java/io/spine/base/Environment.java b/base/src/main/java/io/spine/base/Environment.java index 034d713a71..ef35714f0e 100644 --- a/base/src/main/java/io/spine/base/Environment.java +++ b/base/src/main/java/io/spine/base/Environment.java @@ -269,7 +269,7 @@ public boolean isTests() { /** * Verifies if the code runs in the production mode. * - *

This method is opposite to {@link #isTests()} + *

This method is opposite to {@link #isTests()}. * * @return {@code true} if the code runs in the production mode, {@code false} otherwise * @see Production @@ -293,7 +293,7 @@ public void restoreFrom(Environment copy) { } /** - * Forces the specified environment type to be the current one. + * Sets the current environment type to {@code type.getClass()}. Overrides the current value. */ @VisibleForTesting public void setTo(EnvironmentType type) { @@ -301,10 +301,7 @@ public void setTo(EnvironmentType type) { } /** - * Forces the specified environment type to be the current one. - * - *

The specified type must have a package-private parameterless constructor, otherwise - * an {@code IllegalStateException} is thrown. + * Sets the current environment type to the specified one. Overrides the current value. */ @Internal @VisibleForTesting @@ -318,7 +315,7 @@ public void setTo(Class type) { * *

This method is opposite to {@link #setToProduction()}. * - * @deprecated use {@link #setTo(EnvironmentType)} + * @deprecated use {@link #setTo(Class)} */ @Deprecated @VisibleForTesting @@ -332,7 +329,7 @@ public void setToTests() { * *

This method is opposite to {@link #setToTests()}. * - * @deprecated use {@link #setTo(EnvironmentType)} + * @deprecated use {@link #setTo(Class)} */ @Deprecated @VisibleForTesting From 331ca592e4a3d71a6beb77a80aec8de417306720 Mon Sep 17 00:00:00 2001 From: "serhii.lekariev" Date: Thu, 11 Jun 2020 16:13:36 +0300 Subject: [PATCH 24/43] Correct docs. --- base/src/main/java/io/spine/base/Environment.java | 6 ++++-- .../main/java/io/spine/base/EnvironmentTypes.java | 2 +- .../src/test/java/io/spine/base/EnvironmentTest.java | 12 ------------ 3 files changed, 5 insertions(+), 15 deletions(-) diff --git a/base/src/main/java/io/spine/base/Environment.java b/base/src/main/java/io/spine/base/Environment.java index ef35714f0e..46a85483e4 100644 --- a/base/src/main/java/io/spine/base/Environment.java +++ b/base/src/main/java/io/spine/base/Environment.java @@ -119,8 +119,8 @@ * * System.clearProperty(AwsLambda.AWS_ENV_VARIABLE); * - * // Even though `AwsLambda` is technically not active, we have cached the value, - * // and `is(AwsLambda)` is `true`. + * // Even though `AwsLambda` is not active, we have cached the value, and `is(AwsLambda.class)` + * // is `true`. * assertThat(Environment.instance().is(AwsLambda.class)).isTrue(); * * @@ -188,6 +188,8 @@ public Environment register(EnvironmentType environmentType) { * determine whether it's enabled} later. * *

The specified {@code type} must have a parameterless constructor. + * {@linkplain EnvironmentType#enabled() activity} of the specified environment type is going + * to be checked against an instance created by invoking the parameterless constructor. * *

Otherwise, behaves like {@link #register(EnvironmentType)}. * diff --git a/base/src/main/java/io/spine/base/EnvironmentTypes.java b/base/src/main/java/io/spine/base/EnvironmentTypes.java index 28df03d833..e7520c7819 100644 --- a/base/src/main/java/io/spine/base/EnvironmentTypes.java +++ b/base/src/main/java/io/spine/base/EnvironmentTypes.java @@ -66,7 +66,7 @@ static EnvironmentType instantiate(Class type) { } /** - * If the specified type has a constructor with 0 arguments, returns it. + * If the specified type has a constructor with 0 arguments, returns the type. * *

Otherwise, throws an {@code IllegalArgumentException}. * diff --git a/base/src/test/java/io/spine/base/EnvironmentTest.java b/base/src/test/java/io/spine/base/EnvironmentTest.java index 2a1849d9c3..470c36d22c 100644 --- a/base/src/test/java/io/spine/base/EnvironmentTest.java +++ b/base/src/test/java/io/spine/base/EnvironmentTest.java @@ -291,9 +291,6 @@ void cacheTests() throws InterruptedException { @Immutable static final class Local extends EnvironmentType { - Local() { - } - @Override public boolean enabled() { // `LOCAL` is the default custom env type. It should be used as a fallback. @@ -306,12 +303,6 @@ static final class Staging extends EnvironmentType { static final String STAGING_ENV_TYPE_KEY = "io.spine.base.EnvironmentTest.is_staging"; - /** - * A package-private parameterless ctor allows to register this type by class. - */ - Staging() { - } - @Override public boolean enabled() { return String.valueOf(true) @@ -330,9 +321,6 @@ static void reset() { @Immutable static final class Travis extends EnvironmentType { - Travis() { - } - @Override public boolean enabled() { return false; From 5522e8cbf7de3300f495eaa7ade42827ae9d0a15 Mon Sep 17 00:00:00 2001 From: "serhii.lekariev" Date: Thu, 11 Jun 2020 16:28:26 +0300 Subject: [PATCH 25/43] Correct documentation errors. --- .../src/main/java/io/spine/base/Environment.java | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/base/src/main/java/io/spine/base/Environment.java b/base/src/main/java/io/spine/base/Environment.java index 46a85483e4..4119e32093 100644 --- a/base/src/main/java/io/spine/base/Environment.java +++ b/base/src/main/java/io/spine/base/Environment.java @@ -110,19 +110,27 @@ *

{@code Environment} caches the {@code EnvironmentType} once its calculated. * This means that if one environment type has been found to be active, its instance is saved. * If later it becomes logically inactive, e.g. the environment variable that's used to check the - * environment type changes, {@code Environment} is still going to return the cached value. + * environment type changes, {@code Environment} is still going to return the cached value, unless + * it is {@linkplain #setTo(EnvironmentType) set manually} or {@linkplain #reset() reset + * explicitly}. + * * For example: *

+ *     Environment environment = Environment.instance();
  *     EnvironmentType awsLambda = new AwsLambda();
- *     Environment.instance().register(awsLambda);
- *     assertThat(Environment.instance().is(AwsLambda.class)).isTrue();
+ *     environment.register(awsLambda);
+ *     assertThat(environment.is(AwsLambda.class)).isTrue();
  *
  *     System.clearProperty(AwsLambda.AWS_ENV_VARIABLE);
  *
  *     // Even though `AwsLambda` is not active, we have cached the value, and `is(AwsLambda.class)`
  *     // is `true`.
- *     assertThat(Environment.instance().is(AwsLambda.class)).isTrue();
+ *     assertThat(environment.is(AwsLambda.class)).isTrue();
+ *
+ *     environment.reset();
  *
+ *     // When `reset` explicitly, cached value is erased.
+ *     assertThat(environment.is(AwsLambda.class)).isFalse();
  * 
* *

{@linkplain #setTo(EnvironmentType) explicitly setting} the environment type overrides From 47ed3bd4498a27145ecf71dfebeee60afb2a3b5f Mon Sep 17 00:00:00 2001 From: "serhii.lekariev" Date: Thu, 11 Jun 2020 17:51:34 +0300 Subject: [PATCH 26/43] Extract utilities. --- .../main/java/io/spine/base/Environment.java | 12 +- .../java/io/spine/base/EnvironmentTypes.java | 105 --------------- .../java/io/spine/reflect/Constructors.java | 62 +++++++++ .../main/java/io/spine/reflect/Objects.java | 73 +++++++++++ .../io/spine/reflect/ConstructorsTest.java | 105 +++++++++++++++ .../java/io/spine/reflect/ObjectsTest.java | 123 ++++++++++++++++++ .../reflect/given/ConstructorsTestEnv.java | 121 +++++++++++++++++ license-report.md | 30 ++--- 8 files changed, 506 insertions(+), 125 deletions(-) delete mode 100644 base/src/main/java/io/spine/base/EnvironmentTypes.java create mode 100644 base/src/main/java/io/spine/reflect/Constructors.java create mode 100644 base/src/main/java/io/spine/reflect/Objects.java create mode 100644 base/src/test/java/io/spine/reflect/ConstructorsTest.java create mode 100644 base/src/test/java/io/spine/reflect/ObjectsTest.java create mode 100644 base/src/test/java/io/spine/reflect/given/ConstructorsTestEnv.java diff --git a/base/src/main/java/io/spine/base/Environment.java b/base/src/main/java/io/spine/base/Environment.java index 4119e32093..85363feb19 100644 --- a/base/src/main/java/io/spine/base/Environment.java +++ b/base/src/main/java/io/spine/base/Environment.java @@ -25,9 +25,11 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.spine.annotation.Internal; import io.spine.annotation.SPI; +import io.spine.reflect.Objects; import org.checkerframework.checker.nullness.qual.Nullable; import static com.google.common.base.Preconditions.checkNotNull; +import static io.spine.reflect.Constructors.ensureParameterlessCtor; import static io.spine.util.Exceptions.newIllegalStateException; /** @@ -41,10 +43,10 @@ * Two environment types exist out of the box: * *

* *

The framework users may define their custom settings depending on the current environment @@ -208,8 +210,8 @@ public Environment register(EnvironmentType environmentType) { @Internal @CanIgnoreReturnValue Environment register(Class type) { - EnvironmentTypes.ensureParameterlessCtor(type); - EnvironmentType envTypeInstance = EnvironmentTypes.instantiate(type); + ensureParameterlessCtor(type); + EnvironmentType envTypeInstance = Objects.instantiateWithoutParameters(type); return register(envTypeInstance); } diff --git a/base/src/main/java/io/spine/base/EnvironmentTypes.java b/base/src/main/java/io/spine/base/EnvironmentTypes.java deleted file mode 100644 index e7520c7819..0000000000 --- a/base/src/main/java/io/spine/base/EnvironmentTypes.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2020, TeamDev. All rights reserved. - * - * 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.base; - -import com.google.common.reflect.Invokable; -import com.google.errorprone.annotations.CanIgnoreReturnValue; - -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; - -import static com.google.common.base.Preconditions.checkNotNull; -import static io.spine.util.Exceptions.newIllegalArgumentException; - -/** - * A utility class for working with and verifying {@link EnvironmentType} extenders. - */ -final class EnvironmentTypes { - - private EnvironmentTypes() { - } - - /** - * Tries to instantiate the specified environment type. - * - *

It can only be instantiated if it has a parameterless constructor, an {@code - * IllegalArgumentException} is thrown otherwise. - * - * @param type - * env type to instantiate - * @return a new {@code EnvironmentType} instance - */ - static EnvironmentType instantiate(Class type) { - checkNotNull(type); - Constructor ctor = ensureParameterlessCtor(type); - Invokable invokable = - Invokable.from(ctor); - boolean isAccessible = invokable.isAccessible(); - try { - EnvironmentType result = isAccessible - ? ctor.newInstance() - : instantiatePreservingAccessibility(ctor); - return result; - } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { - invokable.setAccessible(isAccessible); - String message = "Could not instantiate `%s`."; - throw newIllegalArgumentException(e, message, type.getSimpleName()); - } - } - - /** - * If the specified type has a constructor with 0 arguments, returns the type. - * - *

Otherwise, throws an {@code IllegalArgumentException}. - * - * @param type - * type to check - * @return the specified instance, if it has a parameterless constructor - */ - @CanIgnoreReturnValue - @SuppressWarnings("unchecked" /* - * Casting from `Constructor` to - * `Constructor - ensureParameterlessCtor(Class type) { - for (Constructor constructor : type.getDeclaredConstructors()) { - if (constructor.getParameterCount() == 0) { - return (Constructor) constructor; - } - } - - throw newIllegalArgumentException("No parameterless ctor found in class `%s`.", - type.getSimpleName()); - } - - private static EnvironmentType - instantiatePreservingAccessibility(Constructor ctor) - throws IllegalAccessException, InvocationTargetException, InstantiationException { - boolean accessible = ctor.isAccessible(); - ctor.setAccessible(true); - EnvironmentType result = ctor.newInstance(); - ctor.setAccessible(accessible); - return result; - } -} diff --git a/base/src/main/java/io/spine/reflect/Constructors.java b/base/src/main/java/io/spine/reflect/Constructors.java new file mode 100644 index 0000000000..17edea2322 --- /dev/null +++ b/base/src/main/java/io/spine/reflect/Constructors.java @@ -0,0 +1,62 @@ +/* + * Copyright 2020, TeamDev. All rights reserved. + * + * 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; + +import java.lang.reflect.Constructor; + +import static com.google.common.base.Preconditions.checkNotNull; +import static io.spine.util.Exceptions.newIllegalArgumentException; + +/** + * A utility for working with Java {@linkplain java.lang.reflect.Constructor constructors}. + */ +public final class Constructors { + + /** Prevents instantiation of this utility class. */ + private Constructors() { + } + + /** + * Tries to find a constructor with no arguments in the specified class or its parents. + * + *

If no such constructor has been found, throws an {@code IllegalArgumentException}. + * + * @param type + * class to look for constructors in + * @return a constructor with no parameters, if it exists + */ + @CanIgnoreReturnValue + public static Constructor ensureParameterlessCtor(Class type) { + checkNotNull(type); + @SuppressWarnings("unchecked" /* safe, as `Class` only declares `Constructor`. */) + Constructor[] ctors = (Constructor[]) type.getDeclaredConstructors(); + for (Constructor ctor : ctors) { + if (ctor.getParameterCount() == 0) { + return ctor; + } + } + + throw newIllegalArgumentException("No parameterless ctor found in class `%s`.", + type.getSimpleName()); + } +} diff --git a/base/src/main/java/io/spine/reflect/Objects.java b/base/src/main/java/io/spine/reflect/Objects.java new file mode 100644 index 0000000000..45c9d44eeb --- /dev/null +++ b/base/src/main/java/io/spine/reflect/Objects.java @@ -0,0 +1,73 @@ +/* + * Copyright 2020, TeamDev. All rights reserved. + * + * 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.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +import static com.google.common.base.Preconditions.checkNotNull; +import static io.spine.util.Exceptions.newIllegalStateException; + +/** + * A utility class for working reflectively with objects. + */ +public final class Objects { + + private Objects() { + } + + /** + * Attempts to create an instance of the specified type using a constructor without parameters. + * + *

If no such constructor exists, an {@code IllegalArgumentException} is thrown. + * + *

The access level does not matter: the constructor is made accessible during the method + * execution. It is always restored after object instantiation or an error. + * + * @param type + * class to instantiate + * @return the object created using a parameterless constructor + * @throws IllegalStateException + * if the class is abstract, or an exception happens in the + * parameterless constructor + * @throws IllegalArgumentException + * if the specified class does not have a parameterless + * constructors. Note that nested classes fall under this case + */ + public static C instantiateWithoutParameters(Class type) { + checkNotNull(type); + Constructor ctor = Constructors.ensureParameterlessCtor(type); + boolean accessible = ctor.isAccessible(); + try { + ctor.setAccessible(true); + C result = ctor.newInstance(); + return result; + } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) { + String simpleName = type.getSimpleName(); + throw newIllegalStateException(e, + "Could not instantiate the type `%s` using " + + "a parameterless constructor.", + simpleName); + } finally { + ctor.setAccessible(accessible); + } + } +} diff --git a/base/src/test/java/io/spine/reflect/ConstructorsTest.java b/base/src/test/java/io/spine/reflect/ConstructorsTest.java new file mode 100644 index 0000000000..8805fd2cff --- /dev/null +++ b/base/src/test/java/io/spine/reflect/ConstructorsTest.java @@ -0,0 +1,105 @@ +/* + * Copyright 2020, TeamDev. All rights reserved. + * + * 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.spine.reflect.given.ConstructorsTestEnv.Animal; +import io.spine.reflect.given.ConstructorsTestEnv.Cat; +import io.spine.reflect.given.ConstructorsTestEnv.Chicken; +import io.spine.reflect.given.ConstructorsTestEnv.ClassWithDefaultCtor; +import io.spine.reflect.given.ConstructorsTestEnv.Dog; +import io.spine.reflect.given.ConstructorsTestEnv.NoParameterlessConstructors; +import io.spine.testing.UtilityClassTest; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +import static com.google.common.truth.Truth.assertThat; +import static io.spine.reflect.Constructors.ensureParameterlessCtor; +import static io.spine.reflect.given.ConstructorsTestEnv.Animal.MISSING; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@DisplayName("`Constructors` should") +class ConstructorsTest extends UtilityClassTest { + + ConstructorsTest() { + super(Constructors.class); + } + + @Nested + @DisplayName("when looking for parameterless constructors") + class ParamaterlessCtors { + + @Test + @DisplayName("find one in a concrete class") + void findParameterless() throws IllegalAccessException, + InvocationTargetException, + InstantiationException { + Constructor constructor = ensureParameterlessCtor(Cat.class); + Cat cat = constructor.newInstance(); + assertThat(cat.greet()).contains(MISSING); + } + + @Test + @DisplayName("find one in an abstract class") + void findParameterlessInAbstract() { + Constructor constructor = ensureParameterlessCtor(Animal.class); + assertThrows(InstantiationException.class, constructor::newInstance); + } + + @Test + @DisplayName("find one if it's declared in a parent") + void findParameterlessConstructorsInTheParent() throws IllegalAccessException, + InvocationTargetException, + InstantiationException { + Constructor constructor = ensureParameterlessCtor(Dog.class); + Dog dog = constructor.newInstance(); + assertThat(dog.greet()).contains(MISSING); + } + + @Test + @DisplayName("not find one in a nested class") + void notFindInNested() { + assertThrows(IllegalArgumentException.class, + () -> ensureParameterlessCtor(Chicken.class)); + } + + @Test + @DisplayName("find a default one") + void defaultCtor() throws IllegalAccessException, + InvocationTargetException, + InstantiationException { + Constructor ctor = + ensureParameterlessCtor(ClassWithDefaultCtor.class); + ClassWithDefaultCtor instance = ctor.newInstance(); + assertThat(instance.instantiated()).isTrue(); + } + + @Test + @DisplayName("not find one if it's not declared in a concrete class") + void noParameterlessCtorInConcrete() { + assertThrows(IllegalArgumentException.class, () -> + ensureParameterlessCtor(NoParameterlessConstructors.class)); + } + } +} diff --git a/base/src/test/java/io/spine/reflect/ObjectsTest.java b/base/src/test/java/io/spine/reflect/ObjectsTest.java new file mode 100644 index 0000000000..2e69f5598b --- /dev/null +++ b/base/src/test/java/io/spine/reflect/ObjectsTest.java @@ -0,0 +1,123 @@ +/* + * Copyright 2020, TeamDev. All rights reserved. + * + * 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.spine.reflect.given.ConstructorsTestEnv.Animal; +import io.spine.reflect.given.ConstructorsTestEnv.Cat; +import io.spine.reflect.given.ConstructorsTestEnv.Chicken; +import io.spine.reflect.given.ConstructorsTestEnv.ClassWithPrivateCtor; +import io.spine.reflect.given.ConstructorsTestEnv.NoParameterlessConstructors; +import io.spine.reflect.given.ConstructorsTestEnv.ThrowingConstructor; +import io.spine.testing.UtilityClassTest; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Constructor; + +import static com.google.common.truth.Truth.assertThat; +import static io.spine.reflect.Constructors.ensureParameterlessCtor; +import static io.spine.reflect.Objects.instantiateWithoutParameters; +import static io.spine.reflect.given.ConstructorsTestEnv.Animal.MISSING; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@DisplayName("`Objects` should") +class ObjectsTest extends UtilityClassTest { + + ObjectsTest() { + super(Objects.class); + } + + @Test + @DisplayName("instantiate a class using a parameterless constructor") + void instantiate() { + Cat cat = instantiateWithoutParameters(Cat.class); + assertThat(cat.greet()).contains(MISSING); + } + + @Test + @DisplayName("fail to instantiate an abstract class") + void notInstantiateAbstractClass() { + assertThrows(IllegalStateException.class, () -> instantiateWithoutParameters(Animal.class)); + } + + @Test + @DisplayName("throw if there was an exception during class instantiation") + void throwIfThrows() { + assertThrows(IllegalStateException.class, + () -> instantiateWithoutParameters(ThrowingConstructor.class)); + } + + @Test + @DisplayName("fail to instantiate a nested class") + void notInstantiateNested() { + assertThrows(IllegalArgumentException.class, + () -> instantiateWithoutParameters(Chicken.class)); + } + + @Test + @DisplayName("instantiate a private class") + void instantiatePrivate() { + ClassWithPrivateCtor instance = instantiateWithoutParameters(ClassWithPrivateCtor.class); + assertThat(instance.instantiated()).isTrue(); + } + + @Test + @DisplayName("fail to instantiate a class without a parameterless ctor") + void noParameterlessCtor() { + assertThrows(IllegalArgumentException.class, + () -> instantiateWithoutParameters(NoParameterlessConstructors.class)); + } + + @Nested + @DisplayName("bring the accessibility back") + class Accessibility { + + @Test + @DisplayName("if the instantiation succeeded") + void success() { + Class privateCtorClass = ClassWithPrivateCtor.class; + Constructor ctor = + ensureParameterlessCtor(privateCtorClass); + assertThat(ctor.isAccessible()).isFalse(); + + ClassWithPrivateCtor instance = + instantiateWithoutParameters(privateCtorClass); + assertThat(instance.instantiated()).isTrue(); + + assertThat(ctor.isAccessible()).isFalse(); + } + + @Test + @DisplayName("if the instantiation failed") + void failure() { + Class throwingCtorClass = ThrowingConstructor.class; + Constructor ctor = + ensureParameterlessCtor(throwingCtorClass); + assertThat(ctor.isAccessible()).isFalse(); + + assertThrows(IllegalStateException.class, + () -> instantiateWithoutParameters(throwingCtorClass)); + + assertThat(ctor.isAccessible()).isFalse(); + } + } +} diff --git a/base/src/test/java/io/spine/reflect/given/ConstructorsTestEnv.java b/base/src/test/java/io/spine/reflect/given/ConstructorsTestEnv.java new file mode 100644 index 0000000000..cdb7a0a4f1 --- /dev/null +++ b/base/src/test/java/io/spine/reflect/given/ConstructorsTestEnv.java @@ -0,0 +1,121 @@ +/* + * Copyright 2020, TeamDev. All rights reserved. + * + * 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 static java.lang.String.format; + +@SuppressWarnings("unused" /* need unused members for reflection lookup. */) +public final class ConstructorsTestEnv { + + /** Prevents instantiation of this test env class. */ + private ConstructorsTestEnv() { + } + + public static class NoParameterlessConstructors { + + @SuppressWarnings("FieldCanBeLocal") + private final int id; + + public NoParameterlessConstructors(int id) { + this.id = id; + } + } + + public static class ClassWithDefaultCtor { + + public boolean instantiated() { + return true; + } + } + + public abstract static class Animal { + + public static final String MISSING = "missing"; + private final String name; + + protected Animal(String name) { + this.name = name; + } + + public Animal() { + this.name = MISSING; + } + + public abstract String makeSound(); + + public final String greet() { + return makeSound() + format(". My name is %s.", name); + } + } + + public static final class Cat extends Animal { + + public Cat(String name) { + super(name); + } + + public Cat() { + super(); + } + + @Override + public String makeSound() { + return "Meow"; + } + } + + public static final class Dog extends Animal { + + @Override + public String makeSound() { + return "Bark"; + } + } + + public final class Chicken extends Animal { + + @Override + public String makeSound() { + return "Cluck"; + } + } + + public static class ClassWithPrivateCtor { + + private ClassWithPrivateCtor() { + } + + public boolean instantiated() { + return true; + } + } + + public static class ThrowingConstructor { + + public ThrowingConstructor() { + throw new IllegalStateException(""); + } + + public boolean instantiated() { + return true; + } + } +} diff --git a/license-report.md b/license-report.md index c866fa0ad5..bd27dd9bba 100644 --- a/license-report.md +++ b/license-report.md @@ -328,7 +328,7 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Jun 11 11:32:19 EEST 2020** 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). +This report was generated on **Thu Jun 11 14:20:02 EEST 2020** 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). @@ -773,7 +773,7 @@ This report was generated on **Thu Jun 11 11:32:19 EEST 2020** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Jun 11 11:32:19 EEST 2020** 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). +This report was generated on **Thu Jun 11 14:20:02 EEST 2020** 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). @@ -1156,7 +1156,7 @@ This report was generated on **Thu Jun 11 11:32:19 EEST 2020** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Jun 11 11:32:20 EEST 2020** 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). +This report was generated on **Thu Jun 11 14:20:03 EEST 2020** 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). @@ -1521,7 +1521,7 @@ This report was generated on **Thu Jun 11 11:32:20 EEST 2020** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Jun 11 11:32:21 EEST 2020** 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). +This report was generated on **Thu Jun 11 14:20:03 EEST 2020** 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). @@ -1902,7 +1902,7 @@ This report was generated on **Thu Jun 11 11:32:21 EEST 2020** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Jun 11 11:32:21 EEST 2020** 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). +This report was generated on **Thu Jun 11 14:20:03 EEST 2020** 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). @@ -2281,7 +2281,7 @@ This report was generated on **Thu Jun 11 11:32:21 EEST 2020** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Jun 11 11:32:22 EEST 2020** 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). +This report was generated on **Thu Jun 11 14:20:04 EEST 2020** 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). @@ -2646,7 +2646,7 @@ This report was generated on **Thu Jun 11 11:32:22 EEST 2020** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Jun 11 11:32:22 EEST 2020** 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). +This report was generated on **Thu Jun 11 14:20:04 EEST 2020** 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). @@ -3065,7 +3065,7 @@ This report was generated on **Thu Jun 11 11:32:22 EEST 2020** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Jun 11 11:32:23 EEST 2020** 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). +This report was generated on **Thu Jun 11 14:20:04 EEST 2020** 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). @@ -3430,7 +3430,7 @@ This report was generated on **Thu Jun 11 11:32:23 EEST 2020** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Jun 11 11:32:23 EEST 2020** 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). +This report was generated on **Thu Jun 11 14:20:04 EEST 2020** 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). @@ -3795,7 +3795,7 @@ This report was generated on **Thu Jun 11 11:32:23 EEST 2020** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Jun 11 11:32:24 EEST 2020** 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). +This report was generated on **Thu Jun 11 14:20:05 EEST 2020** 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). @@ -4120,7 +4120,7 @@ This report was generated on **Thu Jun 11 11:32:24 EEST 2020** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Jun 11 11:32:24 EEST 2020** 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). +This report was generated on **Thu Jun 11 14:20:05 EEST 2020** 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). @@ -4453,7 +4453,7 @@ This report was generated on **Thu Jun 11 11:32:24 EEST 2020** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Jun 11 11:32:24 EEST 2020** 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). +This report was generated on **Thu Jun 11 14:20:05 EEST 2020** 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). @@ -4832,7 +4832,7 @@ This report was generated on **Thu Jun 11 11:32:24 EEST 2020** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Jun 11 11:32:25 EEST 2020** 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). +This report was generated on **Thu Jun 11 14:20:05 EEST 2020** 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). @@ -5165,7 +5165,7 @@ This report was generated on **Thu Jun 11 11:32:25 EEST 2020** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Jun 11 11:32:25 EEST 2020** 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). +This report was generated on **Thu Jun 11 14:20:06 EEST 2020** 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). @@ -5498,4 +5498,4 @@ This report was generated on **Thu Jun 11 11:32:25 EEST 2020** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Jun 11 11:32:25 EEST 2020** 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 **Thu Jun 11 14:20:06 EEST 2020** 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 db60efe125fb1da68bb350e7e3a137f55d4e822f Mon Sep 17 00:00:00 2001 From: "serhii.lekariev" Date: Thu, 11 Jun 2020 18:01:47 +0300 Subject: [PATCH 27/43] Clean up. --- .../main/java/io/spine/base/Environment.java | 6 +- .../io/spine/base/EnvironmentTypesTest.java | 147 ------------------ .../io/spine/reflect/ConstructorsTest.java | 11 -- .../reflect/given/ConstructorsTestEnv.java | 8 - 4 files changed, 3 insertions(+), 169 deletions(-) delete mode 100644 base/src/test/java/io/spine/base/EnvironmentTypesTest.java diff --git a/base/src/main/java/io/spine/base/Environment.java b/base/src/main/java/io/spine/base/Environment.java index 85363feb19..c35fa002e0 100644 --- a/base/src/main/java/io/spine/base/Environment.java +++ b/base/src/main/java/io/spine/base/Environment.java @@ -43,10 +43,10 @@ * Two environment types exist out of the box: * *

* *

The framework users may define their custom settings depending on the current environment diff --git a/base/src/test/java/io/spine/base/EnvironmentTypesTest.java b/base/src/test/java/io/spine/base/EnvironmentTypesTest.java deleted file mode 100644 index 557ccde493..0000000000 --- a/base/src/test/java/io/spine/base/EnvironmentTypesTest.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright 2020, TeamDev. All rights reserved. - * - * 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.base; - -import io.spine.base.given.AwsLambda; -import io.spine.base.given.VariableControlledEnvironment; -import io.spine.testing.UtilityClassTest; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -import static com.google.common.truth.Truth.assertThat; -import static io.spine.base.EnvironmentTypes.instantiate; -import static org.junit.jupiter.api.Assertions.assertThrows; - -@DisplayName("`EnvironmentTypes` should") -class EnvironmentTypesTest extends UtilityClassTest { - - EnvironmentTypesTest() { - super(EnvironmentTypes.class); - } - - @Nested - @DisplayName("not instantiate an env type") - class NotInstantiate { - - @Test - @DisplayName("if it doesn't have a parameterless constructor") - void noMatchingCtor() { - assertThrows(IllegalArgumentException.class, - () -> EnvironmentTypes.instantiate(ValueDependantEnvironment.class)); - } - - @Test - @DisplayName("if it is a nested class") - void nestedClass() { - assertThrows(IllegalArgumentException.class, - () -> EnvironmentTypes.instantiate(NestedEnvironment.class)); - } - - @Test - @DisplayName("if it is an abstract class") - void abstractClass() { - assertThrows(IllegalArgumentException.class, - () -> EnvironmentTypes.instantiate(VariableControlledEnvironment.class)); - } - - private final class NestedEnvironment extends EnvironmentType { - - @Override - protected boolean enabled() { - return false; - } - } - } - - @Nested - @DisplayName("instantiate an env type") - class Instantiate { - - @Test - @DisplayName("instantiate an env type with a parameterless ctor") - void allowToInstantiate() { - Class local = Local.class; - EnvironmentType localEnv = instantiate(local); - assertThat(localEnv.enabled()).isTrue(); - } - - @Test - @DisplayName("instantiate an env type that has a parameterless ctor among declared ctors") - void allowToInstantiateMoreThan1Ctor() { - Class env = ConfigurableEnvironment.class; - EnvironmentType envType = instantiate(env); - assertThat(envType.enabled()).isFalse(); - } - - @Test - @DisplayName("instantiate an env type that extends a class that has no parameterless ctor") - void allowToInstantiateExtends() { - Class awsLambda = AwsLambda.class; - EnvironmentType environment = instantiate(awsLambda); - assertThat(environment).isInstanceOf(AwsLambda.class); - } - } - - private static final class Local extends EnvironmentType { - - Local() { - } - - @Override - protected boolean enabled() { - return true; - } - } - - @SuppressWarnings("unused" /* need ctors to tests reflection-using functionality. */) - private static final class ConfigurableEnvironment extends EnvironmentType { - - private final boolean enabled; - - ConfigurableEnvironment(boolean enabled) { - this.enabled = enabled; - } - - ConfigurableEnvironment() { - this.enabled = false; - } - - @Override - protected boolean enabled() { - return enabled; - } - } - - private static final class ValueDependantEnvironment extends EnvironmentType { - - private final boolean enabled; - - ValueDependantEnvironment(boolean enabled) { - this.enabled = enabled; - } - - @Override - protected boolean enabled() { - return enabled; - } - } -} diff --git a/base/src/test/java/io/spine/reflect/ConstructorsTest.java b/base/src/test/java/io/spine/reflect/ConstructorsTest.java index 8805fd2cff..9014ff0621 100644 --- a/base/src/test/java/io/spine/reflect/ConstructorsTest.java +++ b/base/src/test/java/io/spine/reflect/ConstructorsTest.java @@ -24,7 +24,6 @@ import io.spine.reflect.given.ConstructorsTestEnv.Cat; import io.spine.reflect.given.ConstructorsTestEnv.Chicken; import io.spine.reflect.given.ConstructorsTestEnv.ClassWithDefaultCtor; -import io.spine.reflect.given.ConstructorsTestEnv.Dog; import io.spine.reflect.given.ConstructorsTestEnv.NoParameterlessConstructors; import io.spine.testing.UtilityClassTest; import org.junit.jupiter.api.DisplayName; @@ -67,16 +66,6 @@ void findParameterlessInAbstract() { assertThrows(InstantiationException.class, constructor::newInstance); } - @Test - @DisplayName("find one if it's declared in a parent") - void findParameterlessConstructorsInTheParent() throws IllegalAccessException, - InvocationTargetException, - InstantiationException { - Constructor constructor = ensureParameterlessCtor(Dog.class); - Dog dog = constructor.newInstance(); - assertThat(dog.greet()).contains(MISSING); - } - @Test @DisplayName("not find one in a nested class") void notFindInNested() { diff --git a/base/src/test/java/io/spine/reflect/given/ConstructorsTestEnv.java b/base/src/test/java/io/spine/reflect/given/ConstructorsTestEnv.java index cdb7a0a4f1..a887ad0df4 100644 --- a/base/src/test/java/io/spine/reflect/given/ConstructorsTestEnv.java +++ b/base/src/test/java/io/spine/reflect/given/ConstructorsTestEnv.java @@ -82,14 +82,6 @@ public String makeSound() { } } - public static final class Dog extends Animal { - - @Override - public String makeSound() { - return "Bark"; - } - } - public final class Chicken extends Animal { @Override From 044d76eb4358ad0e146473ce1bb7ad163a8f4528 Mon Sep 17 00:00:00 2001 From: "serhii.lekariev" Date: Thu, 11 Jun 2020 18:04:45 +0300 Subject: [PATCH 28/43] Mention erasing the registered classes. --- base/src/main/java/io/spine/base/Environment.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/base/src/main/java/io/spine/base/Environment.java b/base/src/main/java/io/spine/base/Environment.java index c35fa002e0..1635e41a36 100644 --- a/base/src/main/java/io/spine/base/Environment.java +++ b/base/src/main/java/io/spine/base/Environment.java @@ -352,6 +352,9 @@ public void setToProduction() { /** * Resets the instance and clears the {@link Tests#ENV_KEY_TESTS} variable. + * + *

Erases all registered environment types, leaving only {@code Tests} and {@code + * Production}. */ @VisibleForTesting public void reset() { From 38ff164dab7fbb2b9ebe1e304162a50048453364 Mon Sep 17 00:00:00 2001 From: "serhii.lekariev" Date: Thu, 11 Jun 2020 18:15:18 +0300 Subject: [PATCH 29/43] Fix documentation. --- base/src/main/java/io/spine/reflect/Constructors.java | 2 +- base/src/main/java/io/spine/reflect/Objects.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/base/src/main/java/io/spine/reflect/Constructors.java b/base/src/main/java/io/spine/reflect/Constructors.java index 17edea2322..ead1f74ca9 100644 --- a/base/src/main/java/io/spine/reflect/Constructors.java +++ b/base/src/main/java/io/spine/reflect/Constructors.java @@ -28,7 +28,7 @@ import static io.spine.util.Exceptions.newIllegalArgumentException; /** - * A utility for working with Java {@linkplain java.lang.reflect.Constructor constructors}. + * A utility for working with {@linkplain java.lang.reflect.Constructor constructors}. */ public final class Constructors { diff --git a/base/src/main/java/io/spine/reflect/Objects.java b/base/src/main/java/io/spine/reflect/Objects.java index 45c9d44eeb..ac616e15b9 100644 --- a/base/src/main/java/io/spine/reflect/Objects.java +++ b/base/src/main/java/io/spine/reflect/Objects.java @@ -46,7 +46,7 @@ private Objects() { * class to instantiate * @return the object created using a parameterless constructor * @throws IllegalStateException - * if the class is abstract, or an exception happens in the + * if the class is abstract, or an exception is thrown in the * parameterless constructor * @throws IllegalArgumentException * if the specified class does not have a parameterless From aa524819a01f157152704a078d4b75491e749168 Mon Sep 17 00:00:00 2001 From: "serhii.lekariev" Date: Thu, 11 Jun 2020 18:36:39 +0300 Subject: [PATCH 30/43] Update `config`. --- .idea/misc.xml | 3 --- .../buildSrc/src/main/kotlin/io/spine/gradle/internal/deps.kt | 2 +- buildSrc/src/main/kotlin/io/spine/gradle/internal/deps.kt | 2 +- .../buildSrc/src/main/kotlin/io/spine/gradle/internal/deps.kt | 2 +- 4 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.idea/misc.xml b/.idea/misc.xml index 90c11c2586..deb97c8116 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,8 +1,5 @@ - - - diff --git a/base-validating-builders/buildSrc/src/main/kotlin/io/spine/gradle/internal/deps.kt b/base-validating-builders/buildSrc/src/main/kotlin/io/spine/gradle/internal/deps.kt index 6846093b1f..77a10789fe 100644 --- a/base-validating-builders/buildSrc/src/main/kotlin/io/spine/gradle/internal/deps.kt +++ b/base-validating-builders/buildSrc/src/main/kotlin/io/spine/gradle/internal/deps.kt @@ -71,7 +71,7 @@ object Versions { val errorProne = "2.3.4" val errorProneJavac = "9+181-r4173-1" // taken from here: https://github.com/tbroyer/gradle-errorprone-plugin/blob/v0.8/build.gradle.kts val errorPronePlugin = "1.1.1" - val pmd = "6.20.0" + val pmd = "6.24.0" val checkstyle = "8.29" val protobufPlugin = "0.8.12" val appengineApi = "1.9.79" diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/internal/deps.kt b/buildSrc/src/main/kotlin/io/spine/gradle/internal/deps.kt index 6846093b1f..77a10789fe 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/internal/deps.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/internal/deps.kt @@ -71,7 +71,7 @@ object Versions { val errorProne = "2.3.4" val errorProneJavac = "9+181-r4173-1" // taken from here: https://github.com/tbroyer/gradle-errorprone-plugin/blob/v0.8/build.gradle.kts val errorPronePlugin = "1.1.1" - val pmd = "6.20.0" + val pmd = "6.24.0" val checkstyle = "8.29" val protobufPlugin = "0.8.12" val appengineApi = "1.9.79" diff --git a/tools/smoke-tests/buildSrc/src/main/kotlin/io/spine/gradle/internal/deps.kt b/tools/smoke-tests/buildSrc/src/main/kotlin/io/spine/gradle/internal/deps.kt index 6846093b1f..77a10789fe 100644 --- a/tools/smoke-tests/buildSrc/src/main/kotlin/io/spine/gradle/internal/deps.kt +++ b/tools/smoke-tests/buildSrc/src/main/kotlin/io/spine/gradle/internal/deps.kt @@ -71,7 +71,7 @@ object Versions { val errorProne = "2.3.4" val errorProneJavac = "9+181-r4173-1" // taken from here: https://github.com/tbroyer/gradle-errorprone-plugin/blob/v0.8/build.gradle.kts val errorPronePlugin = "1.1.1" - val pmd = "6.20.0" + val pmd = "6.24.0" val checkstyle = "8.29" val protobufPlugin = "0.8.12" val appengineApi = "1.9.79" From 431dd04adf757d68aad1dd7b23b66a9701dbbba7 Mon Sep 17 00:00:00 2001 From: "serhii.lekariev" Date: Fri, 12 Jun 2020 12:24:57 +0300 Subject: [PATCH 31/43] Extract reflection utils into a single `Invokables` class. --- .../main/java/io/spine/base/Environment.java | 6 +- .../java/io/spine/reflect/Constructors.java | 62 ---- .../java/io/spine/reflect/Invokables.java | 207 +++++++++++++ .../main/java/io/spine/reflect/Methods.java | 102 ------ .../main/java/io/spine/reflect/Objects.java | 73 ----- .../io/spine/reflect/ConstructorsTest.java | 94 ------ .../java/io/spine/reflect/InvokablesTest.java | 290 ++++++++++++++++++ .../java/io/spine/reflect/MethodsTest.java | 118 ------- .../java/io/spine/reflect/ObjectsTest.java | 123 -------- 9 files changed, 500 insertions(+), 575 deletions(-) delete mode 100644 base/src/main/java/io/spine/reflect/Constructors.java create mode 100644 base/src/main/java/io/spine/reflect/Invokables.java delete mode 100644 base/src/main/java/io/spine/reflect/Methods.java delete mode 100644 base/src/main/java/io/spine/reflect/Objects.java delete mode 100644 base/src/test/java/io/spine/reflect/ConstructorsTest.java create mode 100644 base/src/test/java/io/spine/reflect/InvokablesTest.java delete mode 100644 base/src/test/java/io/spine/reflect/MethodsTest.java delete mode 100644 base/src/test/java/io/spine/reflect/ObjectsTest.java diff --git a/base/src/main/java/io/spine/base/Environment.java b/base/src/main/java/io/spine/base/Environment.java index 1635e41a36..286ba6d26c 100644 --- a/base/src/main/java/io/spine/base/Environment.java +++ b/base/src/main/java/io/spine/base/Environment.java @@ -25,11 +25,11 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.spine.annotation.Internal; import io.spine.annotation.SPI; -import io.spine.reflect.Objects; import org.checkerframework.checker.nullness.qual.Nullable; import static com.google.common.base.Preconditions.checkNotNull; -import static io.spine.reflect.Constructors.ensureParameterlessCtor; +import static io.spine.reflect.Invokables.ensureParameterlessCtor; +import static io.spine.reflect.Invokables.instantiateWithoutParameters; import static io.spine.util.Exceptions.newIllegalStateException; /** @@ -211,7 +211,7 @@ public Environment register(EnvironmentType environmentType) { @CanIgnoreReturnValue Environment register(Class type) { ensureParameterlessCtor(type); - EnvironmentType envTypeInstance = Objects.instantiateWithoutParameters(type); + EnvironmentType envTypeInstance = instantiateWithoutParameters(type); return register(envTypeInstance); } diff --git a/base/src/main/java/io/spine/reflect/Constructors.java b/base/src/main/java/io/spine/reflect/Constructors.java deleted file mode 100644 index ead1f74ca9..0000000000 --- a/base/src/main/java/io/spine/reflect/Constructors.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2020, TeamDev. All rights reserved. - * - * 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; - -import java.lang.reflect.Constructor; - -import static com.google.common.base.Preconditions.checkNotNull; -import static io.spine.util.Exceptions.newIllegalArgumentException; - -/** - * A utility for working with {@linkplain java.lang.reflect.Constructor constructors}. - */ -public final class Constructors { - - /** Prevents instantiation of this utility class. */ - private Constructors() { - } - - /** - * Tries to find a constructor with no arguments in the specified class or its parents. - * - *

If no such constructor has been found, throws an {@code IllegalArgumentException}. - * - * @param type - * class to look for constructors in - * @return a constructor with no parameters, if it exists - */ - @CanIgnoreReturnValue - public static Constructor ensureParameterlessCtor(Class type) { - checkNotNull(type); - @SuppressWarnings("unchecked" /* safe, as `Class` only declares `Constructor`. */) - Constructor[] ctors = (Constructor[]) type.getDeclaredConstructors(); - for (Constructor ctor : ctors) { - if (ctor.getParameterCount() == 0) { - return ctor; - } - } - - throw newIllegalArgumentException("No parameterless ctor found in class `%s`.", - type.getSimpleName()); - } -} diff --git a/base/src/main/java/io/spine/reflect/Invokables.java b/base/src/main/java/io/spine/reflect/Invokables.java new file mode 100644 index 0000000000..d403a4cc29 --- /dev/null +++ b/base/src/main/java/io/spine/reflect/Invokables.java @@ -0,0 +1,207 @@ +/* + * Copyright 2020, TeamDev. All rights reserved. + * + * 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.common.reflect.Invokable; +import com.google.errorprone.annotations.CanIgnoreReturnValue; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.function.Function; +import java.util.function.Supplier; + +import static com.google.common.base.Preconditions.checkNotNull; +import static io.spine.util.Exceptions.newIllegalArgumentException; +import static io.spine.util.Exceptions.newIllegalStateException; +import static java.lang.String.format; +import static java.lang.invoke.MethodHandles.publicLookup; + +/** + * A utility class for working with Java {@linkplain java.lang.reflect.Method methods} and + * {@linkplain java.lang.reflect.Constructor constructors}. + */ +public final class Invokables { + + private static final MethodHandles.Lookup publicLookup = publicLookup(); + + /** Prevents instantiation of this utility class. */ + private Invokables() { + } + + /** + * Tries to find a constructor with no arguments in the specified class or its parents. + * + *

If no such constructor has been found, throws an {@code IllegalArgumentException}. + * + * @param type + * class to look for constructors in + * @return a constructor with no parameters, if it exists + */ + @CanIgnoreReturnValue + public static Constructor ensureParameterlessCtor(Class type) { + checkNotNull(type); + @SuppressWarnings("unchecked" /* safe, as `Class` only declares `Constructor`. */) + Constructor[] ctors = (Constructor[]) type.getDeclaredConstructors(); + for (Constructor ctor : ctors) { + if (ctor.getParameterCount() == 0) { + return ctor; + } + } + + throw newIllegalArgumentException("No parameterless ctor found in class `%s`.", + type.getSimpleName()); + } + + /** + * Converts the given {@link Method} into a {@link MethodHandle}. + * + *

The accessibility parameter of the input method, i.e. {@code method.isAccessible()}, is + * preserved by this method. However, the attributes may be changes in a non-synchronized + * manner, i.e. the {@code asHandle(..)} is not designed to operate concurrently. + */ + public static MethodHandle asHandle(Method method) { + checkNotNull(method); + MethodHandle result = invokePreservingAccessibility( + method, + Invokable::from, + publicLookup::unreflect, + () -> format( + "Unable to obtain method handle for `%s`." + + " The method's accessibility was probably changed concurrently.", + method)); + return result; + } + + /** + * Attempts to create an instance of the specified type using a constructor without parameters. + * + *

If no such constructor exists, an {@code IllegalArgumentException} is thrown. + * + *

The access level does not matter: the constructor is made accessible during the method + * execution. It is always restored after object instantiation or an error. + * + * @param type + * class to instantiate + * @return the object created using a parameterless constructor + * @throws IllegalStateException + * if the class is abstract, or an exception is thrown in the + * parameterless constructor + * @throws IllegalArgumentException + * if the specified class does not have a parameterless + * constructors. Note that nested classes fall under this case + */ + + public static C instantiateWithoutParameters(Class type) { + checkNotNull(type); + Constructor ctor = ensureParameterlessCtor(type); + C result = invokePreservingAccessibility( + ctor, + Invokable::from, + Constructor::newInstance, + () -> format( + "Could not instantiate the type `%s` using " + + "a parameterless constructor.", + type.getSimpleName())); + return result; + } + + /** + * Invokes the given argumentless method on the target ignoring the accessibility restrictions. + * + *

The target must be of the type that declares the given method, otherwise an + * {@link IllegalArgumentException} is thrown. + * + * @throws IllegalArgumentException + * if the target is not of the type that declares the given method + * @throws IllegalStateException + * if an exception is thrown during the method invocation + */ + public static Object setAccessibleAndInvoke(Method method, Object target) { + checkNotNull(method); + checkNotNull(target); + + Object result = invokePreservingAccessibility( + method, + Invokable::from, + m -> m.invoke(target), + () -> format( + "Method `%s` invocation on target `%s` of class `%s` failed.", + method.getName(), target, + target.getClass() + .getCanonicalName())); + return result; + } + + /** + * Performs a reflective operation regardless of its accessibility, returns its result. + * + *

Upon completion, whether successful or erroneous, returns the accessibility to its + * initial state. + * + * @param reflectiveObject + * an object that can be used to make an {@code Invokable} + * @param makeInvokable + * a function of {@code P} -> {@code Invokable}. {@code Invokable} is + * needed to manipulate the accessibility in a generic manner + * @param fn + * a reflective function to perform + * @param onError + * a supplier of the error message to include into the {@code + * IllegalStateException} should an error be thrown + * @param + * a type of reflection-related object to perform a function on + * @param + * a type of result of the reflective function + * @return a result of the reflective function + */ + @SuppressWarnings("OverlyBroadCatchBlock" /* catching any runtimes does not hurt here. */) + private static R invokePreservingAccessibility(T reflectiveObject, + Function> makeInvokable, + ReflectiveFunction fn, + Supplier onError) { + checkNotNull(reflectiveObject); + checkNotNull(makeInvokable); + checkNotNull(fn); + checkNotNull(onError); + Invokable invokable = makeInvokable.apply(reflectiveObject); + boolean accessible = invokable.isAccessible(); + try { + invokable.setAccessible(true); + R result = fn.apply(reflectiveObject); + return result; + } catch (RuntimeException | ReflectiveOperationException e) { + String message = onError.get(); + throw newIllegalStateException(e, message); + } finally { + invokable.setAccessible(accessible); + } + } + + /** + * A function that may throw a reflection-related error on invocation. + */ + private interface ReflectiveFunction { + + R apply(T t) throws ReflectiveOperationException; + } +} diff --git a/base/src/main/java/io/spine/reflect/Methods.java b/base/src/main/java/io/spine/reflect/Methods.java deleted file mode 100644 index 1d8f466f35..0000000000 --- a/base/src/main/java/io/spine/reflect/Methods.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2020, TeamDev. All rights reserved. - * - * 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.invoke.MethodHandle; -import java.lang.invoke.MethodHandles.Lookup; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -import static com.google.common.base.Preconditions.checkNotNull; -import static io.spine.util.Exceptions.newIllegalArgumentException; -import static io.spine.util.Exceptions.newIllegalStateException; -import static java.lang.invoke.MethodHandles.publicLookup; - -/** - * A utility for working with Java {@linkplain Method methods}. - */ -public final class Methods { - - private static final Lookup publicLookup = publicLookup(); - - /** Prevents instantiation of this utility class. */ - private Methods() { - } - - /** - * Invokes the given argumentless method on the target ignoring the accessibility restrictions. - * - *

The target must be of the type that declares the given method, otherwise an - * {@link IllegalArgumentException} is thrown. - * - * @throws IllegalArgumentException - * if the target is not of the type that declares the given method - * @throws IllegalStateException - * if an exception is thrown during the method invocation - */ - public static Object setAccessibleAndInvoke(Method method, Object target) { - checkNotNull(method); - boolean accessible = method.isAccessible(); - try { - method.setAccessible(true); - Object result = method.invoke(target); - return result; - } catch (IllegalAccessException | InvocationTargetException e) { - throw newIllegalStateException( - e, - "Method `%s` invocation on target `%s` of class `%s` failed.", - method.getName(), target, target.getClass() - .getCanonicalName()); - } finally { - method.setAccessible(accessible); - } - } - - /** - * Converts the given {@link Method} into a {@link MethodHandle}. - * - *

The accessibility parameter of the input method, i.e. {@code method.isAccessible()}, is - * preserved by this method. However, the attributes may be changes in a non-synchronized - * manner, i.e. the {@code asHandle(..)} is not designed to operate concurrently. - */ - public static MethodHandle asHandle(Method method) { - checkNotNull(method); - boolean accessible = method.isAccessible(); - if (!accessible) { - method.setAccessible(true); - } - MethodHandle handle; - try { - handle = publicLookup.unreflect(method); - } catch (IllegalAccessException exception) { - throw newIllegalArgumentException( - exception, - "Unable to obtain method handle for `%s`." + - " The method's accessibility was probably changed concurrently.", - method); - } finally { - if (!accessible) { - method.setAccessible(false); - } - } - return handle; - } -} diff --git a/base/src/main/java/io/spine/reflect/Objects.java b/base/src/main/java/io/spine/reflect/Objects.java deleted file mode 100644 index ac616e15b9..0000000000 --- a/base/src/main/java/io/spine/reflect/Objects.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2020, TeamDev. All rights reserved. - * - * 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.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; - -import static com.google.common.base.Preconditions.checkNotNull; -import static io.spine.util.Exceptions.newIllegalStateException; - -/** - * A utility class for working reflectively with objects. - */ -public final class Objects { - - private Objects() { - } - - /** - * Attempts to create an instance of the specified type using a constructor without parameters. - * - *

If no such constructor exists, an {@code IllegalArgumentException} is thrown. - * - *

The access level does not matter: the constructor is made accessible during the method - * execution. It is always restored after object instantiation or an error. - * - * @param type - * class to instantiate - * @return the object created using a parameterless constructor - * @throws IllegalStateException - * if the class is abstract, or an exception is thrown in the - * parameterless constructor - * @throws IllegalArgumentException - * if the specified class does not have a parameterless - * constructors. Note that nested classes fall under this case - */ - public static C instantiateWithoutParameters(Class type) { - checkNotNull(type); - Constructor ctor = Constructors.ensureParameterlessCtor(type); - boolean accessible = ctor.isAccessible(); - try { - ctor.setAccessible(true); - C result = ctor.newInstance(); - return result; - } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) { - String simpleName = type.getSimpleName(); - throw newIllegalStateException(e, - "Could not instantiate the type `%s` using " + - "a parameterless constructor.", - simpleName); - } finally { - ctor.setAccessible(accessible); - } - } -} diff --git a/base/src/test/java/io/spine/reflect/ConstructorsTest.java b/base/src/test/java/io/spine/reflect/ConstructorsTest.java deleted file mode 100644 index 9014ff0621..0000000000 --- a/base/src/test/java/io/spine/reflect/ConstructorsTest.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2020, TeamDev. All rights reserved. - * - * 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.spine.reflect.given.ConstructorsTestEnv.Animal; -import io.spine.reflect.given.ConstructorsTestEnv.Cat; -import io.spine.reflect.given.ConstructorsTestEnv.Chicken; -import io.spine.reflect.given.ConstructorsTestEnv.ClassWithDefaultCtor; -import io.spine.reflect.given.ConstructorsTestEnv.NoParameterlessConstructors; -import io.spine.testing.UtilityClassTest; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; - -import static com.google.common.truth.Truth.assertThat; -import static io.spine.reflect.Constructors.ensureParameterlessCtor; -import static io.spine.reflect.given.ConstructorsTestEnv.Animal.MISSING; -import static org.junit.jupiter.api.Assertions.assertThrows; - -@DisplayName("`Constructors` should") -class ConstructorsTest extends UtilityClassTest { - - ConstructorsTest() { - super(Constructors.class); - } - - @Nested - @DisplayName("when looking for parameterless constructors") - class ParamaterlessCtors { - - @Test - @DisplayName("find one in a concrete class") - void findParameterless() throws IllegalAccessException, - InvocationTargetException, - InstantiationException { - Constructor constructor = ensureParameterlessCtor(Cat.class); - Cat cat = constructor.newInstance(); - assertThat(cat.greet()).contains(MISSING); - } - - @Test - @DisplayName("find one in an abstract class") - void findParameterlessInAbstract() { - Constructor constructor = ensureParameterlessCtor(Animal.class); - assertThrows(InstantiationException.class, constructor::newInstance); - } - - @Test - @DisplayName("not find one in a nested class") - void notFindInNested() { - assertThrows(IllegalArgumentException.class, - () -> ensureParameterlessCtor(Chicken.class)); - } - - @Test - @DisplayName("find a default one") - void defaultCtor() throws IllegalAccessException, - InvocationTargetException, - InstantiationException { - Constructor ctor = - ensureParameterlessCtor(ClassWithDefaultCtor.class); - ClassWithDefaultCtor instance = ctor.newInstance(); - assertThat(instance.instantiated()).isTrue(); - } - - @Test - @DisplayName("not find one if it's not declared in a concrete class") - void noParameterlessCtorInConcrete() { - assertThrows(IllegalArgumentException.class, () -> - ensureParameterlessCtor(NoParameterlessConstructors.class)); - } - } -} diff --git a/base/src/test/java/io/spine/reflect/InvokablesTest.java b/base/src/test/java/io/spine/reflect/InvokablesTest.java new file mode 100644 index 0000000000..955dcd19ee --- /dev/null +++ b/base/src/test/java/io/spine/reflect/InvokablesTest.java @@ -0,0 +1,290 @@ +/* + * Copyright 2020, TeamDev. All rights reserved. + * + * 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.common.testing.NullPointerTester; +import io.spine.reflect.given.ConstructorsTestEnv; +import io.spine.reflect.given.MethodsTestEnv.ClassWithPrivateMethod; +import io.spine.testing.UtilityClassTest; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.lang.invoke.MethodHandle; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import static com.google.common.truth.Truth.assertThat; +import static io.spine.reflect.Invokables.asHandle; +import static io.spine.reflect.Invokables.ensureParameterlessCtor; +import static io.spine.reflect.Invokables.instantiateWithoutParameters; +import static io.spine.reflect.Invokables.setAccessibleAndInvoke; +import static io.spine.reflect.given.ConstructorsTestEnv.Animal; +import static io.spine.reflect.given.ConstructorsTestEnv.Animal.MISSING; +import static io.spine.reflect.given.ConstructorsTestEnv.Cat; +import static io.spine.reflect.given.ConstructorsTestEnv.ClassWithDefaultCtor; +import static io.spine.reflect.given.ConstructorsTestEnv.ClassWithPrivateCtor; +import static io.spine.reflect.given.ConstructorsTestEnv.NoParameterlessConstructors; +import static io.spine.reflect.given.ConstructorsTestEnv.ThrowingConstructor; +import static io.spine.reflect.given.MethodsTestEnv.ClassWithPrivateMethod.METHOD_RESULT; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@DisplayName("`Invokables` should") +class InvokablesTest extends UtilityClassTest { + + InvokablesTest() { + super(Invokables.class); + } + + private Method privateMethod; + + @Override + protected void configure(NullPointerTester tester) { + super.configure(tester); + tester.setDefault(Method.class, objectToString()); + } + + @Nested + @DisplayName("when invoking methods") + class Methods { + + @BeforeEach + void obtainMethod() throws NoSuchMethodException { + privateMethod = ClassWithPrivateMethod.class.getDeclaredMethod("privateMethod"); + } + + @AfterEach + void resetMethod() { + privateMethod.setAccessible(false); + } + + @Test + @DisplayName("set accessible and invoke successfully") + void allowToSetAccessibleAndInvoke() { + ClassWithPrivateMethod target = new ClassWithPrivateMethod(); + Object result = setAccessibleAndInvoke(privateMethod, target); + + assertThat(result).isEqualTo(METHOD_RESULT); + } + + @SuppressWarnings("CheckReturnValue") // Called to throw exception. + @Test + @DisplayName("throw `IAE` if the given target is not a valid invocation target") + void throwOnInvalidTarget() { + Object wrongTarget = new Object(); + + assertThrows(IllegalStateException.class, + () -> setAccessibleAndInvoke(privateMethod, wrongTarget)); + } + + @SuppressWarnings("CheckReturnValue") // Called to throw exception. + @Test + @DisplayName("throw `ISE` if an exception is thrown during invocation") + void throwOnInvocationError() throws NoSuchMethodException { + Method method = ClassWithPrivateMethod.class.getDeclaredMethod("throwingMethod"); + ClassWithPrivateMethod target = new ClassWithPrivateMethod(); + + assertThrows(IllegalStateException.class, + () -> setAccessibleAndInvoke(method, target)); + } + + @Test + @DisplayName("convert a visible method to a handle") + void convertToHandle() throws Throwable { + Method method = ClassWithPrivateMethod.class.getMethod("publicMethod"); + MethodHandle handle = asHandle(method); + assertThat(handle).isNotNull(); + + Object invocationResult = handle.bindTo(new ClassWithPrivateMethod()) + .invoke(); + assertThat(invocationResult) + .isEqualTo(METHOD_RESULT); + } + + @Test + @DisplayName("convert an invisible method to a handle") + void convertInvisibleToHandle() throws Throwable { + MethodHandle handle = asHandle(privateMethod); + assertThat(handle).isNotNull(); + assertThat(privateMethod.isAccessible()).isFalse(); + + Object invocationResult = handle.invoke(new ClassWithPrivateMethod()); + assertThat(invocationResult) + .isEqualTo(METHOD_RESULT); + } + + @Test + @DisplayName("convert an accessible method to a handle") + void convertAccessibleToHandle() throws Throwable { + privateMethod.setAccessible(true); + MethodHandle handle = asHandle(privateMethod); + assertThat(privateMethod.isAccessible()).isTrue(); + assertThat(handle).isNotNull(); + + Object invocationResult = handle.invoke(new ClassWithPrivateMethod()); + assertThat(invocationResult) + .isEqualTo(METHOD_RESULT); + } + } + + @Nested + @DisplayName("when looking for parameterless constructors") + class ParamaterlessCtors { + + @Test + @DisplayName("find one in a concrete class") + void findParameterless() throws IllegalAccessException, + InvocationTargetException, + InstantiationException { + Constructor constructor = ensureParameterlessCtor( + Cat.class); + Cat cat = constructor.newInstance(); + assertThat(cat.greet()).contains(MISSING); + } + + @Test + @DisplayName("find one in an abstract class") + void findParameterlessInAbstract() { + Constructor constructor = ensureParameterlessCtor( + Animal.class); + assertThrows(InstantiationException.class, constructor::newInstance); + } + + @Test + @DisplayName("not find one in a nested class") + void notFindInNested() { + assertThrows(IllegalArgumentException.class, + () -> ensureParameterlessCtor(ConstructorsTestEnv.Chicken.class)); + } + + @Test + @DisplayName("find a default one") + void defaultCtor() throws IllegalAccessException, + InvocationTargetException, + InstantiationException { + Constructor ctor = + ensureParameterlessCtor(ClassWithDefaultCtor.class); + ClassWithDefaultCtor instance = ctor.newInstance(); + assertThat(instance.instantiated()).isTrue(); + } + + @Test + @DisplayName("not find one if it's not declared in a concrete class") + void noParameterlessCtorInConcrete() { + assertThrows(IllegalArgumentException.class, () -> + ensureParameterlessCtor(NoParameterlessConstructors.class)); + } + } + + @Nested + @DisplayName("when instantiating objects") + class Objects { + + @Test + @DisplayName("instantiate a class using a parameterless constructor") + void instantiate() { + Cat cat = instantiateWithoutParameters(Cat.class); + assertThat(cat.greet()).contains(MISSING); + } + + @Test + @DisplayName("fail to instantiate an abstract class") + void notInstantiateAbstractClass() { + assertThrows(IllegalStateException.class, () -> instantiateWithoutParameters( + Animal.class)); + } + + @Test + @DisplayName("throw if there was an exception during class instantiation") + void throwIfThrows() { + assertThrows(IllegalStateException.class, + () -> instantiateWithoutParameters(ThrowingConstructor.class)); + } + + @Test + @DisplayName("fail to instantiate a nested class") + void notInstantiateNested() { + assertThrows(IllegalArgumentException.class, + () -> instantiateWithoutParameters(ConstructorsTestEnv.Chicken.class)); + } + + @Test + @DisplayName("instantiate a private class") + void instantiatePrivate() { + ClassWithPrivateCtor instance = instantiateWithoutParameters( + ClassWithPrivateCtor.class); + assertThat(instance.instantiated()).isTrue(); + } + + @Test + @DisplayName("fail to instantiate a class without a parameterless ctor") + void noParameterlessCtor() { + assertThrows(IllegalArgumentException.class, + () -> instantiateWithoutParameters( + NoParameterlessConstructors.class)); + } + + @Nested + @DisplayName("bring the accessibility back") + class Accessibility { + + @Test + @DisplayName("if the instantiation succeeded") + void success() { + Class privateCtorClass = ClassWithPrivateCtor.class; + Constructor ctor = + ensureParameterlessCtor(privateCtorClass); + assertThat(ctor.isAccessible()).isFalse(); + + ClassWithPrivateCtor instance = + instantiateWithoutParameters(privateCtorClass); + assertThat(instance.instantiated()).isTrue(); + + assertThat(ctor.isAccessible()).isFalse(); + } + + @Test + @DisplayName("if the instantiation failed") + void failure() { + Class throwingCtorClass = ThrowingConstructor.class; + Constructor ctor = + ensureParameterlessCtor(throwingCtorClass); + assertThat(ctor.isAccessible()).isFalse(); + + assertThrows(IllegalStateException.class, + () -> instantiateWithoutParameters(throwingCtorClass)); + + assertThat(ctor.isAccessible()).isFalse(); + } + } + } + + private static Method objectToString() { + try { + return Object.class.getDeclaredMethod("toString"); + } catch (NoSuchMethodException e) { + throw new IllegalStateException(e); + } + } +} diff --git a/base/src/test/java/io/spine/reflect/MethodsTest.java b/base/src/test/java/io/spine/reflect/MethodsTest.java deleted file mode 100644 index ed6e60325f..0000000000 --- a/base/src/test/java/io/spine/reflect/MethodsTest.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2020, TeamDev. All rights reserved. - * - * 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.spine.reflect.given.MethodsTestEnv.ClassWithPrivateMethod; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import java.lang.invoke.MethodHandle; -import java.lang.reflect.Method; - -import static com.google.common.truth.Truth.assertThat; -import static io.spine.reflect.given.MethodsTestEnv.ClassWithPrivateMethod.METHOD_RESULT; -import static org.junit.jupiter.api.Assertions.assertThrows; - -@DisplayName("`Methods` utility should") -class MethodsTest { - - private Method privateMethod; - - @BeforeEach - void obtainMethod() throws NoSuchMethodException { - privateMethod = ClassWithPrivateMethod.class.getDeclaredMethod("privateMethod"); - } - - @AfterEach - void resetMethod() { - privateMethod.setAccessible(false); - } - - @Test - @DisplayName("set the method accessible and invoke") - void setAccessibleAndInvoke() { - ClassWithPrivateMethod target = new ClassWithPrivateMethod(); - Object result = Methods.setAccessibleAndInvoke(privateMethod, target); - - assertThat(result).isEqualTo(METHOD_RESULT); - } - - @SuppressWarnings("CheckReturnValue") // Called to throw exception. - @Test - @DisplayName("throw `IAE` if the given target is not a valid invocation target") - void throwOnInvalidTarget() { - Object wrongTarget = new Object(); - - assertThrows(IllegalArgumentException.class, - () -> Methods.setAccessibleAndInvoke(privateMethod, wrongTarget)); - } - - @SuppressWarnings("CheckReturnValue") // Called to throw exception. - @Test - @DisplayName("throw `ISE` if an exception is thrown during invocation") - void throwOnInvocationError() throws NoSuchMethodException { - Method method = ClassWithPrivateMethod.class.getDeclaredMethod("throwingMethod"); - ClassWithPrivateMethod target = new ClassWithPrivateMethod(); - - assertThrows(IllegalStateException.class, - () -> Methods.setAccessibleAndInvoke(method, target)); - } - - @Test - @DisplayName("convert a visible method to a handle") - void convertToHandle() throws Throwable { - Method method = ClassWithPrivateMethod.class.getMethod("publicMethod"); - MethodHandle handle = Methods.asHandle(method); - assertThat(handle).isNotNull(); - - Object invocationResult = handle.bindTo(new ClassWithPrivateMethod()) - .invoke(); - assertThat(invocationResult) - .isEqualTo(METHOD_RESULT); - } - - @Test - @DisplayName("convert an invisible method to a handle") - void convertInvisibleToHandle() throws Throwable { - MethodHandle handle = Methods.asHandle(privateMethod); - assertThat(handle).isNotNull(); - assertThat(privateMethod.isAccessible()).isFalse(); - - Object invocationResult = handle.invoke(new ClassWithPrivateMethod()); - assertThat(invocationResult) - .isEqualTo(METHOD_RESULT); - } - - @Test - @DisplayName("convert an accessible method to a handle") - void convertAccessibleToHandle() throws Throwable { - privateMethod.setAccessible(true); - MethodHandle handle = Methods.asHandle(privateMethod); - assertThat(privateMethod.isAccessible()).isTrue(); - assertThat(handle).isNotNull(); - - Object invocationResult = handle.invoke(new ClassWithPrivateMethod()); - assertThat(invocationResult) - .isEqualTo(METHOD_RESULT); - } -} diff --git a/base/src/test/java/io/spine/reflect/ObjectsTest.java b/base/src/test/java/io/spine/reflect/ObjectsTest.java deleted file mode 100644 index 2e69f5598b..0000000000 --- a/base/src/test/java/io/spine/reflect/ObjectsTest.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright 2020, TeamDev. All rights reserved. - * - * 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.spine.reflect.given.ConstructorsTestEnv.Animal; -import io.spine.reflect.given.ConstructorsTestEnv.Cat; -import io.spine.reflect.given.ConstructorsTestEnv.Chicken; -import io.spine.reflect.given.ConstructorsTestEnv.ClassWithPrivateCtor; -import io.spine.reflect.given.ConstructorsTestEnv.NoParameterlessConstructors; -import io.spine.reflect.given.ConstructorsTestEnv.ThrowingConstructor; -import io.spine.testing.UtilityClassTest; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -import java.lang.reflect.Constructor; - -import static com.google.common.truth.Truth.assertThat; -import static io.spine.reflect.Constructors.ensureParameterlessCtor; -import static io.spine.reflect.Objects.instantiateWithoutParameters; -import static io.spine.reflect.given.ConstructorsTestEnv.Animal.MISSING; -import static org.junit.jupiter.api.Assertions.assertThrows; - -@DisplayName("`Objects` should") -class ObjectsTest extends UtilityClassTest { - - ObjectsTest() { - super(Objects.class); - } - - @Test - @DisplayName("instantiate a class using a parameterless constructor") - void instantiate() { - Cat cat = instantiateWithoutParameters(Cat.class); - assertThat(cat.greet()).contains(MISSING); - } - - @Test - @DisplayName("fail to instantiate an abstract class") - void notInstantiateAbstractClass() { - assertThrows(IllegalStateException.class, () -> instantiateWithoutParameters(Animal.class)); - } - - @Test - @DisplayName("throw if there was an exception during class instantiation") - void throwIfThrows() { - assertThrows(IllegalStateException.class, - () -> instantiateWithoutParameters(ThrowingConstructor.class)); - } - - @Test - @DisplayName("fail to instantiate a nested class") - void notInstantiateNested() { - assertThrows(IllegalArgumentException.class, - () -> instantiateWithoutParameters(Chicken.class)); - } - - @Test - @DisplayName("instantiate a private class") - void instantiatePrivate() { - ClassWithPrivateCtor instance = instantiateWithoutParameters(ClassWithPrivateCtor.class); - assertThat(instance.instantiated()).isTrue(); - } - - @Test - @DisplayName("fail to instantiate a class without a parameterless ctor") - void noParameterlessCtor() { - assertThrows(IllegalArgumentException.class, - () -> instantiateWithoutParameters(NoParameterlessConstructors.class)); - } - - @Nested - @DisplayName("bring the accessibility back") - class Accessibility { - - @Test - @DisplayName("if the instantiation succeeded") - void success() { - Class privateCtorClass = ClassWithPrivateCtor.class; - Constructor ctor = - ensureParameterlessCtor(privateCtorClass); - assertThat(ctor.isAccessible()).isFalse(); - - ClassWithPrivateCtor instance = - instantiateWithoutParameters(privateCtorClass); - assertThat(instance.instantiated()).isTrue(); - - assertThat(ctor.isAccessible()).isFalse(); - } - - @Test - @DisplayName("if the instantiation failed") - void failure() { - Class throwingCtorClass = ThrowingConstructor.class; - Constructor ctor = - ensureParameterlessCtor(throwingCtorClass); - assertThat(ctor.isAccessible()).isFalse(); - - assertThrows(IllegalStateException.class, - () -> instantiateWithoutParameters(throwingCtorClass)); - - assertThat(ctor.isAccessible()).isFalse(); - } - } -} From 18d3fd1f961a44ff9ed544ae36fa16396cbc1dd6 Mon Sep 17 00:00:00 2001 From: "serhii.lekariev" Date: Fri, 12 Jun 2020 12:50:43 +0300 Subject: [PATCH 32/43] Add `@throws` documentation. --- base/src/main/java/io/spine/reflect/Invokables.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/base/src/main/java/io/spine/reflect/Invokables.java b/base/src/main/java/io/spine/reflect/Invokables.java index d403a4cc29..57d2d2c968 100644 --- a/base/src/main/java/io/spine/reflect/Invokables.java +++ b/base/src/main/java/io/spine/reflect/Invokables.java @@ -56,6 +56,8 @@ private Invokables() { * @param type * class to look for constructors in * @return a constructor with no parameters, if it exists + * @throws IllegalArgumentException + * if the specified class does not declare a parameterless constructor */ @CanIgnoreReturnValue public static Constructor ensureParameterlessCtor(Class type) { @@ -129,11 +131,10 @@ public static C instantiateWithoutParameters(Class type) { * Invokes the given argumentless method on the target ignoring the accessibility restrictions. * *

The target must be of the type that declares the given method, otherwise an - * {@link IllegalArgumentException} is thrown. + * {@link IllegalStateException} is thrown. * - * @throws IllegalArgumentException - * if the target is not of the type that declares the given method * @throws IllegalStateException + * if the target is not of the type that declares the given method or * if an exception is thrown during the method invocation */ public static Object setAccessibleAndInvoke(Method method, Object target) { @@ -173,6 +174,9 @@ public static Object setAccessibleAndInvoke(Method method, Object target) { * @param * a type of result of the reflective function * @return a result of the reflective function + * @throws IllegalStateException + * if a {@code ReflectiveOperationException} is thrown + * by {@code fn}, or other error occurs during the reflective operation execution */ @SuppressWarnings("OverlyBroadCatchBlock" /* catching any runtimes does not hurt here. */) private static R invokePreservingAccessibility(T reflectiveObject, From 8c527317dcf104f4937fd182abdcb51d66af7af0 Mon Sep 17 00:00:00 2001 From: "serhii.lekariev" Date: Fri, 12 Jun 2020 16:00:11 +0300 Subject: [PATCH 33/43] Hide a method that looks for a parameterless constructor. Its users only check for it to instantiate it. Clean up, add and tweak documentation. --- .../main/java/io/spine/base/Environment.java | 6 +- .../java/io/spine/reflect/Invokables.java | 6 +- .../java/io/spine/base/given/AwsLambda.java | 2 +- .../java/io/spine/reflect/InvokablesTest.java | 89 +++++-------------- .../reflect/given/ConstructorsTestEnv.java | 4 + .../spine/reflect/given/MethodsTestEnv.java | 4 + 6 files changed, 36 insertions(+), 75 deletions(-) diff --git a/base/src/main/java/io/spine/base/Environment.java b/base/src/main/java/io/spine/base/Environment.java index 286ba6d26c..4a863ee09a 100644 --- a/base/src/main/java/io/spine/base/Environment.java +++ b/base/src/main/java/io/spine/base/Environment.java @@ -28,8 +28,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; import static com.google.common.base.Preconditions.checkNotNull; -import static io.spine.reflect.Invokables.ensureParameterlessCtor; -import static io.spine.reflect.Invokables.instantiateWithoutParameters; +import static io.spine.reflect.Invokables.invokeParameterlessCtor; import static io.spine.util.Exceptions.newIllegalStateException; /** @@ -210,8 +209,7 @@ public Environment register(EnvironmentType environmentType) { @Internal @CanIgnoreReturnValue Environment register(Class type) { - ensureParameterlessCtor(type); - EnvironmentType envTypeInstance = instantiateWithoutParameters(type); + EnvironmentType envTypeInstance = invokeParameterlessCtor(type); return register(envTypeInstance); } diff --git a/base/src/main/java/io/spine/reflect/Invokables.java b/base/src/main/java/io/spine/reflect/Invokables.java index 57d2d2c968..baa56e008e 100644 --- a/base/src/main/java/io/spine/reflect/Invokables.java +++ b/base/src/main/java/io/spine/reflect/Invokables.java @@ -38,7 +38,7 @@ /** * A utility class for working with Java {@linkplain java.lang.reflect.Method methods} and - * {@linkplain java.lang.reflect.Constructor constructors}. + * instantiating objects using reflectively-obtained {@linkplain Constructor constructors}. */ public final class Invokables { @@ -60,7 +60,7 @@ private Invokables() { * if the specified class does not declare a parameterless constructor */ @CanIgnoreReturnValue - public static Constructor ensureParameterlessCtor(Class type) { + private static Constructor ensureParameterlessCtor(Class type) { checkNotNull(type); @SuppressWarnings("unchecked" /* safe, as `Class` only declares `Constructor`. */) Constructor[] ctors = (Constructor[]) type.getDeclaredConstructors(); @@ -113,7 +113,7 @@ public static MethodHandle asHandle(Method method) { * constructors. Note that nested classes fall under this case */ - public static C instantiateWithoutParameters(Class type) { + public static C invokeParameterlessCtor(Class type) { checkNotNull(type); Constructor ctor = ensureParameterlessCtor(type); C result = invokePreservingAccessibility( diff --git a/base/src/test/java/io/spine/base/given/AwsLambda.java b/base/src/test/java/io/spine/base/given/AwsLambda.java index 1f612f12ac..3df42f5af5 100644 --- a/base/src/test/java/io/spine/base/given/AwsLambda.java +++ b/base/src/test/java/io/spine/base/given/AwsLambda.java @@ -23,7 +23,7 @@ /** * AWS Lambda environment is enabled if a specific env variable is set. */ -public class AwsLambda extends VariableControlledEnvironment { +public final class AwsLambda extends VariableControlledEnvironment { public AwsLambda() { super("AWS_LAMBDA_FUNCTION_NAME"); diff --git a/base/src/test/java/io/spine/reflect/InvokablesTest.java b/base/src/test/java/io/spine/reflect/InvokablesTest.java index 955dcd19ee..f722de83c2 100644 --- a/base/src/test/java/io/spine/reflect/InvokablesTest.java +++ b/base/src/test/java/io/spine/reflect/InvokablesTest.java @@ -32,13 +32,11 @@ import java.lang.invoke.MethodHandle; import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import static com.google.common.truth.Truth.assertThat; import static io.spine.reflect.Invokables.asHandle; -import static io.spine.reflect.Invokables.ensureParameterlessCtor; -import static io.spine.reflect.Invokables.instantiateWithoutParameters; +import static io.spine.reflect.Invokables.invokeParameterlessCtor; import static io.spine.reflect.Invokables.setAccessibleAndInvoke; import static io.spine.reflect.given.ConstructorsTestEnv.Animal; import static io.spine.reflect.given.ConstructorsTestEnv.Animal.MISSING; @@ -148,55 +146,6 @@ void convertAccessibleToHandle() throws Throwable { } } - @Nested - @DisplayName("when looking for parameterless constructors") - class ParamaterlessCtors { - - @Test - @DisplayName("find one in a concrete class") - void findParameterless() throws IllegalAccessException, - InvocationTargetException, - InstantiationException { - Constructor constructor = ensureParameterlessCtor( - Cat.class); - Cat cat = constructor.newInstance(); - assertThat(cat.greet()).contains(MISSING); - } - - @Test - @DisplayName("find one in an abstract class") - void findParameterlessInAbstract() { - Constructor constructor = ensureParameterlessCtor( - Animal.class); - assertThrows(InstantiationException.class, constructor::newInstance); - } - - @Test - @DisplayName("not find one in a nested class") - void notFindInNested() { - assertThrows(IllegalArgumentException.class, - () -> ensureParameterlessCtor(ConstructorsTestEnv.Chicken.class)); - } - - @Test - @DisplayName("find a default one") - void defaultCtor() throws IllegalAccessException, - InvocationTargetException, - InstantiationException { - Constructor ctor = - ensureParameterlessCtor(ClassWithDefaultCtor.class); - ClassWithDefaultCtor instance = ctor.newInstance(); - assertThat(instance.instantiated()).isTrue(); - } - - @Test - @DisplayName("not find one if it's not declared in a concrete class") - void noParameterlessCtorInConcrete() { - assertThrows(IllegalArgumentException.class, () -> - ensureParameterlessCtor(NoParameterlessConstructors.class)); - } - } - @Nested @DisplayName("when instantiating objects") class Objects { @@ -204,35 +153,42 @@ class Objects { @Test @DisplayName("instantiate a class using a parameterless constructor") void instantiate() { - Cat cat = instantiateWithoutParameters(Cat.class); + Cat cat = invokeParameterlessCtor(Cat.class); assertThat(cat.greet()).contains(MISSING); } @Test @DisplayName("fail to instantiate an abstract class") void notInstantiateAbstractClass() { - assertThrows(IllegalStateException.class, () -> instantiateWithoutParameters( + assertThrows(IllegalStateException.class, () -> invokeParameterlessCtor( Animal.class)); } + @Test + @DisplayName("instantiate using a default ctor") + void defaultCtor() { + ClassWithDefaultCtor instance = invokeParameterlessCtor(ClassWithDefaultCtor.class); + assertThat(instance.instantiated()).isTrue(); + } + @Test @DisplayName("throw if there was an exception during class instantiation") void throwIfThrows() { assertThrows(IllegalStateException.class, - () -> instantiateWithoutParameters(ThrowingConstructor.class)); + () -> invokeParameterlessCtor(ThrowingConstructor.class)); } @Test @DisplayName("fail to instantiate a nested class") void notInstantiateNested() { assertThrows(IllegalArgumentException.class, - () -> instantiateWithoutParameters(ConstructorsTestEnv.Chicken.class)); + () -> invokeParameterlessCtor(ConstructorsTestEnv.Chicken.class)); } @Test @DisplayName("instantiate a private class") void instantiatePrivate() { - ClassWithPrivateCtor instance = instantiateWithoutParameters( + ClassWithPrivateCtor instance = invokeParameterlessCtor( ClassWithPrivateCtor.class); assertThat(instance.instantiated()).isTrue(); } @@ -241,7 +197,7 @@ void instantiatePrivate() { @DisplayName("fail to instantiate a class without a parameterless ctor") void noParameterlessCtor() { assertThrows(IllegalArgumentException.class, - () -> instantiateWithoutParameters( + () -> invokeParameterlessCtor( NoParameterlessConstructors.class)); } @@ -251,14 +207,13 @@ class Accessibility { @Test @DisplayName("if the instantiation succeeded") - void success() { + void success() throws NoSuchMethodException { Class privateCtorClass = ClassWithPrivateCtor.class; - Constructor ctor = - ensureParameterlessCtor(privateCtorClass); - assertThat(ctor.isAccessible()).isFalse(); + Constructor ctor = + ClassWithPrivateCtor.class.getDeclaredConstructor(); ClassWithPrivateCtor instance = - instantiateWithoutParameters(privateCtorClass); + invokeParameterlessCtor(privateCtorClass); assertThat(instance.instantiated()).isTrue(); assertThat(ctor.isAccessible()).isFalse(); @@ -266,14 +221,14 @@ void success() { @Test @DisplayName("if the instantiation failed") - void failure() { + void failure() throws NoSuchMethodException { Class throwingCtorClass = ThrowingConstructor.class; + Constructor ctor = - ensureParameterlessCtor(throwingCtorClass); - assertThat(ctor.isAccessible()).isFalse(); + ThrowingConstructor.class.getDeclaredConstructor(); assertThrows(IllegalStateException.class, - () -> instantiateWithoutParameters(throwingCtorClass)); + () -> invokeParameterlessCtor(throwingCtorClass)); assertThat(ctor.isAccessible()).isFalse(); } diff --git a/base/src/test/java/io/spine/reflect/given/ConstructorsTestEnv.java b/base/src/test/java/io/spine/reflect/given/ConstructorsTestEnv.java index a887ad0df4..4d300fd6ff 100644 --- a/base/src/test/java/io/spine/reflect/given/ConstructorsTestEnv.java +++ b/base/src/test/java/io/spine/reflect/given/ConstructorsTestEnv.java @@ -22,6 +22,10 @@ import static java.lang.String.format; +/** + * This is an environment for {@linkplain io.spine.reflect.InvokablesTest testing + * constructor-related} utilities. + */ @SuppressWarnings("unused" /* need unused members for reflection lookup. */) public final class ConstructorsTestEnv { diff --git a/base/src/test/java/io/spine/reflect/given/MethodsTestEnv.java b/base/src/test/java/io/spine/reflect/given/MethodsTestEnv.java index 7c50539599..fc6ad739c3 100644 --- a/base/src/test/java/io/spine/reflect/given/MethodsTestEnv.java +++ b/base/src/test/java/io/spine/reflect/given/MethodsTestEnv.java @@ -20,6 +20,10 @@ package io.spine.reflect.given; +/** + * This is an environment for {@linkplain io.spine.reflect.InvokablesTest testing methods-related} + * utilities. + */ public final class MethodsTestEnv { /** Prevents instantiation of this test env class. */ From e1a10186c4f49335cb22ff3862d180168c015db3 Mon Sep 17 00:00:00 2001 From: "serhii.lekariev" Date: Fri, 12 Jun 2020 16:01:26 +0300 Subject: [PATCH 34/43] Shorten a method name. --- .../main/java/io/spine/base/Environment.java | 4 ++-- .../java/io/spine/reflect/Invokables.java | 2 +- .../java/io/spine/reflect/InvokablesTest.java | 20 +++++++++---------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/base/src/main/java/io/spine/base/Environment.java b/base/src/main/java/io/spine/base/Environment.java index 4a863ee09a..44cb494c10 100644 --- a/base/src/main/java/io/spine/base/Environment.java +++ b/base/src/main/java/io/spine/base/Environment.java @@ -28,7 +28,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; import static com.google.common.base.Preconditions.checkNotNull; -import static io.spine.reflect.Invokables.invokeParameterlessCtor; +import static io.spine.reflect.Invokables.callParameterlessCtor; import static io.spine.util.Exceptions.newIllegalStateException; /** @@ -209,7 +209,7 @@ public Environment register(EnvironmentType environmentType) { @Internal @CanIgnoreReturnValue Environment register(Class type) { - EnvironmentType envTypeInstance = invokeParameterlessCtor(type); + EnvironmentType envTypeInstance = callParameterlessCtor(type); return register(envTypeInstance); } diff --git a/base/src/main/java/io/spine/reflect/Invokables.java b/base/src/main/java/io/spine/reflect/Invokables.java index baa56e008e..6ab93ef873 100644 --- a/base/src/main/java/io/spine/reflect/Invokables.java +++ b/base/src/main/java/io/spine/reflect/Invokables.java @@ -113,7 +113,7 @@ public static MethodHandle asHandle(Method method) { * constructors. Note that nested classes fall under this case */ - public static C invokeParameterlessCtor(Class type) { + public static C callParameterlessCtor(Class type) { checkNotNull(type); Constructor ctor = ensureParameterlessCtor(type); C result = invokePreservingAccessibility( diff --git a/base/src/test/java/io/spine/reflect/InvokablesTest.java b/base/src/test/java/io/spine/reflect/InvokablesTest.java index f722de83c2..030c7eec4b 100644 --- a/base/src/test/java/io/spine/reflect/InvokablesTest.java +++ b/base/src/test/java/io/spine/reflect/InvokablesTest.java @@ -36,7 +36,7 @@ import static com.google.common.truth.Truth.assertThat; import static io.spine.reflect.Invokables.asHandle; -import static io.spine.reflect.Invokables.invokeParameterlessCtor; +import static io.spine.reflect.Invokables.callParameterlessCtor; import static io.spine.reflect.Invokables.setAccessibleAndInvoke; import static io.spine.reflect.given.ConstructorsTestEnv.Animal; import static io.spine.reflect.given.ConstructorsTestEnv.Animal.MISSING; @@ -153,21 +153,21 @@ class Objects { @Test @DisplayName("instantiate a class using a parameterless constructor") void instantiate() { - Cat cat = invokeParameterlessCtor(Cat.class); + Cat cat = callParameterlessCtor(Cat.class); assertThat(cat.greet()).contains(MISSING); } @Test @DisplayName("fail to instantiate an abstract class") void notInstantiateAbstractClass() { - assertThrows(IllegalStateException.class, () -> invokeParameterlessCtor( + assertThrows(IllegalStateException.class, () -> callParameterlessCtor( Animal.class)); } @Test @DisplayName("instantiate using a default ctor") void defaultCtor() { - ClassWithDefaultCtor instance = invokeParameterlessCtor(ClassWithDefaultCtor.class); + ClassWithDefaultCtor instance = callParameterlessCtor(ClassWithDefaultCtor.class); assertThat(instance.instantiated()).isTrue(); } @@ -175,20 +175,20 @@ void defaultCtor() { @DisplayName("throw if there was an exception during class instantiation") void throwIfThrows() { assertThrows(IllegalStateException.class, - () -> invokeParameterlessCtor(ThrowingConstructor.class)); + () -> callParameterlessCtor(ThrowingConstructor.class)); } @Test @DisplayName("fail to instantiate a nested class") void notInstantiateNested() { assertThrows(IllegalArgumentException.class, - () -> invokeParameterlessCtor(ConstructorsTestEnv.Chicken.class)); + () -> callParameterlessCtor(ConstructorsTestEnv.Chicken.class)); } @Test @DisplayName("instantiate a private class") void instantiatePrivate() { - ClassWithPrivateCtor instance = invokeParameterlessCtor( + ClassWithPrivateCtor instance = callParameterlessCtor( ClassWithPrivateCtor.class); assertThat(instance.instantiated()).isTrue(); } @@ -197,7 +197,7 @@ void instantiatePrivate() { @DisplayName("fail to instantiate a class without a parameterless ctor") void noParameterlessCtor() { assertThrows(IllegalArgumentException.class, - () -> invokeParameterlessCtor( + () -> callParameterlessCtor( NoParameterlessConstructors.class)); } @@ -213,7 +213,7 @@ void success() throws NoSuchMethodException { Constructor ctor = ClassWithPrivateCtor.class.getDeclaredConstructor(); ClassWithPrivateCtor instance = - invokeParameterlessCtor(privateCtorClass); + callParameterlessCtor(privateCtorClass); assertThat(instance.instantiated()).isTrue(); assertThat(ctor.isAccessible()).isFalse(); @@ -228,7 +228,7 @@ void failure() throws NoSuchMethodException { ThrowingConstructor.class.getDeclaredConstructor(); assertThrows(IllegalStateException.class, - () -> invokeParameterlessCtor(throwingCtorClass)); + () -> callParameterlessCtor(throwingCtorClass)); assertThat(ctor.isAccessible()).isFalse(); } From 9013ab82e6019a16d00dfc2119a7288d4772b5f9 Mon Sep 17 00:00:00 2001 From: "serhii.lekariev" Date: Fri, 12 Jun 2020 20:17:59 +0300 Subject: [PATCH 35/43] Fix Javadoc mistakes. --- base/src/main/java/io/spine/base/Environment.java | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/base/src/main/java/io/spine/base/Environment.java b/base/src/main/java/io/spine/base/Environment.java index 44cb494c10..70fb3149cc 100644 --- a/base/src/main/java/io/spine/base/Environment.java +++ b/base/src/main/java/io/spine/base/Environment.java @@ -108,12 +108,13 @@ * * *

Caching

+ * *

{@code Environment} caches the {@code EnvironmentType} once its calculated. * This means that if one environment type has been found to be active, its instance is saved. * If later it becomes logically inactive, e.g. the environment variable that's used to check the - * environment type changes, {@code Environment} is still going to return the cached value, unless - * it is {@linkplain #setTo(EnvironmentType) set manually} or {@linkplain #reset() reset - * explicitly}. + * environment type changes, {@code Environment} is still going to return the cached value. To + * overwrite the value use {@link #setTo(EnvironmentType)}. Also, the value may be + * {@linkplain ... reset}. * * For example: *

@@ -134,9 +135,6 @@
  *     assertThat(environment.is(AwsLambda.class)).isFalse();
  * 
* - *

{@linkplain #setTo(EnvironmentType) explicitly setting} the environment type overrides - * the cached value. - * *

When registering custom types, please ensure their mutual exclusivity. * If two or more environment types {@linkplain EnvironmentType#enabled() consider themselves * enabled} at the same time, the behaviour of {@link #is(Class)}} is undefined. @@ -197,10 +195,6 @@ public Environment register(EnvironmentType environmentType) { * determine whether it's enabled} later. * *

The specified {@code type} must have a parameterless constructor. - * {@linkplain EnvironmentType#enabled() activity} of the specified environment type is going - * to be checked against an instance created by invoking the parameterless constructor. - * - *

Otherwise, behaves like {@link #register(EnvironmentType)}. * * @param type * environment type to register From 0da0c9334608a652302d6b285edea916c047dc83 Mon Sep 17 00:00:00 2001 From: "serhii.lekariev" Date: Fri, 12 Jun 2020 20:35:04 +0300 Subject: [PATCH 36/43] Fix Javadoc errors. --- base/src/main/java/io/spine/base/Environment.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/base/src/main/java/io/spine/base/Environment.java b/base/src/main/java/io/spine/base/Environment.java index 70fb3149cc..1ae31cb447 100644 --- a/base/src/main/java/io/spine/base/Environment.java +++ b/base/src/main/java/io/spine/base/Environment.java @@ -194,7 +194,8 @@ public Environment register(EnvironmentType environmentType) { * Remembers the specified environment type, allowing {@linkplain #is(Class) to * determine whether it's enabled} later. * - *

The specified {@code type} must have a parameterless constructor. + *

The specified {@code type} must have a parameterless constructor. The + * {@code EnvironmentType} is going to be instantiated using the parameterless constructor. * * @param type * environment type to register From f67ff1b25c024295f0db9e338537da81d9628fb1 Mon Sep 17 00:00:00 2001 From: "serhii.lekariev" Date: Fri, 12 Jun 2020 20:38:10 +0300 Subject: [PATCH 37/43] Rearrange utitlity methods, fix wording mistakes. --- .../main/java/io/spine/base/Environment.java | 2 +- .../java/io/spine/reflect/Invokables.java | 52 +++++++++---------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/base/src/main/java/io/spine/base/Environment.java b/base/src/main/java/io/spine/base/Environment.java index 1ae31cb447..415e45a750 100644 --- a/base/src/main/java/io/spine/base/Environment.java +++ b/base/src/main/java/io/spine/base/Environment.java @@ -225,7 +225,7 @@ public Environment createCopy() { /** * Determines whether the current environment is the same as the specified one. * - *

If {@linkplain #register(EnvironmentType) custom env types have been defined}, + *

If {@linkplain #register(EnvironmentType) custom env types have been registered}, * goes through them in the latest-registered to earliest-registered order. * Then, checks {@link Tests} and {@link Production}. * diff --git a/base/src/main/java/io/spine/reflect/Invokables.java b/base/src/main/java/io/spine/reflect/Invokables.java index 6ab93ef873..3d0e658363 100644 --- a/base/src/main/java/io/spine/reflect/Invokables.java +++ b/base/src/main/java/io/spine/reflect/Invokables.java @@ -48,32 +48,6 @@ public final class Invokables { private Invokables() { } - /** - * Tries to find a constructor with no arguments in the specified class or its parents. - * - *

If no such constructor has been found, throws an {@code IllegalArgumentException}. - * - * @param type - * class to look for constructors in - * @return a constructor with no parameters, if it exists - * @throws IllegalArgumentException - * if the specified class does not declare a parameterless constructor - */ - @CanIgnoreReturnValue - private static Constructor ensureParameterlessCtor(Class type) { - checkNotNull(type); - @SuppressWarnings("unchecked" /* safe, as `Class` only declares `Constructor`. */) - Constructor[] ctors = (Constructor[]) type.getDeclaredConstructors(); - for (Constructor ctor : ctors) { - if (ctor.getParameterCount() == 0) { - return ctor; - } - } - - throw newIllegalArgumentException("No parameterless ctor found in class `%s`.", - type.getSimpleName()); - } - /** * Converts the given {@link Method} into a {@link MethodHandle}. * @@ -153,6 +127,32 @@ public static Object setAccessibleAndInvoke(Method method, Object target) { return result; } + /** + * Tries to find a constructor with no arguments in the specified class or its parents. + * + *

If no such constructor has been found, throws an {@code IllegalArgumentException}. + * + * @param type + * class to look for constructors in + * @return a constructor with no parameters, if it exists + * @throws IllegalArgumentException + * if the specified class does not declare a parameterless constructor + */ + @CanIgnoreReturnValue + private static Constructor ensureParameterlessCtor(Class type) { + checkNotNull(type); + @SuppressWarnings("unchecked" /* safe, as `Class` only declares `Constructor`. */) + Constructor[] ctors = (Constructor[]) type.getDeclaredConstructors(); + for (Constructor ctor : ctors) { + if (ctor.getParameterCount() == 0) { + return ctor; + } + } + + throw newIllegalArgumentException("No parameterless ctor found in class `%s`.", + type.getSimpleName()); + } + /** * Performs a reflective operation regardless of its accessibility, returns its result. * From 2558a8206a11a9d405b583437142003353167043 Mon Sep 17 00:00:00 2001 From: "serhii.lekariev" Date: Sat, 13 Jun 2020 01:04:36 +0300 Subject: [PATCH 38/43] Fix a wrong link. --- base/src/main/java/io/spine/base/Environment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/src/main/java/io/spine/base/Environment.java b/base/src/main/java/io/spine/base/Environment.java index 415e45a750..b31b6d0590 100644 --- a/base/src/main/java/io/spine/base/Environment.java +++ b/base/src/main/java/io/spine/base/Environment.java @@ -114,7 +114,7 @@ * If later it becomes logically inactive, e.g. the environment variable that's used to check the * environment type changes, {@code Environment} is still going to return the cached value. To * overwrite the value use {@link #setTo(EnvironmentType)}. Also, the value may be - * {@linkplain ... reset}. + * {@link #reset}. * * For example: *


From e220c604b0a550f81d1d014cfe000bd6493a5241 Mon Sep 17 00:00:00 2001
From: "serhii.lekariev" 
Date: Sat, 13 Jun 2020 15:32:08 +0300
Subject: [PATCH 39/43] Add a Javadoc paragraph for a `@Test` method.

---
 base/src/test/java/io/spine/base/EnvironmentTest.java | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

diff --git a/base/src/test/java/io/spine/base/EnvironmentTest.java b/base/src/test/java/io/spine/base/EnvironmentTest.java
index 470c36d22c..7894d38831 100644
--- a/base/src/test/java/io/spine/base/EnvironmentTest.java
+++ b/base/src/test/java/io/spine/base/EnvironmentTest.java
@@ -266,18 +266,17 @@ void cacheCustom() {
         assertThat(environment.is(Staging.class)).isTrue();
     }
 
+    /**
+     * This test checks whether {@code Tests} type is preserved and is visible from a different
+     * thread. This is a common case for tests that involve a multithreading environment or
+     * test client-server communication.
+     */
     @Test
     @DisplayName("cache the `Tests` environment type")
     void cacheTests() throws InterruptedException {
         AtomicBoolean envCached = new AtomicBoolean(false);
         assertThat(environment.is(Tests.class));
         Thread thread = new Thread(() -> {
-            /*
-             * Here the stack trace does not contain mentions of testing frameworks, because
-             * we check from a new thread.
-             *
-             * We also explicitly clear the variable.
-             */
             Tests.clearTestingEnvVariable();
             assertThat(environment.is(Tests.class)).isTrue();
             assertThat(new Tests().enabled()).isFalse();

From 6b4f302e2bea5f83f71635d3bd9dd93fe8f71a0d Mon Sep 17 00:00:00 2001
From: "serhii.lekariev" 
Date: Sat, 13 Jun 2020 16:10:05 +0300
Subject: [PATCH 40/43] `register` an env on `setTo`.

---
 base/src/main/java/io/spine/base/Environment.java | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/base/src/main/java/io/spine/base/Environment.java b/base/src/main/java/io/spine/base/Environment.java
index b31b6d0590..4dbe564c97 100644
--- a/base/src/main/java/io/spine/base/Environment.java
+++ b/base/src/main/java/io/spine/base/Environment.java
@@ -114,7 +114,7 @@
  * If later it becomes logically inactive, e.g. the environment variable that's used to check the
  * environment type changes, {@code Environment} is still going to return the cached value. To
  * overwrite the value use {@link #setTo(EnvironmentType)}. Also, the value may be
- * {@link #reset}.
+ * {@linkplain #reset}.
  *
  * For example:
  * 
@@ -302,7 +302,9 @@ public void restoreFrom(Environment copy) {
      */
     @VisibleForTesting
     public void setTo(EnvironmentType type) {
-        this.currentEnvType = checkNotNull(type).getClass();
+        checkNotNull(type);
+        register(type);
+        this.currentEnvType = type.getClass();
     }
 
     /**
@@ -312,6 +314,7 @@ public void setTo(EnvironmentType type) {
     @VisibleForTesting
     public void setTo(Class type) {
         checkNotNull(type);
+        register(type);
         this.currentEnvType = type;
     }
 

From 858bd7d77be4e23971ed81f72eb039b7f954915e Mon Sep 17 00:00:00 2001
From: "serhii.lekariev" 
Date: Sat, 13 Jun 2020 16:21:53 +0300
Subject: [PATCH 41/43] Prefer `register(Class)` over
 `register(EnvironmentType)` in tests.

---
 .../test/java/io/spine/base/EnvironmentTest.java  | 15 +++++++--------
 1 file changed, 7 insertions(+), 8 deletions(-)

diff --git a/base/src/test/java/io/spine/base/EnvironmentTest.java b/base/src/test/java/io/spine/base/EnvironmentTest.java
index 7894d38831..22c7e55db3 100644
--- a/base/src/test/java/io/spine/base/EnvironmentTest.java
+++ b/base/src/test/java/io/spine/base/EnvironmentTest.java
@@ -87,9 +87,8 @@ void cleanUp() {
     @Test
     @DisplayName("tell that we are under tests if env. variable set to true")
     void environmentVarTrue() {
-        Tests tests = new Tests();
         Environment.instance()
-                   .setTo(tests);
+                   .setTo(Tests.class);
 
         assertThat(environment.is(Tests.class)).isTrue();
     }
@@ -147,7 +146,7 @@ void inProductionUsingDeprecatedMethod() {
     @Test
     @DisplayName("turn tests mode on")
     void turnTestsOn() {
-        environment.setTo(new Tests());
+        environment.setTo(Tests.class);
 
         assertThat(environment.is(Tests.class)).isTrue();
     }
@@ -155,7 +154,7 @@ void turnTestsOn() {
     @Test
     @DisplayName("turn production mode on")
     void turnProductionOn() {
-        environment.setTo(new Production());
+        environment.setTo(Production.class);
 
         assertThat(environment.is(Production.class)).isTrue();
     }
@@ -196,8 +195,8 @@ class CustomEnvTypes {
         @Test
         @DisplayName("allow to provide user defined environment types")
         void provideCustomTypes() {
-            environment.register(new Staging())
-                       .register(new Local());
+            environment.register(Staging.class)
+                       .register(Local.class);
 
             // Now that `Environment` knows about `LOCAL`, it should use it as fallback.
             assertThat(environment.is(Local.class)).isTrue();
@@ -207,7 +206,7 @@ void provideCustomTypes() {
         @DisplayName("fallback to the `TESTS` environment")
         void fallBack() {
             Environment.instance()
-                       .register(new Travis());
+                       .register(Travis.class);
             assertThat(environment.is(Travis.class)).isFalse();
             assertThat(environment.is(Tests.class)).isTrue();
         }
@@ -216,7 +215,7 @@ void fallBack() {
     @Test
     @DisplayName("follow assignment-compatibility when determining the type")
     void polymorphicEnv() {
-        environment.register(new AppEngineStandard());
+        environment.register(AppEngineStandard.class);
 
         AppEngineStandard.enable();
         assertThat(environment.is(AppEngine.class)).isTrue();

From 459a670b8ec34b2ad4a7f99d219264e35678c9ac Mon Sep 17 00:00:00 2001
From: "serhii.lekariev" 
Date: Sat, 13 Jun 2020 16:35:42 +0300
Subject: [PATCH 42/43] Mention the fact that environment types are registered
 automatically.

---
 base/src/main/java/io/spine/base/Environment.java | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/base/src/main/java/io/spine/base/Environment.java b/base/src/main/java/io/spine/base/Environment.java
index 4dbe564c97..5ea3d0e943 100644
--- a/base/src/main/java/io/spine/base/Environment.java
+++ b/base/src/main/java/io/spine/base/Environment.java
@@ -299,6 +299,8 @@ public void restoreFrom(Environment copy) {
 
     /**
      * Sets the current environment type to {@code type.getClass()}. Overrides the current value.
+     *
+     * 

Calls {@link #register(EnvironmentType)} internally. */ @VisibleForTesting public void setTo(EnvironmentType type) { @@ -309,6 +311,8 @@ public void setTo(EnvironmentType type) { /** * Sets the current environment type to the specified one. Overrides the current value. + * + *

Calls {@link #register(Class)} internally. */ @Internal @VisibleForTesting From 36868c6070ee17c9101e48e937191ba619806936 Mon Sep 17 00:00:00 2001 From: "serhii.lekariev" Date: Sat, 13 Jun 2020 18:00:56 +0300 Subject: [PATCH 43/43] Tweak Javadoc wording. --- base/src/main/java/io/spine/base/Environment.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/base/src/main/java/io/spine/base/Environment.java b/base/src/main/java/io/spine/base/Environment.java index 5ea3d0e943..c3d829dbd6 100644 --- a/base/src/main/java/io/spine/base/Environment.java +++ b/base/src/main/java/io/spine/base/Environment.java @@ -137,7 +137,7 @@ * *

When registering custom types, please ensure their mutual exclusivity. * If two or more environment types {@linkplain EnvironmentType#enabled() consider themselves - * enabled} at the same time, the behaviour of {@link #is(Class)}} is undefined. + * enabled} at the same time, the behaviour of {@link #is(Class)} is undefined. * * @see EnvironmentType * @see Tests @@ -300,7 +300,8 @@ public void restoreFrom(Environment copy) { /** * Sets the current environment type to {@code type.getClass()}. Overrides the current value. * - *

Calls {@link #register(EnvironmentType)} internally. + * If the supplied type was not {@linkplain #register(EnvironmentType) registered} previously, + * it is registered. */ @VisibleForTesting public void setTo(EnvironmentType type) { @@ -312,7 +313,8 @@ public void setTo(EnvironmentType type) { /** * Sets the current environment type to the specified one. Overrides the current value. * - *

Calls {@link #register(Class)} internally. + * If the supplied type was not {@linkplain #register(EnvironmentType) registered} previously, + * it is registered. */ @Internal @VisibleForTesting