Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package io.split.android.client.fallback;

import androidx.annotation.Nullable;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

public final class FallbackConfiguration {

@Nullable
private final FallbackTreatment mGlobal;
private final Map<String, FallbackTreatment> mByFlag;

private FallbackConfiguration(@Nullable FallbackTreatment global,
@Nullable Map<String, FallbackTreatment> byFlag) {
mGlobal = global;
if (byFlag == null || byFlag.isEmpty()) {
mByFlag = Collections.emptyMap();
} else {
mByFlag = Collections.unmodifiableMap(new HashMap<>(byFlag));
}
}

@Nullable
public FallbackTreatment getGlobal() {
return mGlobal;
}

public Map<String, FallbackTreatment> getByFlag() {
return mByFlag;
}

/**
* Creates a new {@link Builder} for {@link FallbackConfiguration}.
* Use this to provide an optional global fallback and flag-specific fallbacks.
*/
public static Builder builder() {
return new Builder();
}

public static final class Builder {
@Nullable
private FallbackTreatment mGlobal;
@Nullable
private Map<String, FallbackTreatment> mByFlag;

private Builder() {
mGlobal = null;
mByFlag = null;
}

/**
* Sets an optional global fallback treatment to be used when no flag-specific
* fallback exists for a given flag. This value is returned only in place of
* the "control" treatment.
*
* @param global optional global {@link FallbackTreatment}
* @return this builder instance
*/
public Builder global(@Nullable FallbackTreatment global) {
mGlobal = global;
return this;
}

/**
* Sets optional flag-specific fallback treatments, where keys are flag names.
* These take precedence over the global fallback.
*
* @param byFlag map of flag name to {@link FallbackTreatment}; may be null or empty
* @return this builder instance
*/
public Builder byFlag(@Nullable Map<String, FallbackTreatment> byFlag) {
mByFlag = byFlag;
return this;
}

/**
* Builds an immutable {@link FallbackConfiguration} snapshot of the
* configured values.
*
* @return a new immutable {@link FallbackConfiguration}
*/
public FallbackConfiguration build() {
return new FallbackConfiguration(mGlobal, mByFlag);
}
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FallbackConfiguration that = (FallbackConfiguration) o;
return Objects.equals(mGlobal, that.mGlobal) &&
Objects.equals(mByFlag, that.mByFlag);
}

@Override
public int hashCode() {
return Objects.hash(mGlobal, mByFlag);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package io.split.android.client.fallback;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.util.Objects;

/**
* Represents the fallback treatment, with an optional config and a fixed label.
*/
public final class FallbackTreatment {

public static final String LABEL = "fallback treatment";

@NonNull
private final String mTreatment;
@Nullable
private final String mConfig;

public FallbackTreatment(@NonNull String treatment) {
this(treatment, null);
}

public FallbackTreatment(@NonNull String treatment, @Nullable String config) {
mTreatment = treatment;
mConfig = config;
}

public String getTreatment() {
return mTreatment;
}

@Nullable
public String getConfig() {
return mConfig;
}

public String getLabel() {
return LABEL;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FallbackTreatment that = (FallbackTreatment) o;
return Objects.equals(mTreatment, that.mTreatment) &&
Objects.equals(mConfig, that.mConfig);
}

@Override
public int hashCode() {
return Objects.hash(mTreatment, mConfig);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package io.split.android.client.fallback;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertSame;

import org.junit.Test;

import java.util.HashMap;
import java.util.Map;

public class FallbackConfigurationTest {

@Test
public void constructorSetsFields() {
FallbackTreatment global = new FallbackTreatment("off");
Map<String, FallbackTreatment> map = new HashMap<>();
map.put("flagA", new FallbackTreatment("off"));

FallbackConfiguration cfg = FallbackConfiguration.builder()
.global(global)
.byFlag(map)
.build();

assertSame(global, cfg.getGlobal());
assertEquals(1, cfg.getByFlag().size());
assertEquals("off", cfg.getByFlag().get("flagA").getTreatment());
}

@Test
public void byFlagIsUnmodifiable() {
FallbackTreatment global = new FallbackTreatment("off");
Map<String, FallbackTreatment> byFlag = new HashMap<>();
byFlag.put("flagA", new FallbackTreatment("off"));

FallbackConfiguration config = FallbackConfiguration.builder()
.global(global)
.byFlag(byFlag)
.build();

byFlag.put("flagB", new FallbackTreatment("on"));

// config map must not change
assertEquals(1, config.getByFlag().size());

try {
config.getByFlag().put("x", new FallbackTreatment("on"));
throw new AssertionError("Map should be unmodifiable");
} catch (UnsupportedOperationException expected) {

}
}

@Test
public void equalityAndHashCodeByValue() {
FallbackTreatment global = new FallbackTreatment("off");
Map<String, FallbackTreatment> a = new HashMap<>();
a.put("flagA", new FallbackTreatment("off"));

Map<String, FallbackTreatment> b = new HashMap<>();
b.put("flagA", new FallbackTreatment("off"));

FallbackConfiguration configOne = FallbackConfiguration.builder().global(global).byFlag(a).build();
FallbackConfiguration configTwo = FallbackConfiguration.builder().global(global).byFlag(b).build();
FallbackConfiguration configThree = FallbackConfiguration.builder().global(null).byFlag(b).build();

assertEquals(configOne, configTwo);
assertEquals(configOne.hashCode(), configTwo.hashCode());
assertNotEquals(configOne, configThree);
assertNotEquals(configOne.hashCode(), configThree.hashCode());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package io.split.android.client.fallback;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;

import org.junit.Test;

public class FallbackTreatmentTest {

private static final String FALLBACK_TREATMENT = "fallback treatment";

@Test
public void constructorSetsFields() {
FallbackTreatment ft = new FallbackTreatment("off", "{\"k\":true}");
assertEquals("off", ft.getTreatment());
assertEquals("{\"k\":true}", ft.getConfig());
assertEquals(FALLBACK_TREATMENT, ft.getLabel());
}

@Test
public void configCanBeNull() {
FallbackTreatment ft = new FallbackTreatment("off", null);
assertEquals("off", ft.getTreatment());
assertNull(ft.getConfig());
assertEquals(FALLBACK_TREATMENT, ft.getLabel());
}

@Test
public void convenienceConstructorSetsNullConfig() {
FallbackTreatment ft = new FallbackTreatment("off");
assertEquals("off", ft.getTreatment());
assertNull(ft.getConfig());
assertEquals(FALLBACK_TREATMENT, ft.getLabel());
}

@Test
public void equalityAndHashCodeByValue() {
FallbackTreatment a = new FallbackTreatment("off", null);
FallbackTreatment b = new FallbackTreatment("off", null);
FallbackTreatment c = new FallbackTreatment("on", null);

assertEquals(a, b);
assertEquals(a.hashCode(), b.hashCode());
assertNotEquals(a, c);
}
}
Loading