Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 58 additions & 46 deletions base/src/main/java/io/spine/base/Environment.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,16 @@
import org.checkerframework.checker.nullness.qual.Nullable;

import static com.google.common.base.Preconditions.checkNotNull;
import static io.spine.util.Exceptions.newIllegalStateException;

/**
* Provides information about the environment (current platform used, etc.).
*
* <h1>Environment Type Detection</h1>
*
* <p>Current implementation allows to {@linkplain #is(EnvironmentType) check} whether a given
* environment is currently the active one and {@linkplain #type() get an instance of the current
* environment type}. Two environment types exist out of the box:
* <p>Current implementation allows to {@linkplain #is(Class) check} the type of the current
* environment, or {@linkplain #type() get the instance of the current environment}.
* Two environment types exist out of the box:
*
* <ul>
* <li><em>{@link Tests}</em> is detected if the current call stack has a reference to the unit
Expand All @@ -55,7 +56,7 @@
*
* private Application() {
* Environment environment = Environment.instance();
* if(environment.is(Tests.type())) {
* if(environment.is(Tests.class)) {
* // Do not send out emails if in tests.
* this.sender = new MockEmailSender();
* } else {
Expand All @@ -78,19 +79,19 @@
*
* static {
* Environment.instance()
* .register(StagingEnvironmentType.type())
* .register(LoadTestingType.type());
* .register(new Staging())
* .register(new LoadTesting());
* }
*
* private final ConnectionPool pool;
*
* private Application() {
* Environment environment = Environment.instance();
* if (environment.is(Tests.type()) {
* if (environment.is(Tests.class) {
* // Single connection is enough for tests.
* this.pool = new ConnectionPoolImpl(PoolCapacity.of(1));
* } else {
* if(environment.is(LoadTesting.type()) {
* if(environment.is(LoadTesting.class) {
* this.pool =
* new ConnectionPoolImpl(PoolCapacity.fromConfig("load_tests.yml"));
* } else {
Expand All @@ -105,7 +106,7 @@
*
* <p><b>When registering custom types, please ensure</b> their mutual exclusivity.
* If two or more environment types {@linkplain EnvironmentType#enabled() consider themselves
* enabled} at the same time, the behaviour of {@link #is(EnvironmentType)}} is undefined.
* enabled} at the same time, the behaviour of {@link #is(Class)}} is undefined.
*
* @see EnvironmentType
* @see Tests
Expand All @@ -115,7 +116,7 @@
public final class Environment {

private static final ImmutableList<EnvironmentType> BASE_TYPES =
ImmutableList.of(Tests.type(), Production.type());
ImmutableList.of(new Tests(), new Production());

private static final Environment INSTANCE = new Environment();

Expand All @@ -133,7 +134,7 @@ private Environment(Environment copy) {
}

/**
* Remembers the specified environment type, allowing {@linkplain #is(EnvironmentType) to
* Remembers the specified environment type, allowing {@linkplain #is(Class) to
* determine whether it's enabled} later.
*
* <p>Note that the default types are still present.
Expand Down Expand Up @@ -179,52 +180,46 @@ public Environment createCopy() {
* goes through them in the latest-registered to earliest-registered order.
* Then, checks {@link Tests} and {@link Production}.
*
* @return the current environment type.
*/
@SuppressWarnings("ConstantConditions"/* no NPE is ensured by the `ensureTypeIsSet` call. */)
public boolean is(EnvironmentType type) {
ensureTypeIsSet();
return currentEnvType.equals(type);
}

/**
* Returns the current environment type.
* <p>Please note that {@code is} follows assigment-compatibility:
* <pre>
* abstract class AppEngine extends EnvironmentType {
* ...
* }
*
* <p>If {@linkplain #register(EnvironmentType) custom env types have been defined},
* goes through them in the latest-registered to earliest-registered order.
* Then, checks {@link Tests} and {@link Production}.
* final class AppEngineStandard extends AppEngine {
* ...
* }
*
* @return the current environment type
* Environment environment = Environment.instance();
*
* // Assuming we are under App Engine Standard
* assertThat(environment.is(AppEngine.class)).isTrue();
*
* </pre>
*
* @return whether the current environment type matches the specified one
*/
public EnvironmentType type() {
ensureTypeIsSet();
return currentEnvType;
}

private void ensureTypeIsSet() {
if (currentEnvType == null) {
determineCurrentType();
}
public boolean is(Class<? extends EnvironmentType> type) {
EnvironmentType currentEnv = cachedOrCalculated();
boolean result = type.isInstance(currentEnv);
return result;
}

private void determineCurrentType() {
for (EnvironmentType type : knownEnvTypes) {
if (type.enabled()) {
this.currentEnvType = type;
return;
}
}
/** Returns the instance of the current environment. */
public EnvironmentType type() {
EnvironmentType currentEnv = cachedOrCalculated();
return currentEnv;
}

/**
* Verifies if the code currently runs under a unit testing framework.
*
* @see Tests
* @deprecated use {@code Environment.instance().is(Tests.type)}
* @deprecated use {@code Environment.instance().is(Tests.class)}
*/
@Deprecated
public boolean isTests() {
return is(Tests.type());
return is(Tests.class);
}

/**
Expand All @@ -234,7 +229,7 @@ public boolean isTests() {
*
* @return {@code true} if the code runs in the production mode, {@code false} otherwise
* @see Production
* @deprecated use {@code Environment.instance().is(Production.type())}
* @deprecated use {@code Environment.instance().is(Production.class)}
*/
@Deprecated
public boolean isProduction() {
Expand Down Expand Up @@ -271,7 +266,7 @@ public void setTo(EnvironmentType type) {
@Deprecated
@VisibleForTesting
public void setToTests() {
this.currentEnvType = Tests.type();
this.currentEnvType = new Tests();
Tests.enable();
}

Expand All @@ -285,7 +280,7 @@ public void setToTests() {
@Deprecated
@VisibleForTesting
public void setToProduction() {
this.currentEnvType = Production.type();
this.currentEnvType = new Production();
Tests.clearTestingEnvVariable();
}

Expand All @@ -298,4 +293,21 @@ public void reset() {
this.knownEnvTypes = BASE_TYPES;
Tests.clearTestingEnvVariable();
}

private EnvironmentType cachedOrCalculated() {
EnvironmentType result = currentEnvType != null
? currentEnvType
: currentType();
return result;
}

private EnvironmentType currentType() {
for (EnvironmentType type : knownEnvTypes) {
if (type.enabled()) {
return type;
}
}

throw newIllegalStateException("`Environment` could not find an active environment type.");
}
}
29 changes: 1 addition & 28 deletions base/src/main/java/io/spine/base/EnvironmentType.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,12 @@

package io.spine.base;

import com.google.common.base.Objects;

/**
* A type of environment.
*
* <p>Some examples may be {@code STAGING} or {@code LOCAL} environments.
*
* @implNote developers are encouraged to make their environment types singletons, such
* that their API is consistent with the env types provided by the {@code base} library:
* {@link Production}, {@link Tests}.
* @implNote Not an {@code interface} to limit the access level of {@link #enabled()}
*/
public abstract class EnvironmentType {

Expand All @@ -41,27 +37,4 @@ public abstract class EnvironmentType {
* knowledge to determine the current environment.
*/
protected abstract boolean enabled();

/**
* @inheritDoc
*
* <p>By default, environments types are compared based on their classes.
*/
@Override
public boolean equals(Object obj) {
return this.getClass()
.equals(obj.getClass());
}

/**
* @inheritDoc
*
* <p>By default, adheres to the {@code equals} and {@code hashCode} contract, assuming that
* the implementation of the {@code equals} is the {@linkplain EnvironmentType#equals(Object)
* default one}.
*/
@Override
public int hashCode() {
return Objects.hashCode(getClass());
}
}
33 changes: 9 additions & 24 deletions base/src/main/java/io/spine/base/Production.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,40 +20,25 @@

package io.spine.base;

import com.google.errorprone.annotations.Immutable;

/**
* A non-testing environment.
*
* <p>If the system is not in the {@link Tests} environment, it is in the production environment.
*/
@Immutable
public final class Production extends EnvironmentType {

@Override
protected boolean enabled() {
return !Tests.type()
.enabled();
}

/**
* Returns the singleton instance.
* Creates a new instance.
*
* <p>All {@code Production} instances are immutable and equivalent.
*/
public static Production type() {
return Singleton.INSTANCE.production;
public Production() {
super();
}

private enum Singleton {

INSTANCE;

@SuppressWarnings({
"NonSerializableFieldInSerializableClass",
"PMD.SingularField" /* this field cannot be local */})
private final Production production;

Singleton() {
this.production = new Production();
}
@Override
protected boolean enabled() {
boolean tests = new Tests().enabled();
return !tests;
}
}
36 changes: 11 additions & 25 deletions base/src/main/java/io/spine/base/Tests.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
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;

Expand All @@ -34,7 +33,6 @@
*
* <p>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 {

Expand All @@ -53,14 +51,23 @@ public final class Tests extends EnvironmentType {

private static final Pattern TEST_PROP_PATTERN = Pattern.compile("\"' ");

/**
* Creates a new instance.
*
* <p>All {@code Tests} instances are immutable and equivalent.
*/
public Tests() {
super();
}

/**
* Verifies if the code currently runs under a unit testing framework.
*
* <p>The method returns {@code true} if the following packages are discovered
* in the stacktrace:
* <ul>
* <li>{@code org.junit}
* <li>{@code org.testng}
* <li>{@code org.junit}
* <li>{@code org.testng}
* </ul>
*
* @return {@code true} if the code runs under a testing framework, {@code false} otherwise
Expand Down Expand Up @@ -99,25 +106,4 @@ static void clearTestingEnvVariable() {
static void enable() {
System.setProperty(ENV_KEY_TESTS, String.valueOf(true));
}

/**
* Returns the singleton instance of this class.
*/
public static Tests type() {
return Singleton.INSTANCE.tests;
}

private enum Singleton {

INSTANCE;

@SuppressWarnings({
"NonSerializableFieldInSerializableClass",
"PMD.SingularField" /* this field cannot be local */})
private final Tests tests;

Singleton() {
this.tests = new Tests();
}
}
}
Loading