Commit 4915e756 authored by Rajiv Prabhakar's avatar Rajiv Prabhakar
Browse files

v2.0.4 release: DynamicConstant class added

parent 5e0d4694
Pipeline #25360890 passed with stage
in 1 minute and 47 seconds
......@@ -117,7 +117,7 @@ Add the following to your pom.xml file, and you should be good to go. Check [her
<dependency>
<groupId>com.rajivprab</groupId>
<artifactId>cava</artifactId>
<version>2.0.1</version>
<version>2.0.4</version>
</dependency>
</dependencies>
```
......
......@@ -6,7 +6,7 @@
<groupId>com.rajivprab</groupId>
<artifactId>cava</artifactId>
<version>2.0.3</version>
<version>2.0.4</version>
<name>Cava: Clean Java</name>
<description>A library that enables users to write minimal, clean and simple Java</description>
......
package org.rajivprab.cava;
import org.apache.commons.lang3.Validate;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
/**
* Constant: all calls to get() are guaranteed to return the same value.
*
* Dynamic: The default value is lazily constructed/retrieved during the first get() call,
* and only if no other value was previously set.
*
* Before the first get() call, the data can be set() to any arbitrary value.
* It is the caller's responsibility to ensure that any calls to set() match any previous set() or get() calls made.
* Else, an exception will be thrown, and the newly set value will be ignored.
*
* This class does not support setting/getting null values.
* It's ok for no default to be given, or for the default-supplier to return a null,
* iff a non-null value is set(), prior to any get() calls.
*
* This class is intended to be used in cases where the set() call might be needed.
* Otherwise, just use the lazy-singleton pattern: https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom
*
* This class is thread-safe.
*/
public class DynamicConstant<T> {
private final AtomicReference<T> value = new AtomicReference<>();
private final Supplier<T> defaultSupplier;
public static <T> DynamicConstant<T> noDefault() {
return withDefault(null);
}
public static <T> DynamicConstant<T> withDefault(T def) {
return new DynamicConstant<>(() -> def);
}
public static <T> DynamicConstant<T> lazyDefault(Supplier<T> defaultSupplier) {
return new DynamicConstant<>(defaultSupplier);
}
private DynamicConstant(Supplier<T> defaultSupplier) {
Validatec.notNull(defaultSupplier, "Cannot have a null supplier");
this.defaultSupplier = defaultSupplier;
}
public T get() {
T data = this.value.get();
if (data == null) {
T def = Validatec.notNull(defaultSupplier.get(), "Default/set value does not exist, or is null");
return set(def);
}
return data;
}
// If set is called with data that is incompatible with the previous get/set calls,
// An exception will be thrown, and no data will be changed.
public T set(T newVal) {
Validatec.notNull(newVal, "Cannot set value to null");
boolean updated = value.compareAndSet(null, newVal);
Validate.isTrue(updated || value.get().equals(newVal),
"New value: %s, does not match existing value: %s", newVal, value.get());
return newVal;
}
}
\ No newline at end of file
package org.rajivprab.cava;
import org.junit.Assert;
import org.junit.Test;
import static com.google.common.truth.Truth.assertThat;
public class DynamicConstantTest extends TestBase {
@Test
public void lazyDefaultShouldNeverGetCalled_ifSetIsInvoked() {
DynamicConstant<String> dynamicConstant = DynamicConstant.lazyDefault(() -> LandmineHolder.VAL);
dynamicConstant.set("override");
assertThat(dynamicConstant.get()).isEqualTo("override");
}
@Test
public void lazyDefaultShouldBeUsed_multipleTimes_ifSetIsNotInvoked() {
DynamicConstant<String> dynamicConstant = DynamicConstant.lazyDefault(() -> SafeHolder.VAL);
assertThat(dynamicConstant.get()).isEqualTo(SafeHolder.VAL);
assertThat(dynamicConstant.get()).isEqualTo(SafeHolder.VAL);
assertThat(dynamicConstant.get()).isEqualTo(SafeHolder.VAL);
}
@Test
public void defaultShouldBeUsed_multipleTimes_ifSetIsNotInvoked() {
String myDefault = "my default";
DynamicConstant<String> dynamicConstant = DynamicConstant.withDefault(myDefault);
assertThat(dynamicConstant.get()).isEqualTo(myDefault);
assertThat(dynamicConstant.get()).isEqualTo(myDefault);
assertThat(dynamicConstant.get()).isEqualTo(myDefault);
}
@Test
public void getDefault_thenSet_sameValue_shouldSucceed() {
DynamicConstant<String> dynamicConstant = DynamicConstant.lazyDefault(() -> SafeHolder.VAL);
dynamicConstant.get();
dynamicConstant.set(SafeHolder.VAL);
assertThat(dynamicConstant.get()).isEqualTo(SafeHolder.VAL);
}
@Test
public void getDefault_thenSet_differentValues_shouldFail() {
DynamicConstant<String> dynamicConstant = DynamicConstant.lazyDefault(() -> SafeHolder.VAL);
dynamicConstant.get();
try {
dynamicConstant.set("new_value");
Assert.fail();
} catch (IllegalArgumentException e) {
assertThat(e).hasMessageThat().matches("New value: .* does not match existing value: .*");
}
}
@Test
public void set_thenSet_sameValue_shouldSucceed() {
DynamicConstant<String> dynamicConstant = DynamicConstant.lazyDefault(() -> SafeHolder.VAL);
dynamicConstant.set("new value");
dynamicConstant.set("new value");
assertThat(dynamicConstant.get()).isEqualTo("new value");
}
@Test
public void set_thenSet_differentValues_shouldFail() {
DynamicConstant<String> dynamicConstant = DynamicConstant.lazyDefault(() -> SafeHolder.VAL);
dynamicConstant.set("new value 1");
try {
dynamicConstant.set("new value 2");
Assert.fail();
} catch (IllegalArgumentException e) {
assertThat(e).hasMessageThat().matches("New value: .* does not match existing value: .*");
}
}
@Test
public void setToNull_shouldThrowException() {
DynamicConstant<String> dynamicConstant = DynamicConstant.lazyDefault(() -> SafeHolder.VAL);
try {
dynamicConstant.set(null);
Assert.fail();
} catch (IllegalArgumentException e) {
assertThat(e).hasMessageThat().isEqualTo("Cannot set value to null");
}
}
@Test
public void noDefault_followedBySet_shouldSucceed() {
DynamicConstant<Integer> dynamicConstant = DynamicConstant.noDefault();
dynamicConstant.set(29);
assertThat(dynamicConstant.get()).isEqualTo(29);
}
@Test
public void noDefault_shouldThrowExceptionWhenCallingGet() {
DynamicConstant<Integer> dynamicConstant = DynamicConstant.noDefault();
try {
dynamicConstant.get();
Assert.fail();
} catch (IllegalArgumentException e) {
assertThat(e).hasMessageThat().isEqualTo("Default/set value does not exist, or is null");
}
}
@Test
public void supplierReturnsNull_shouldThrowExceptionDuringGet() {
DynamicConstant<Integer> dynamicConstant = DynamicConstant.lazyDefault(() -> null);
try {
dynamicConstant.get();
Assert.fail();
} catch (IllegalArgumentException e) {
assertThat(e).hasMessageThat().isEqualTo("Default/set value does not exist, or is null");
}
}
@Test
public void nullSupplier_shouldThrowExceptionImmediately() {
try {
DynamicConstant.lazyDefault(null);
Assert.fail();
} catch (IllegalArgumentException e) {
assertThat(e).hasMessageThat().isEqualTo("Cannot have a null supplier");
}
}
private static class LandmineHolder {
static final String VAL = getVal();
private static String getVal() {
throw new IllegalStateException();
}
}
private static class SafeHolder {
static final String VAL = getVal();
private static String getVal() {
return "Holder Value";
}
}
}
......@@ -18,7 +18,7 @@ public class TestBase {
log.error("error message");
log.warn("warn message");
log.info("info message");
log.debug("debug message");
log.trace("trace message");
log.debug("debug message - SHOULD NOT BE PRINTED");
log.trace("trace message - SHOULD NOT BE PRINTED");
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment