Intro
This class emulates a switch statement, in order to
- include non-switchable types (POJOs)
- have the same switch statement executable for different values
- be able to extend a previously defined switch
- TODO: Refactor into
ConsumerSwitch.BuilderandFunctionSwitch.Builderthat representsSwitchCase<Predicate<T>, Consumer<T>>andSwitchCase<Predicate<T>, Function<T, U>>
Implementation
/* Switch.java
* Copyright (C) 2018 Zymus
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.zephyrion.util;
import java.util.LinkedList;
import java.util.function.Consumer;
import java.util.function.Predicate;
public final class Switch<T> {
public static final class Builder<T> {
private final LinkedList<SwitchCase<T>> cases = new LinkedList<>();
private Consumer<T> defaultCase;
public Builder() {
}
public Builder(final Switch<T> existingSwitch) {
this.cases.addAll(existingSwitch.cases);
this.defaultCase = existingSwitch.defaultCase;
}
public Builder<T> when(final Predicate<T> predicate, final Consumer<T> consumer) {
cases.add(new SwitchCase<>(predicate, consumer, false));
return this;
}
public Builder<T> breakWhen(final Predicate<T> predicate, final Consumer<T> consumer) {
cases.add(new SwitchCase<>(predicate, consumer, true));
return this;
}
public Builder<T> defaultCase(final Consumer<T> consumer) {
this.defaultCase = consumer;
return this;
}
public Switch<T> build() {
return new Switch<>(cases, defaultCase);
}
}
private static class SwitchCase<T> {
private final Predicate<T> predicate;
private final Consumer<T> consumer;
private final boolean shouldBreak;
private SwitchCase(final Predicate<T> predicate, final Consumer<T> consumer, final boolean shouldBreak) {
this.predicate = predicate;
this.consumer = consumer;
this.shouldBreak = shouldBreak;
}
private boolean evaluate(final T value) {
if (predicate.test(value)) {
consumer.accept(value);
return true;
}
return false;
}
}
private LinkedList<SwitchCase<T>> cases;
private Consumer<T> defaultCase;
private Switch(LinkedList<SwitchCase<T>> cases, Consumer<T> defaultCase) {
this.cases = cases;
this.defaultCase = defaultCase;
}
public void evaluate(final T value) {
boolean caseExecuted = false;
for (final SwitchCase<T> switchCase : cases) {
caseExecuted = switchCase.evaluate(value);
if (switchCase.shouldBreak) {
break;
}
}
if (!caseExecuted) {
if (defaultCase != null) {
defaultCase.accept(value);
}
}
}
}
Tests
Tests use String as type, even though String is switchable, but this allows non-constant value tests.
/* SwitchTests.java
* Copyright (C) 2018 Zymus
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.zephyrion.util;
import org.junit.Test;
public class SwitchTests {
@Test
public void testSwitches() {
final String mod = "Mod";
final String notMod = "Admin";
final Switch<String> nonBreakingNameSwitch = createNonBreakingTestSwitch();
final Switch<String> breakingSwitch = createBreakBeforeLastTestSwitch();
nonBreakingNameSwitch.evaluate(mod);
System.out.println();
breakingSwitch.evaluate(mod);
System.out.println();
nonBreakingNameSwitch.evaluate(notMod);
System.out.println();
final Switch<String> extendedSwitch = createNonBreakingExtendedTestSwitch(nonBreakingNameSwitch);
System.out.println("===== Extended Switch =====");
extendedSwitch.evaluate(mod);
System.out.println();
extendedSwitch.evaluate(notMod);
System.out.println();
final Switch<String> extendedSwitchWithoutDefault = createNonBreakingExtendedTestSwitchWithoutDefault(nonBreakingNameSwitch);
System.out.println("===== Extended Switch Without Default =====");
extendedSwitchWithoutDefault.evaluate(mod);
System.out.println();
extendedSwitchWithoutDefault.evaluate(notMod);
System.out.println();
}
private Switch<String> createNonBreakingTestSwitch() {
final Switch.Builder<String> nameSwitchBuilder = new Switch.Builder<>();
return nameSwitchBuilder.defaultCase(this::printUnhandledName)
.when(this::nameContainsUppercaseLetter, this::printUppercaseName)
.when(this::nameIsMod, this::printNameIsMod)
.when(this::nameContainsM, this::printNameContainsM)
.build();
}
private Switch<String> createBreakBeforeLastTestSwitch() {
final Switch.Builder<String> nameSwitchBuilder = new Switch.Builder<>();
return nameSwitchBuilder.defaultCase(this::printUnhandledName)
.when(this::nameContainsUppercaseLetter, this::printUppercaseName)
.breakWhen(this::nameIsMod, this::printNameIsMod)
.when(this::nameContainsM, this::printNameContainsM)
.build();
}
private Switch<String> createNonBreakingExtendedTestSwitch(final Switch<String> existingSwitch) {
final Switch.Builder<String> nameSwitchBuilder = new Switch.Builder<>(existingSwitch);
return nameSwitchBuilder.defaultCase(this::differentDefaultCase)
.when(this::nameContainsO, this::printNameContainsO)
.build();
}
private Switch<String> createNonBreakingExtendedTestSwitchWithoutDefault(final Switch<String> existingSwitch) {
final Switch.Builder<String> nameSwitchBuilder = new Switch.Builder<>(existingSwitch);
return nameSwitchBuilder.when(this::nameContainsO, this::printNameContainsO)
.build();
}
private void printUnhandledName(final String name) {
System.out.println("Unhandled name: " + name);
}
// region Uppercase Case
private boolean nameContainsUppercaseLetter(final String name) {
return name.chars().anyMatch(Character::isUpperCase);
}
private void printUppercaseName(final String name) {
System.out.println(name + " contains uppercase letter");
}
// endregion
//region Mod case
private boolean nameIsMod(final String name) {
return name.equals("Mod");
}
private void printNameIsMod(final String name) {
System.out.println("name is Mod");
}
// endregion
//region M case
private boolean nameContainsM(final String name) {
return name.contains("M");
}
private void printNameContainsM(final String name) {
System.out.println("name contains M");
}
// endregion
// region o case
private boolean nameContainsO(final String name) {
return name.contains("o");
}
private void printNameContainsO(final String name) {
System.out.println("name contains o");
}
private void differentDefaultCase(final String name) {
System.out.println(name + " triggered the different default case");
}
// endregion
}
Output
Mod contains uppercase letter
name is Mod
name contains M
Mod contains uppercase letter
name is Mod
Admin contains uppercase letter
Unhandled name: Admin
===== Extended Switch =====
Mod contains uppercase letter
name is Mod
name contains M
name contains o
Admin contains uppercase letter
Admin triggered the different default case
===== Extended Switch Without Default =====
Mod contains uppercase letter
name is Mod
name contains M
name contains o
Admin contains uppercase letter
Unhandled name: Admin