diff --git a/bans-api/pom.xml b/bans-api/pom.xml
index 1d313a296..bd24bb965 100644
--- a/bans-api/pom.xml
+++ b/bans-api/pom.xml
@@ -6,7 +6,7 @@
space.arim.libertybans
bans-parent
- 0.1.4-SNAPSHOT
+ 0.2.0-SNAPSHOT
bans-api
@@ -14,7 +14,11 @@
src/main/java
- ${project.name}_v${project.version}
+
+
+
+ ${project.groupId}.${project.name}_${project.version}
+
org.codehaus.mojo
@@ -62,17 +66,22 @@
space.arim.omnibus
- omnibus-core
+ omnibus
space.arim.uuidvault
api
+
- org.junit.jupiter
- junit-jupiter-api
+ org.slf4j
+ slf4j-api
test
+
+ org.slf4j
+ slf4j-simple
+
\ No newline at end of file
diff --git a/bans-api/src/main/java/space/arim/libertybans/api/AbstractPunishment.java b/bans-api/src/main/java/space/arim/libertybans/api/AbstractPunishment.java
deleted file mode 100644
index 2544a3deb..000000000
--- a/bans-api/src/main/java/space/arim/libertybans/api/AbstractPunishment.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * LibertyBans-api
- * Copyright © 2020 Anand Beh
- *
- * LibertyBans-api is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * LibertyBans-api 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 Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with LibertyBans-api. If not, see
- * and navigate to version 3 of the GNU Affero General Public License.
- */
-package space.arim.libertybans.api;
-
-import java.util.Objects;
-
-/**
- * Abstract implementation of {@link PunishmentBase} so that {@link Punishment} implementations
- * and {@link DraftPunishment} itself may avoid boilerplate.
- *
- * @author A248
- *
- */
-public abstract class AbstractPunishment implements PunishmentBase {
-
- // Package visible so DraftPunishment may use them in equals/hashCode
-
- final PunishmentType type;
- final Victim victim;
- final Operator operator;
- final String reason;
- final Scope scope;
- final long start;
- final long end;
-
- protected AbstractPunishment(PunishmentType type, Victim victim, Operator operator, String reason, Scope scope, long start, long end) {
- this.type = Objects.requireNonNull(type, "PunishmentType must not be null");
- this.victim = Objects.requireNonNull(victim, "Victim must not be null");
- this.operator = Objects.requireNonNull(operator, "Operator must not be null");
- this.reason = Objects.requireNonNull(reason, "Reason must not be null");
- this.scope = Objects.requireNonNull(scope, "Scope must not be null");
- this.start = start;
- if (start < 0L) {
- throw new IllegalArgumentException("Start time must be greater than or equal to 0");
- }
- this.end = end;
- if (end < -1L) {
- throw new IllegalArgumentException("End time must be greater than or equal to 0, or -1 for permanent");
- }
- }
-
- @Override
- public PunishmentType getType() {
- return type;
- }
-
- @Override
- public Victim getVictim() {
- return victim;
- }
-
- @Override
- public Operator getOperator() {
- return operator;
- }
-
- @Override
- public String getReason() {
- return reason;
- }
-
- @Override
- public Scope getScope() {
- return scope;
- }
-
- @Override
- public long getStart() {
- return start;
- }
-
- @Override
- public long getEnd() {
- return end;
- }
-
- @Override
- public String toString() {
- if (this instanceof Punishment) {
- return getClass().getSimpleName() + " [id=" + ((Punishment) this).getID() + ", type=" + type + ", victim="
- + victim + ", operator=" + operator + ", reason=" + reason + ", scope=" + scope + ", start=" + start
- + ", end=" + end + "]";
- } else {
- return getClass().getSimpleName() + " [type=" + type + ", victim=" + victim + ", operator=" + operator
- + ", reason=" + reason + ", scope=" + scope + ", start=" + start + ", end=" + end + "]";
- }
- }
-
- @Override
- public abstract int hashCode();
-
- @Override
- public abstract boolean equals(Object object);
-
-}
diff --git a/bans-api/src/main/java/space/arim/libertybans/api/AddressVictim.java b/bans-api/src/main/java/space/arim/libertybans/api/AddressVictim.java
index 5c9c47b8b..db0a3f051 100644
--- a/bans-api/src/main/java/space/arim/libertybans/api/AddressVictim.java
+++ b/bans-api/src/main/java/space/arim/libertybans/api/AddressVictim.java
@@ -21,6 +21,8 @@
import java.net.InetAddress;
import java.util.Objects;
+import space.arim.libertybans.api.Victim.VictimType;
+
/**
* An IP address as the victim of a punishment
*
@@ -32,7 +34,6 @@ public final class AddressVictim extends Victim {
private final NetworkAddress address;
private AddressVictim(NetworkAddress address) {
- super(VictimType.ADDRESS);
this.address = address;
}
@@ -69,6 +70,15 @@ public static AddressVictim of(byte[] address) {
return new AddressVictim(NetworkAddress.of(address));
}
+ /**
+ * Gets this victim's type: {@link VictimType#ADDRESS}
+ *
+ */
+ @Override
+ public VictimType getType() {
+ return VictimType.ADDRESS;
+ }
+
/**
* Gets the network address represented by this victim
*
diff --git a/bans-api/src/main/java/space/arim/libertybans/api/ConsoleOperator.java b/bans-api/src/main/java/space/arim/libertybans/api/ConsoleOperator.java
index 812c0486e..2a9a8078d 100644
--- a/bans-api/src/main/java/space/arim/libertybans/api/ConsoleOperator.java
+++ b/bans-api/src/main/java/space/arim/libertybans/api/ConsoleOperator.java
@@ -32,10 +32,27 @@ public final class ConsoleOperator extends Operator {
*/
public static final ConsoleOperator INSTANCE = new ConsoleOperator();
- private ConsoleOperator() {
- super(OperatorType.CONSOLE);
+ private ConsoleOperator() {}
+
+ /**
+ * Gets this operator's type: {@link OperatorType#CONSOLE}
+ *
+ */
+ @Override
+ public OperatorType getType() {
+ return OperatorType.CONSOLE;
}
+ @Override
+ public int hashCode() {
+ return System.identityHashCode(this);
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ return this == object;
+ }
+
@Override
public String toString() {
return "ConsoleOperator.INSTANCE";
diff --git a/bans-api/src/main/java/space/arim/libertybans/api/DraftPunishment.java b/bans-api/src/main/java/space/arim/libertybans/api/DraftPunishment.java
deleted file mode 100644
index 8c5f2479b..000000000
--- a/bans-api/src/main/java/space/arim/libertybans/api/DraftPunishment.java
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- * LibertyBans-api
- * Copyright © 2020 Anand Beh
- *
- * LibertyBans-api is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * LibertyBans-api 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 Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with LibertyBans-api. If not, see
- * and navigate to version 3 of the GNU Affero General Public License.
- */
-package space.arim.libertybans.api;
-
-import java.util.Objects;
-
-/**
- * A punishment ready to be created, which does not yet have an ID
- *
- * @author A248
- *
- */
-public final class DraftPunishment extends AbstractPunishment {
-
- private DraftPunishment(PunishmentType type, Victim victim, Operator operator, String reason, Scope scope, long start, long end) {
- super(type, victim, operator, reason, scope, start, end);
- }
-
- /**
- * Builder of draft punishments. Should not be shared across threads
- *
- * @author A248
- *
- */
- public static class Builder {
-
- private PunishmentType type;
- private Victim victim;
- private Operator operator;
- private String reason;
- private Scope scope;
- private long start = -1L;
- private long end = 0L;
-
- /**
- * Creates the builder
- *
- */
- public Builder() {
-
- }
-
- /**
- * Sets the type of this builder to the given one
- *
- * @param type the punishment type
- * @return this builder
- * @throws NullPointerException if {@code type} is null
- */
- public Builder type(PunishmentType type) {
- this.type = Objects.requireNonNull(type, "type");
- return this;
- }
-
- /**
- * Sets the victim of this builder to the given one
- *
- * @param victim the victim
- * @return this builder
- * @throws NullPointerException if {@code victim} is null
- */
- public Builder victim(Victim victim) {
- this.victim = Objects.requireNonNull(victim, "victim");
- return this;
- }
-
- /**
- * Sets the operator of this builder to the given one
- *
- * @param operator the operator
- * @return this builder
- * @throws NullPointerException if {@code operator} is null
- */
- public Builder operator(Operator operator) {
- this.operator = Objects.requireNonNull(operator, "operator");
- return this;
- }
-
- /**
- * Sets the reason of this builder to the given one
- *
- * @param reason the reason
- * @return this builder
- * @throws NullPointerException if {@code reason} is null
- */
- public Builder reason(String reason) {
- this.reason = Objects.requireNonNull(reason, "reason");
- return this;
- }
-
- /**
- * Sets the scope of this builder to the given one
- *
- * @param scope the scope
- * @return this builder
- * @throws NullPointerException if {@code scope} is null
- */
- public Builder scope(Scope scope) {
- this.scope = Objects.requireNonNull(scope, "scope");
- return this;
- }
-
- /**
- * Sets the start time of this builder to the given one. The time is in unix seconds,
- * NOT miliseconds.
- *
- * To use the current time as the start time, don't call this method!
- * The API will fill in the current time automatically.
- *
- * @param start the start time in unix seconds
- * @return this builder
- */
- public Builder start(long start) {
- if (start < 0L) {
- throw new IllegalArgumentException("Start time must be greater than or equal to 0");
- }
- this.start = start;
- return this;
- }
-
- /**
- * Sets the end time of this builder to permanent. This is the default
- *
- * @return this builder
- */
- public Builder permanent() {
- return end(0L);
- }
-
- /**
- * Sets the end time of this builder to the given one. The time is in unix seconds,
- * NOT milliseconds.
- *
- * For a permanent punishment, don't call this method. The API will make the punishment
- * permanent unless specified otherwise.
- *
- * @param end the end time in unix seconds, or 0 for permanent
- * @return this builder
- */
- public Builder end(long end) {
- if (end < 0L) {
- throw new IllegalArgumentException("End time must be greater than or equal to 0");
- }
- this.end = end;
- return this;
- }
-
- /**
- * Builds into a {@link DraftPunishment}.
- *
- * Requires that this builder's type, victim, operator, reason, and scope are set. If any are unset,
- * {@code IllegalArgumentException} is thrown
- *
- * @return the draft punishment
- * @throws IllegalArgumentException if any detail has not been set
- */
- public DraftPunishment build() {
- if (type == null || victim == null || operator == null || reason == null || scope == null) {
- throw new IllegalArgumentException("Punishment details have not been set");
- }
- return new DraftPunishment(type, victim, operator, reason, scope,
- (start == -1L) ? (System.currentTimeMillis() / 1_000L) : start, end);
- }
-
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + type.hashCode();
- result = prime * result + victim.hashCode();
- result = prime * result + operator.hashCode();
- result = prime * result + reason.hashCode();
- result = prime * result + scope.hashCode();
- result = prime * result + (int) (start ^ (start >>> 32));
- result = prime * result + (int) (end ^ (end >>> 32));
- return result;
- }
-
- @Override
- public boolean equals(Object object) {
- if (this == object) {
- return true;
- }
- if (!(object instanceof DraftPunishment)) {
- return false;
- }
- DraftPunishment that = (DraftPunishment) object;
- return type == that.type && victim.equals(that.victim) && operator.equals(that.operator)
- && reason.equals(that.reason) && scope.equals(that.scope) && start == that.start && end == that.end;
- }
-
-}
diff --git a/bans-api/src/main/java/space/arim/libertybans/api/LibertyBans.java b/bans-api/src/main/java/space/arim/libertybans/api/LibertyBans.java
index 15e87bcc9..9f941308e 100644
--- a/bans-api/src/main/java/space/arim/libertybans/api/LibertyBans.java
+++ b/bans-api/src/main/java/space/arim/libertybans/api/LibertyBans.java
@@ -21,20 +21,66 @@
import space.arim.omnibus.Omnibus;
import space.arim.omnibus.util.concurrent.FactoryOfTheFuture;
+import space.arim.libertybans.api.manager.PunishmentDatabase;
+import space.arim.libertybans.api.manager.PunishmentFormatter;
+import space.arim.libertybans.api.manager.ScopeManager;
+import space.arim.libertybans.api.punish.PunishmentDrafter;
+import space.arim.libertybans.api.revoke.PunishmentRevoker;
+import space.arim.libertybans.api.select.PunishmentSelector;
+
+/**
+ * The main entry point to the LibertyBans API
+ *
+ * @author A248
+ *
+ */
public interface LibertyBans {
Omnibus getOmnibus();
FactoryOfTheFuture getFuturesFactory();
- PunishmentDatabase getDatabase();
+ /**
+ * Gets the punishment drafter used for creating punishments
+ *
+ * @return the punishment drafter
+ */
+ PunishmentDrafter getDrafter();
+
+ /**
+ * Gets the punishment revoker used for undoing punishments
+ *
+ * @return the punishment revoker
+ */
+ PunishmentRevoker getRevoker();
+ /**
+ * Gets the punishment selector used to select punishments from the database
+ *
+ * @return the punishment selector
+ */
PunishmentSelector getSelector();
- PunishmentEnactor getEnactor();
+ /**
+ * Gets the punishment database. Per the class javadoc of {@link PunishmentDatabase},
+ * use of this method should be avoided in all but a few cases
+ *
+ * @return the punishment database
+ */
+ PunishmentDatabase getDatabase();
- PunishmentEnforcer getEnforcer();
+ /**
+ * Gets the punishment formatter
+ *
+ * @return the formatter manager
+ */
+ PunishmentFormatter getFormatter();
+ /**
+ * Gets the scope manager used to create scopes
+ *
+ * @return the scope manager
+ */
ScopeManager getScopeManager();
}
diff --git a/bans-api/src/main/java/space/arim/libertybans/api/Operator.java b/bans-api/src/main/java/space/arim/libertybans/api/Operator.java
index 1498bc2fb..442c1fbf8 100644
--- a/bans-api/src/main/java/space/arim/libertybans/api/Operator.java
+++ b/bans-api/src/main/java/space/arim/libertybans/api/Operator.java
@@ -27,20 +27,14 @@
*/
public abstract class Operator {
- private final OperatorType type;
-
- Operator(OperatorType type) {
- this.type = type;
- }
+ Operator() {}
/**
* Gets the type of the operator
*
* @return the operator type
*/
- public OperatorType getType() {
- return type;
- }
+ public abstract OperatorType getType();
/**
* A type of operator. Corresponds to the subclasses of {@code Operator}
@@ -63,6 +57,16 @@ public enum OperatorType {
}
+ @Override
+ public abstract int hashCode();
+
+ /**
+ * Evaluates whether this operator is the same as another
+ *
+ */
+ @Override
+ public abstract boolean equals(Object object);
+
@Override
public abstract String toString();
diff --git a/bans-api/src/main/java/space/arim/libertybans/api/PlayerOperator.java b/bans-api/src/main/java/space/arim/libertybans/api/PlayerOperator.java
index 08906d89d..5f52a8060 100644
--- a/bans-api/src/main/java/space/arim/libertybans/api/PlayerOperator.java
+++ b/bans-api/src/main/java/space/arim/libertybans/api/PlayerOperator.java
@@ -32,7 +32,6 @@ public final class PlayerOperator extends Operator {
private final UUID uuid;
private PlayerOperator(UUID uuid) {
- super(OperatorType.PLAYER);
this.uuid = uuid;
}
@@ -41,12 +40,20 @@ private PlayerOperator(UUID uuid) {
*
* @param uuid the player UUID
* @return the operator representation of the player
- * @throws NullPointerException if {@code uuid} is null
*/
public static PlayerOperator of(UUID uuid) {
return new PlayerOperator(Objects.requireNonNull(uuid, "uuid"));
}
+ /**
+ * Gets this operator's type: {@link OperatorType#PLAYER}
+ *
+ */
+ @Override
+ public OperatorType getType() {
+ return OperatorType.PLAYER;
+ }
+
/**
* Gets the UUID of this player operator
*
diff --git a/bans-api/src/main/java/space/arim/libertybans/api/PlayerVictim.java b/bans-api/src/main/java/space/arim/libertybans/api/PlayerVictim.java
index f77c1e902..09bee07e8 100644
--- a/bans-api/src/main/java/space/arim/libertybans/api/PlayerVictim.java
+++ b/bans-api/src/main/java/space/arim/libertybans/api/PlayerVictim.java
@@ -32,8 +32,7 @@ public final class PlayerVictim extends Victim {
private final UUID uuid;
private PlayerVictim(UUID uuid) {
- super(VictimType.PLAYER);
- this.uuid = uuid;
+ this.uuid = Objects.requireNonNull(uuid, "uuid");
}
/**
@@ -41,10 +40,18 @@ private PlayerVictim(UUID uuid) {
*
* @param uuid the player UUID
* @return the victim representation of the player
- * @throws NullPointerException if {@code uuid} is null
*/
public static PlayerVictim of(UUID uuid) {
- return new PlayerVictim(Objects.requireNonNull(uuid, "uuid"));
+ return new PlayerVictim(uuid);
+ }
+
+ /**
+ * Gets this victim's type: {@link VictimType#PLAYER}
+ *
+ */
+ @Override
+ public VictimType getType() {
+ return VictimType.PLAYER;
}
/**
diff --git a/bans-api/src/main/java/space/arim/libertybans/api/Punishment.java b/bans-api/src/main/java/space/arim/libertybans/api/Punishment.java
deleted file mode 100644
index 93a8f9af0..000000000
--- a/bans-api/src/main/java/space/arim/libertybans/api/Punishment.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * LibertyBans-api
- * Copyright © 2020 Anand Beh
- *
- * LibertyBans-api is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * LibertyBans-api 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 Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with LibertyBans-api. If not, see
- * and navigate to version 3 of the GNU Affero General Public License.
- */
-package space.arim.libertybans.api;
-
-/**
- * A full punishment, identifiable by its ID
- *
- * @author A248
- *
- */
-public interface Punishment extends PunishmentBase {
-
- /**
- * Gets the unique ID of this punishment
- *
- * @return the ID of the punishment
- */
- int getID();
-
- /**
- * Whether this punishment is equal to another. Implementations
- * need only check {@link #getID()} since IDs must always be unique.
- *
- * @param object the other object
- * @return true if the objects are equal, false otherwise
- */
- @Override
- boolean equals(Object object);
-
-}
diff --git a/bans-api/src/main/java/space/arim/libertybans/api/PunishmentEnactor.java b/bans-api/src/main/java/space/arim/libertybans/api/PunishmentEnactor.java
deleted file mode 100644
index f01203ec5..000000000
--- a/bans-api/src/main/java/space/arim/libertybans/api/PunishmentEnactor.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * LibertyBans-api
- * Copyright © 2020 Anand Beh
- *
- * LibertyBans-api is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * LibertyBans-api 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 Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with LibertyBans-api. If not, see
- * and navigate to version 3 of the GNU Affero General Public License.
- */
-package space.arim.libertybans.api;
-
-import space.arim.omnibus.util.concurrent.CentralisedFuture;
-
-/**
- * Guardkeeper of adding punishments while enforcing constraints and reporting such enforcement.
- * For example, one victim cannot have more than 1 ban.
- *
- * Generally, methods to undo punishments taking more information are more efficient than those taking less.
- * For example, the "undoAndGet" methods are less efficient than the similar and simpler "undo" methods which
- * do not return the undone {@code Punishment}, since the latter do not need to additionally fetch the punishment.
- *
- * @author A248
- *
- */
-public interface PunishmentEnactor {
-
- /**
- * Enacts a punishment, adding it to the database.
- * If the punishment type is a ban or mute, and there is already a ban or mute for the victim,
- * the future will yield {@code null}.
- *
- * Assuming the caller wants the punishment to be enforced, {@link PunishmentEnforcer#enforce(Punishment)}
- * should be called after enaction.
- *
- * @param draftPunishment the draft punishment to enact
- * @return a centralised future which yields the punishment or {@code null} if there was a conflict
- */
- CentralisedFuture enactPunishment(DraftPunishment draftPunishment);
-
- /**
- * Undoes an existing punishment in the database.
- * If the punishment existed and was removed, the future yields {@code true}, else {@code false}.
- *
- * @param punishment the punishment to undo
- * @return a centralised future which yields {@code true} if the punishment existed and was removed, {@code false} otherwise
- */
- CentralisedFuture undoPunishment(Punishment punishment);
-
- /**
- * Undoes a punishment by its ID and type.
- * If the punishment with the ID and of the specified type existed and was removed, the future yields {@code true},
- * else {@code false}.
- *
- * Efficiency implications
- * When the full {@code Punishment} is known, {@link #undoPunishment(Punishment)} should be used.
- *
- * @param id the id of the punishment to undo
- * @param type the type of the punishment to undo
- * @return a centralised future which yields {@code true} if the punishment existed and was removed, {@code false} otherwise
- */
- CentralisedFuture undoPunishmentByIdAndType(int id, PunishmentType type);
-
- /**
- * Undoes and gets a punishment by its ID and type.
- * If the punishment with the ID and of the specified type existed and was removed, the future yields it,
- * else {@code null}.
- *
- * Efficiency implications
- * When the undone {@code Punishment} is not needed, {@link #undoPunishmentByIdAndType(int, PunishmentType)} should be used.
- *
- * @param id the id of the punishment to undo
- * @param type the type of the punishment to undo
- * @return a centralised future which yields the punishment if it existed and was removed, {@code null} otherwise
- */
- CentralisedFuture undoAndGetPunishmentByIdAndType(int id, PunishmentType type);
-
- /**
- * Undoes a punishment according to its ID.
- * If the punishment with the ID existed and was removed, the future yields {@code true}, else {@code false}.
- *
- * Efficiency implications
- * When the full {@code Punishment} is known, {@link #undoPunishment(Punishment)} should be used.
- * When the punishment type is known, {@link #undoPunishmentByIdAndType(int, PunishmentType)} should be used.
- *
- * @param id the id of the punishment to undo
- * @return a centralised future which yields {@code true} if the punishment existed and was removed, {@code false} otherwise
- */
- CentralisedFuture undoPunishmentById(int id);
-
- /**
- * Undoes and gets a punishment according to its ID.
- * If the punishment with the ID existed and was removed, the future yields it, else {@code null}.
- *
- * Efficiency implications
- * When the undone {@code Punishment} is not needed, {@link #undoPunishmentById(int)} should be used.
- * When the punishment type is known, {@link #undoAndGetPunishmentByIdAndType(int, PunishmentType)} should be used.
- *
- * @param id the id of the punishment to undo
- * @return a centralised future which yields the punishment if it existed and was removed, {@code null} otherwise
- */
- CentralisedFuture undoAndGetPunishmentById(int id);
-
- /**
- * Undoes a single punishment by its type and victim. This may only be used for singular punishments
- * (bans and mutes), since it relies on the fact that a single victim cannot have more than 1 such punishment.
- *
- * @param type the punishment type, must be singular per {@link PunishmentType#isSingular()}
- * @param victim the victim whose punishment to undo
- * @return a centralised future which yields {@code true} if the punishment existed and was removed, {@code false} otherwise
- * @throws IllegalArgumentException if {@code type} is not singular (BAN or MUTE)
- */
- CentralisedFuture undoPunishmentByTypeAndVictim(PunishmentType type, Victim victim);
-
- /**
- * Undoes and gets a single punishment by its type and victim. This may only be used for singular punishments
- * (bans and mutes), since it relies on the fact that a single victim cannot have more than 1 such punishment.
- *
- * Efficiency implications
- * When the undone {@code Punishment} is not needed, {@link #undoPunishmentByTypeAndVictim(PunishmentType, Victim)}
- * should be used.
- *
- * @param type the punishment type, must be singular per {@link PunishmentType#isSingular()}
- * @param victim the victim whose punishment to undo
- * @return a centralised future which yields the punishment if it existed and was removed, {@code null} otherwise
- * @throws IllegalArgumentException if {@code type} is not singular (BAN or MUTE)
- */
- CentralisedFuture undoAndGetPunishmentByTypeAndVictim(PunishmentType type, Victim victim);
-
-}
diff --git a/bans-api/src/main/java/space/arim/libertybans/api/PunishmentEnforcer.java b/bans-api/src/main/java/space/arim/libertybans/api/PunishmentEnforcer.java
deleted file mode 100644
index c09fd7249..000000000
--- a/bans-api/src/main/java/space/arim/libertybans/api/PunishmentEnforcer.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * LibertyBans-api
- * Copyright © 2020 Anand Beh
- *
- * LibertyBans-api is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * LibertyBans-api 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 Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with LibertyBans-api. If not, see
- * and navigate to version 3 of the GNU Affero General Public License.
- */
-package space.arim.libertybans.api;
-
-import space.arim.omnibus.util.concurrent.CentralisedFuture;
-
-/**
- * Enforcer of punishments
- *
- * @author A248
- *
- */
-public interface PunishmentEnforcer {
-
- /**
- * Enforces a punishment.
- *
- * For bans and mutes, this will kick players matching the punishment's
- * victim. For mutes and warn, the players will be sent a warning message.
- * Additionally for mutes, the player will be unable to chat (depending on the implementation)
- * until the mute cache expires, at which point the database is re-queried for a mute.
- *
- * @param punishment the punishment to enforce
- * @return a future completed when enforcement has been conducted
- */
- CentralisedFuture> enforce(Punishment punishment);
-
- /**
- * "Unenforces" a punishment.
- *
- * This will update local punishment caches, such as the mute cache.
- *
- * @param punishment the punishment to unenforce
- * @return a future completed when unenforcement has been conducted
- */
- CentralisedFuture> unenforce(Punishment punishment);
-
-}
diff --git a/bans-api/src/main/java/space/arim/libertybans/api/PunishmentSelection.java b/bans-api/src/main/java/space/arim/libertybans/api/PunishmentSelection.java
deleted file mode 100644
index 835121b47..000000000
--- a/bans-api/src/main/java/space/arim/libertybans/api/PunishmentSelection.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * LibertyBans-api
- * Copyright © 2020 Anand Beh
- *
- * LibertyBans-api is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * LibertyBans-api 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 Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with LibertyBans-api. If not, see
- * and navigate to version 3 of the GNU Affero General Public License.
- */
-package space.arim.libertybans.api;
-
-/**
- * A selection which will match punishments in the database with certain details.
- * To retrieve an instance, use the {@link Builder}
- *
- * @author A248
- *
- */
-public final class PunishmentSelection {
-
- private final PunishmentType type;
- private final Victim victim;
- private final Operator operator;
- private final Scope scope;
- private final boolean selectActiveOnly;
-
- PunishmentSelection(PunishmentType type, Victim victim, Operator operator, Scope scope, boolean selectActiveOnly) {
- this.type = type;
- this.victim = victim;
- this.operator = operator;
- this.scope = scope;
- this.selectActiveOnly = selectActiveOnly;
- }
-
- /**
- * Gets the punishment type matched, or {@code null} to match all types
- *
- * @return the punishment type or {@code null} for all types
- */
- public PunishmentType getType() {
- return type;
- }
-
- /**
- * Gets the victim matched, or {@code null} to match all victims
- *
- * @return the victim or {@code null} for all victims
- */
- public Victim getVictim() {
- return victim;
- }
-
- /**
- * Gets the operator matched, or {@code null} to match all operators
- *
- * @return the operator or {@code null} for all operators
- */
- public Operator getOperator() {
- return operator;
- }
-
- /**
- * Gets the scope matched, or {@code null} to match all scopes
- *
- * @return the scope or {@code null} for all scopes
- */
- public Scope getScope() {
- return scope;
- }
-
- /**
- * Whether this selection will match only active, non-expired punishments. If {@code false},
- * then this selection may match undone or expired punishments as drawn from the punishments
- * history.
- *
- * @return true to select only active and non-expired punishments, false to select all punishments
- */
- public boolean selectActiveOnly() {
- return selectActiveOnly;
- }
-
- /**
- * Builder of {@link PunishmentSelection}s
- *
- * @author A248
- *
- */
- public static class Builder {
-
- private PunishmentType type;
- private Victim victim;
- private Operator operator;
- private Scope scope;
- private boolean selectActiveOnly = true;
-
- /**
- * Sets the punishment type matched, or {@code null} to match all types
- *
- * @param type the punishment type or {@code null} for all types
- * @return this builder
- */
- public Builder type(PunishmentType type) {
- this.type = type;
- return this;
- }
-
- /**
- * Sets the victim matched, or {@code null} to match all victims
- *
- * @param victim the victim or {@code null} for all victims
- * @return this builder
- */
- public Builder victim(Victim victim) {
- this.victim = victim;
- return this;
- }
-
- /**
- * Sets the operator matched, or {@code null} to match all operators
- *
- * @param operator the operator or {@code null} for all operators
- * @return this builder
- */
- public Builder operator(Operator operator) {
- this.operator = operator;
- return this;
- }
-
- /**
- * Sets the scope matched, or {@code null} to match all scopes
- *
- * @param scope the scope or {@code null} for all scopes
- * @return this builder
- */
- public Builder scope(Scope scope) {
- this.scope = scope;
- return this;
- }
-
- /**
- * Sets to match only active, non-expired punishments.
- *
- * @return this builder
- */
- public Builder selectActiveOnly() {
- selectActiveOnly = true;
- return this;
- }
-
- /**
- * Sets to match all punishments, including those undone or expired
- *
- * @return this builder
- */
- public Builder selectAll() {
- selectActiveOnly = false;
- return this;
- }
-
- /**
- * Builds into a full {@link PunishmentSelection}
- *
- * @return a selection based on this builder's details
- */
- public PunishmentSelection build() {
- return new PunishmentSelection(type, victim, operator, scope, selectActiveOnly);
- }
-
- }
-
-}
diff --git a/bans-api/src/main/java/space/arim/libertybans/api/PunishmentType.java b/bans-api/src/main/java/space/arim/libertybans/api/PunishmentType.java
index 9c42bc656..d8bfa0ca5 100644
--- a/bans-api/src/main/java/space/arim/libertybans/api/PunishmentType.java
+++ b/bans-api/src/main/java/space/arim/libertybans/api/PunishmentType.java
@@ -28,16 +28,10 @@
*/
public enum PunishmentType {
- BAN(true),
- MUTE(true),
- WARN(false),
- KICK(false);
-
- private final boolean singular;
-
- private PunishmentType(boolean singular) {
- this.singular = singular;
- }
+ BAN,
+ MUTE,
+ WARN,
+ KICK;
/**
* If this punishment type is singular, i.e., whether there can only be 1 kind of it
@@ -47,16 +41,16 @@ private PunishmentType(boolean singular) {
* @return true if the type is singular, false otherwise
*/
public boolean isSingular() {
- return singular;
+ return this == BAN || this == MUTE;
}
/**
- * Shortcut for {@link #name()} lowercased using the english locale
+ * Shortcut for {@link #name()} lowercased using the root locale
*
* @return the lowercased name
*/
public String getLowercaseName() {
- return name().toLowerCase(Locale.ENGLISH);
+ return name().toLowerCase(Locale.ROOT);
}
/**
@@ -68,28 +62,6 @@ public String getLowercaseNamePlural() {
return getLowercaseName() + 's';
}
- /**
- * Gets a PunishmentType from an ordinal, or {@code null} if no such
- * ordinal exists in the enum
- *
- * @param ordinal the ordinal, 0, 1, 2, 3
- * @return the corresponding punishment type, or {@code null}
- */
- public static PunishmentType fromOrdinal(int ordinal) {
- switch (ordinal) {
- case 0:
- return BAN;
- case 1:
- return MUTE;
- case 2:
- return WARN;
- case 3:
- return KICK;
- default:
- return null;
- }
- }
-
@Override
public String toString() {
String name = name();
diff --git a/bans-api/src/main/java/space/arim/libertybans/api/Scope.java b/bans-api/src/main/java/space/arim/libertybans/api/ServerScope.java
similarity index 80%
rename from bans-api/src/main/java/space/arim/libertybans/api/Scope.java
rename to bans-api/src/main/java/space/arim/libertybans/api/ServerScope.java
index ec83667b0..2f66dae15 100644
--- a/bans-api/src/main/java/space/arim/libertybans/api/Scope.java
+++ b/bans-api/src/main/java/space/arim/libertybans/api/ServerScope.java
@@ -18,13 +18,15 @@
*/
package space.arim.libertybans.api;
+import space.arim.libertybans.api.manager.ScopeManager;
+
/**
* The server-specific scope of a punishment. To get a scope, see {@link ScopeManager}
*
* @author A248
*
*/
-public interface Scope {
+public interface ServerScope {
/**
* Whether this scope applies to a server
@@ -35,9 +37,8 @@ public interface Scope {
boolean appliesTo(String server);
/**
- * Determines equality with a given object. The contract of the {@code equals} method for {@code Scope}
- * is that the other object must be an instance of the same implementation type, and it must apply to the
- * same possible server strings as described in {@link #appliesTo(String)}
+ * Determines equality with a given object. The other object must be a scope applying to the same servers
+ * as described in {@link #appliesTo(String)}
*
* @param object the object to determine equality with
* @return true if equal, false otherwise
diff --git a/bans-api/src/main/java/space/arim/libertybans/api/Victim.java b/bans-api/src/main/java/space/arim/libertybans/api/Victim.java
index 729026854..1dfef2e20 100644
--- a/bans-api/src/main/java/space/arim/libertybans/api/Victim.java
+++ b/bans-api/src/main/java/space/arim/libertybans/api/Victim.java
@@ -27,20 +27,14 @@
*/
public abstract class Victim {
- private final VictimType type;
-
- Victim(VictimType type) {
- this.type = type;
- }
+ Victim() {}
/**
* Gets the type of the victim
*
* @return the victim type
*/
- public VictimType getType() {
- return type;
- }
+ public abstract VictimType getType();
/**
* A victim type. Corresponds to the subclasses of {@code Victim}
@@ -51,7 +45,7 @@ public VictimType getType() {
public enum VictimType {
/**
- * A player
+ * A player, identified by UUID
*
*/
PLAYER,
@@ -61,29 +55,15 @@ public enum VictimType {
*/
ADDRESS;
- /**
- * Gets a VictimType from an ordinal, or {@code null} if no such
- * ordinal exists in the enum
- *
- * @param ordinal the ordinal, 0 or 1
- * @return the corresponding victim type, or {@code null}
- */
- public static VictimType fromOrdinal(int ordinal) {
- switch (ordinal) {
- case 0:
- return PLAYER;
- case 1:
- return ADDRESS;
- default:
- return null;
- }
- }
-
}
@Override
public abstract int hashCode();
+ /**
+ * Evaluates whether this victim is the same as another
+ *
+ */
@Override
public abstract boolean equals(Object object);
diff --git a/bans-api/src/main/java/space/arim/libertybans/api/PunishmentDatabase.java b/bans-api/src/main/java/space/arim/libertybans/api/manager/PunishmentDatabase.java
similarity index 87%
rename from bans-api/src/main/java/space/arim/libertybans/api/PunishmentDatabase.java
rename to bans-api/src/main/java/space/arim/libertybans/api/manager/PunishmentDatabase.java
index aa568a02b..fde46bede 100644
--- a/bans-api/src/main/java/space/arim/libertybans/api/PunishmentDatabase.java
+++ b/bans-api/src/main/java/space/arim/libertybans/api/manager/PunishmentDatabase.java
@@ -16,12 +16,14 @@
* along with LibertyBans-api. If not, see
* and navigate to version 3 of the GNU Affero General Public License.
*/
-package space.arim.libertybans.api;
+package space.arim.libertybans.api.manager;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.concurrent.Executor;
+import space.arim.libertybans.api.select.PunishmentSelector;
+
/**
* Access to the raw database. Use of this should be avoided at great cost. Executing SQL queries
* is not considered traditional API.
@@ -33,19 +35,20 @@
* API Status
* Use of the SQL backend directly is not considered traditional API. Although attempts will be made to limit
* the number of breaking changes, LibertyBans's major version will not necessarily change to match the SQL backend.
- * Unlike other plugins, LibertyBans makes use of a wide swath of queries.
+ * Unlike other plugins, LibertyBans makes use of a wide swath of SQL queries and operations, so a rigid table
+ * structure is not feasible.
*
- * Rather, a separate version is maintained specifically for the SQL backend. This version is maintained returned by
+ * Rather, a separate version is maintained specifically for the SQL backend. This version is returned by
* {@link #getMajorRevision()} and {@link #getMinorRevision()}. Additionally, it is located in the table
* libertybans_revision
, with the {@code major} and {@code minor} columns indicating the major version
* and minor version, respectively (both columns use the INT data type).
*
- * Whenever the tables and/or views definitions change incompatibly, the major version will be incremented. The minor
+ * Whenever the tables and/or views definitions change incompatibly, this major version will be incremented. This minor
* version will be incremented as new tables and views are added or existing ones changed, but not in ways which
* break backwards compatibility.
*
- * Stored routines are not supported within any versioning context. Use of any SQL functions or procedures created
- * by LibertyBans is strictly unsupported.
+ * Stored routines are not supported within any versioning context. Use of any SQL functions or procedures created
+ * by LibertyBans is strictly unsupported.
*
* @author A248
*
@@ -58,7 +61,7 @@ public interface PunishmentDatabase {
* Use of this should be avoided at great cost. Executing SQL queries is not considered traditional API.
* See the class javadoc for more information.
*
- * @return a connection from the plugin pool, which should be closed after immediately use
+ * @return a connection from the plugin pool, which should be closed immediately after use
* @throws SQLException as relayed from JDBC
*/
Connection getConnection() throws SQLException;
diff --git a/bans-api/src/main/java/space/arim/libertybans/api/manager/PunishmentFormatter.java b/bans-api/src/main/java/space/arim/libertybans/api/manager/PunishmentFormatter.java
new file mode 100644
index 000000000..fd98e0385
--- /dev/null
+++ b/bans-api/src/main/java/space/arim/libertybans/api/manager/PunishmentFormatter.java
@@ -0,0 +1,62 @@
+/*
+ * LibertyBans-api
+ * Copyright © 2020 Anand Beh
+ *
+ * LibertyBans-api is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * LibertyBans-api 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with LibertyBans-api. If not, see
+ * and navigate to version 3 of the GNU Affero General Public License.
+ */
+package space.arim.libertybans.api.manager;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * Contains what little API exists for formatting punishments
+ *
+ */
+public interface PunishmentFormatter {
+
+ /**
+ * Gets the configured timezone
+ *
+ * @return the zone id used for displaying dates
+ */
+ ZoneId getTimezone();
+
+ /**
+ * Gets the configured date time formatter used for displaying dates
+ *
+ * @return the date time formatter
+ */
+ DateTimeFormatter getDateTimeFormatter();
+
+ /**
+ * Formats an absolute date in conjunction with {@link #getTimezone()} and {@link #getDateTimeFormatter()}
+ *
+ * @param date the date to format
+ * @return the formatted string
+ */
+ String formatAbsoluteDate(Instant date);
+
+ /**
+ * Formats a duration
+ *
+ * @param duration the duration
+ * @return the formatted string
+ */
+ String formatDuration(Duration duration);
+
+}
diff --git a/bans-api/src/main/java/space/arim/libertybans/api/ScopeManager.java b/bans-api/src/main/java/space/arim/libertybans/api/manager/ScopeManager.java
similarity index 77%
rename from bans-api/src/main/java/space/arim/libertybans/api/ScopeManager.java
rename to bans-api/src/main/java/space/arim/libertybans/api/manager/ScopeManager.java
index 862b3b9e9..143deeab3 100644
--- a/bans-api/src/main/java/space/arim/libertybans/api/ScopeManager.java
+++ b/bans-api/src/main/java/space/arim/libertybans/api/manager/ScopeManager.java
@@ -16,10 +16,12 @@
* along with LibertyBans-api. If not, see
* and navigate to version 3 of the GNU Affero General Public License.
*/
-package space.arim.libertybans.api;
+package space.arim.libertybans.api.manager;
+
+import space.arim.libertybans.api.ServerScope;
/**
- * Factory which produces {@link Scope}s
+ * Factory which produces {@link ServerScope}s
*
* @author A248
*
@@ -34,13 +36,20 @@ public interface ScopeManager {
* @return a scope applying to the server
* @throws IllegalArgumentException if server.length()
{@literal >} 32
*/
- Scope specificScope(String server);
+ ServerScope specificScope(String server);
/**
* Gets the global scope, applying to all servers
*
* @return the global scope
*/
- Scope globalScope();
+ ServerScope globalScope();
+
+ /**
+ * Gets a scope applying to the current server
+ *
+ * @return a scope applying to the current server
+ */
+ ServerScope currentServerScope();
}
diff --git a/bans-api/src/main/java/space/arim/libertybans/api/package-info.java b/bans-api/src/main/java/space/arim/libertybans/api/package-info.java
new file mode 100644
index 000000000..81325ab2f
--- /dev/null
+++ b/bans-api/src/main/java/space/arim/libertybans/api/package-info.java
@@ -0,0 +1,16 @@
+/**
+ * The base of the LibertyBans API.
+ *
+ * Nullability
+ * Unless stated otherwise, no method will return {@code null}. All nullability is explicitly mentioned in the javadoc,
+ * therefore the lack of such mention implies the return value or method parameter in question will be or must be nonnull.
+ *
+ * This further implies all methods not declared to accept {@code null} parameters throw {@code NullPointerException}
+ * on null input.
+ *
+ * Thread Safety
+ * Generally, objects are immutable and thread safe. The only exception to this rule are builder objects, which
+ * are mutable and unsafe for concurrent use. It is only intended for builders to be used from a single thread.
+ *
+ */
+package space.arim.libertybans.api;
\ No newline at end of file
diff --git a/bans-api/src/main/java/space/arim/libertybans/api/punish/DraftPunishment.java b/bans-api/src/main/java/space/arim/libertybans/api/punish/DraftPunishment.java
new file mode 100644
index 000000000..0e0375b5f
--- /dev/null
+++ b/bans-api/src/main/java/space/arim/libertybans/api/punish/DraftPunishment.java
@@ -0,0 +1,77 @@
+/*
+ * LibertyBans-api
+ * Copyright © 2020 Anand Beh
+ *
+ * LibertyBans-api is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * LibertyBans-api 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with LibertyBans-api. If not, see
+ * and navigate to version 3 of the GNU Affero General Public License.
+ */
+package space.arim.libertybans.api.punish;
+
+import java.time.Duration;
+
+import space.arim.omnibus.util.concurrent.CentralisedFuture;
+
+/**
+ * A punishment ready to be created. Does not yet have an ID or start and end time, but does contain a duration.
+ *
+ * To obtain an instance, use a {@link DraftPunishmentBuilder} obtained from {@link PunishmentDrafter}
+ *
+ * @author A248
+ *
+ */
+public interface DraftPunishment extends PunishmentBase {
+
+ /**
+ * Gets the duration of this draft punishment
+ *
+ * @return the duration
+ */
+ Duration getDuration();
+
+ /**
+ * Enacts this punishment, adding it to the database, then enforces it.
+ *
+ * If the punishment type is a ban or mute, and there is already an active ban or mute for the victim,
+ * the future will yield {@code null}. See {@link space.arim.libertybans.api.punish} for a description of
+ * active and historical punishments.
+ *
+ * @return a centralised future which yields the punishment or {@code null} if there was a conflict
+ */
+ CentralisedFuture enactPunishment();
+
+ /**
+ * Enacts this punishment, adding it to the database.
+ *
+ * If the punishment type is a ban or mute, and there is already an active ban or mute for the victim,
+ * the future will yield {@code null}. See {@link space.arim.libertybans.api.punish} for a description of
+ * active and historical punishments.
+ *
+ * Most callers will want to use {@link #enactPunishment()} instead, which has the added effect
+ * of enforcing the punishment.
+ *
+ * @return a centralised future which yields the punishment or {@code null} if there was a conflict
+ */
+ CentralisedFuture enactPunishmentWithoutEnforcement();
+
+ /**
+ * Whether this draft punishment is equal to another. The other draft punishment
+ * must have the same details as this one
+ *
+ * @param object the object to determine equality with
+ * @return true if equal, false otherwise
+ */
+ @Override
+ boolean equals(Object object);
+
+}
diff --git a/bans-api/src/main/java/space/arim/libertybans/api/punish/DraftPunishmentBuilder.java b/bans-api/src/main/java/space/arim/libertybans/api/punish/DraftPunishmentBuilder.java
new file mode 100644
index 000000000..29a294ac8
--- /dev/null
+++ b/bans-api/src/main/java/space/arim/libertybans/api/punish/DraftPunishmentBuilder.java
@@ -0,0 +1,104 @@
+/*
+ * LibertyBans-api
+ * Copyright © 2020 Anand Beh
+ *
+ * LibertyBans-api is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * LibertyBans-api 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with LibertyBans-api. If not, see
+ * and navigate to version 3 of the GNU Affero General Public License.
+ */
+package space.arim.libertybans.api.punish;
+
+import java.time.Duration;
+
+import space.arim.libertybans.api.ConsoleOperator;
+import space.arim.libertybans.api.Operator;
+import space.arim.libertybans.api.PunishmentType;
+import space.arim.libertybans.api.ServerScope;
+import space.arim.libertybans.api.Victim;
+
+/**
+ * Builder of draft punishments. It is required to set the type, victim, and reason.
+ *
+ * @author A248
+ *
+ */
+public interface DraftPunishmentBuilder {
+
+ /**
+ * Sets the punishment type of this builder to the specified one. Required operation
+ *
+ * @param type the type of the punishment
+ * @return this builder
+ */
+ DraftPunishmentBuilder type(PunishmentType type);
+
+ /**
+ * Sets the victim of this builder to the specified one. Required operation
+ *
+ * @param victim the victim of the punishmnt
+ * @return this builder
+ */
+ DraftPunishmentBuilder victim(Victim victim);
+
+ /**
+ * Sets the operator of this builder to the specified one. If unspecified, the console operator
+ * is used ({@link ConsoleOperator#INSTANCE})
+ *
+ * @param operator the operator of the punishment, by default the console
+ * @return this builder
+ */
+ DraftPunishmentBuilder operator(Operator operator);
+
+ /**
+ * Sets the reason of this builder to the specified one. Required operation
+ *
+ * @param reason the reason of the punishment
+ * @return this builder
+ */
+ DraftPunishmentBuilder reason(String reason);
+
+ /**
+ * Sets the duration of this builder to the specified one. If unspecified, a duration of zero,
+ * indicating a permanent duration, is used
+ *
+ * @param duration the duration of the punishment, or zero for a permanent punishment
+ * @return this builder
+ * @throws IllegalArgumentException if {@code duration} is negative
+ */
+ DraftPunishmentBuilder duration(Duration duration);
+
+ /**
+ * Sets the scope of this builder to the specified one. If unspecified, the global scope
+ * is used
+ *
+ * @param scope the scope of the punishment
+ * @return this builder
+ */
+ DraftPunishmentBuilder scope(ServerScope scope);
+
+ /**
+ * Builds into a full draft punishment. All the required information must be set
+ * on this builder.
+ *
+ * May be used repeatedly without side effects.
+ *
+ * @return a draft punishment from this builder's details
+ * @throws IllegalStateException if any of the required details on this
+ * builder are not set
+ * @throws IllegalArgumentException if the punishment type of this builder is
+ * {@link PunishmentType#KICK} and the duration
+ * is not permanent
+ */
+ DraftPunishment build();
+
+}
diff --git a/bans-api/src/main/java/space/arim/libertybans/api/punish/Punishment.java b/bans-api/src/main/java/space/arim/libertybans/api/punish/Punishment.java
new file mode 100644
index 000000000..e6b88cdc1
--- /dev/null
+++ b/bans-api/src/main/java/space/arim/libertybans/api/punish/Punishment.java
@@ -0,0 +1,163 @@
+/*
+ * LibertyBans-api
+ * Copyright © 2020 Anand Beh
+ *
+ * LibertyBans-api is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * LibertyBans-api 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with LibertyBans-api. If not, see
+ * and navigate to version 3 of the GNU Affero General Public License.
+ */
+package space.arim.libertybans.api.punish;
+
+import java.time.Instant;
+
+import space.arim.omnibus.util.concurrent.CentralisedFuture;
+
+/**
+ * A full punishment, identifiable by its ID.
+ *
+ * See {@link space.arim.libertybans.api.punish} for a description of active and historical punishments.
+ *
+ * @author A248
+ *
+ */
+public interface Punishment extends PunishmentBase {
+
+ /**
+ * Gets the unique ID of this punishment
+ *
+ * @return the ID of the punishment
+ */
+ int getID();
+
+ /**
+ * Gets the start time of the punishment
+ *
+ * @return the state date
+ */
+ Instant getStartDate();
+
+ /**
+ * Gets the start date of the punishment, in unix seconds
+ *
+ * @return the start date seconds
+ */
+ default long getStartDateSeconds() {
+ return getStartDate().getEpochSecond();
+ }
+
+ /**
+ * Gets the end time of the punishment. {@link Instant#MAX} is used for a permanent punishment
+ *
+ * @return the end date
+ */
+ Instant getEndDate();
+
+ /**
+ * Gets the end time of the punishment, in unix seconds. {@code 0} is used for a permanent punishment
+ *
+ * @return the end date seconds
+ */
+ default long getEndDateSeconds() {
+ Instant endDate = getEndDate();
+ return endDate.equals(Instant.MAX) ? 0L : endDate.getEpochSecond();
+ }
+
+ /**
+ * Convenience method to determine if this punishment is permanent
+ *
+ * @return true if this punishment is permanent, false otherwise
+ */
+ default boolean isPermanent() {
+ return Instant.MAX.equals(getEndDate());
+ }
+
+ /**
+ * Convenience method to determine if this punishment is temporary (opposite of {@link #isPermanent()})
+ *
+ * @return true if this punishment is temporary, false otherwise
+ */
+ default boolean isTemporary() {
+ return !isPermanent();
+ }
+
+ /**
+ * Convenience method to determine if this punishment has expired. If permanent, will always be {@code false}
+ *
+ * @return true if this is a temporary punishment which has expired
+ */
+ default boolean isExpired() {
+ if (isPermanent()) {
+ return false;
+ }
+ return Instant.now().compareTo(getEndDate()) > 0;
+ }
+
+ /**
+ * Enforces this punishment.
+ *
+ * For bans and mutes, this will kick players matching the punishment's
+ * victim. For mutes and warn, the players will be sent a warning message.
+ * Additionally for mutes, the player will be unable to chat (depending on the implementation)
+ * until the mute cache expires, at which point the database is re-queried for a mute.
+ *
+ * @return a future completed when enforcement has been conducted
+ */
+ CentralisedFuture> enforcePunishment();
+
+ /**
+ * Undoes and "unenforces" this punishment assuming it active and in the database.
+ * If the punishment was active then was removed, the future yields {@code true},
+ * else {@code false}.
+ *
+ * Unenforcement implies purging of this punishment from any local caches.
+ *
+ * @return a centralised future which yields {@code true} if this punishment
+ * existed and was removed and unenforced, {@code false} otherwise
+ */
+ CentralisedFuture undoPunishment();
+
+ /**
+ * Undoes this punishment assuming it is in the database.
+ * If the punishment was active then was removed, the future yields {@code true},
+ * else {@code false}.
+ *
+ * Most callers will want to use {@link #undoPunishment()} instead, which has the added effect
+ * of "unenforcing" the punishment. Unenforcement implies purging of this punishment from any local caches.
+ *
+ * @return a centralised future which yields {@code true} if this punishment
+ * existed and was removed, {@code false} otherwise
+ */
+ CentralisedFuture undoPunishmentWithoutUnenforcement();
+
+ /**
+ * "Unenforces" this punishment.
+ *
+ * This will update local punishment caches, such as the mute cache.
+ *
+ * @return a future completed when unenforcement has been conducted
+ */
+ CentralisedFuture> unenforcePunishment();
+
+ /**
+ * Whether this punishment is equal to another. The other punishment must represent the
+ * same punishment stored in the database.
+ *
+ * Implementations need only check {@link #getID()} since IDs must always be unique.
+ *
+ * @param object the other object
+ * @return true if the objects are equal, false otherwise
+ */
+ @Override
+ boolean equals(Object object);
+
+}
diff --git a/bans-api/src/main/java/space/arim/libertybans/api/PunishmentBase.java b/bans-api/src/main/java/space/arim/libertybans/api/punish/PunishmentBase.java
similarity index 81%
rename from bans-api/src/main/java/space/arim/libertybans/api/PunishmentBase.java
rename to bans-api/src/main/java/space/arim/libertybans/api/punish/PunishmentBase.java
index 08d2a8f40..a11ef9a20 100644
--- a/bans-api/src/main/java/space/arim/libertybans/api/PunishmentBase.java
+++ b/bans-api/src/main/java/space/arim/libertybans/api/punish/PunishmentBase.java
@@ -16,7 +16,12 @@
* along with LibertyBans-api. If not, see
* and navigate to version 3 of the GNU Affero General Public License.
*/
-package space.arim.libertybans.api;
+package space.arim.libertybans.api.punish;
+
+import space.arim.libertybans.api.Operator;
+import space.arim.libertybans.api.PunishmentType;
+import space.arim.libertybans.api.ServerScope;
+import space.arim.libertybans.api.Victim;
/**
* Base interface for {@link DraftPunishment} and {@link Punishment}
@@ -59,21 +64,6 @@ public interface PunishmentBase {
*
* @return the scope
*/
- Scope getScope();
-
- /**
- * Gets the start time, in unix seconds, of the punishment
- *
- * @return the start time
- */
- long getStart();
-
- /**
- * Gets the end time, in unix seconds, of the punishment.
- * 0 represents a permanent punishment.
- *
- * @return the end time
- */
- long getEnd();
+ ServerScope getScope();
}
diff --git a/bans-api/src/test/java/space/arim/libertybans/api/PunishmentTypeTest.java b/bans-api/src/main/java/space/arim/libertybans/api/punish/PunishmentDrafter.java
similarity index 71%
rename from bans-api/src/test/java/space/arim/libertybans/api/PunishmentTypeTest.java
rename to bans-api/src/main/java/space/arim/libertybans/api/punish/PunishmentDrafter.java
index 78beff99c..4c367bc5f 100644
--- a/bans-api/src/test/java/space/arim/libertybans/api/PunishmentTypeTest.java
+++ b/bans-api/src/main/java/space/arim/libertybans/api/punish/PunishmentDrafter.java
@@ -16,19 +16,21 @@
* along with LibertyBans-api. If not, see
* and navigate to version 3 of the GNU Affero General Public License.
*/
-package space.arim.libertybans.api;
+package space.arim.libertybans.api.punish;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-import org.junit.jupiter.api.Test;
-
-public class PunishmentTypeTest {
+/**
+ * Initial point for drafting punishments
+ *
+ * @author A248
+ *
+ */
+public interface PunishmentDrafter {
- @Test
- public void testOrdinals() {
- for (PunishmentType type : PunishmentType.values()) {
- assertEquals(type, PunishmentType.fromOrdinal(type.ordinal()));
- }
- }
+ /**
+ * Begins creating a draft punishment by returning a {@link DraftPunishmentBuilder}
+ *
+ * @return a draft punishment builder
+ */
+ DraftPunishmentBuilder draftBuilder();
}
diff --git a/bans-api/src/main/java/space/arim/libertybans/api/punish/package-info.java b/bans-api/src/main/java/space/arim/libertybans/api/punish/package-info.java
new file mode 100644
index 000000000..82182d565
--- /dev/null
+++ b/bans-api/src/main/java/space/arim/libertybans/api/punish/package-info.java
@@ -0,0 +1,11 @@
+/**
+ * Defines API for drafting punishments as well as punishments themselves.
+ *
+ * Active versus Historical Punishments
+ * An active punishment is neither expired nor undone.After being undone, a punishment is no longer active.
+ * After a temporary punishment expires, it is no longer active.
+ *
+ * 'Historial' punishments include both those active, those expired, and those undone. All punishments,
+ * regardless of whether they are active, are historical punishments.
+ */
+package space.arim.libertybans.api.punish;
\ No newline at end of file
diff --git a/bans-api/src/main/java/space/arim/libertybans/api/revoke/PunishmentRevoker.java b/bans-api/src/main/java/space/arim/libertybans/api/revoke/PunishmentRevoker.java
new file mode 100644
index 000000000..30b7a33d2
--- /dev/null
+++ b/bans-api/src/main/java/space/arim/libertybans/api/revoke/PunishmentRevoker.java
@@ -0,0 +1,70 @@
+/*
+ * LibertyBans-api
+ * Copyright © 2020 Anand Beh
+ *
+ * LibertyBans-api is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * LibertyBans-api 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with LibertyBans-api. If not, see
+ * and navigate to version 3 of the GNU Affero General Public License.
+ */
+package space.arim.libertybans.api.revoke;
+
+import space.arim.libertybans.api.PunishmentType;
+import space.arim.libertybans.api.Victim;
+import space.arim.libertybans.api.punish.Punishment;
+
+/**
+ * Agent of removing active punishments in accordance with constraints. For example, one victim
+ * cannot have more than 1 ban, therefore a ban may be undone if the victim is known.
+ *
+ * Only active punishments can be undone. Historical punishments cannot be removed. See
+ * {@link space.arim.libertybans.api.punish} for a description of active and historical punishments.
+ *
+ * Note that {@link Punishment#undoPunishment()} should be used instead of a revocation order if a punishment
+ * instance is already obtained.
+ *
+ * @author A248
+ *
+ */
+public interface PunishmentRevoker {
+
+ /**
+ * Gets a {@link RevocationOrder} to undo a punishment by its ID and type.
+ *
+ * @param id the id of the punishment to undo
+ * @param type the type of the punishment to undo
+ * @return a revocation order using the ID and type
+ */
+ RevocationOrder revokeByIdAndType(int id, PunishmentType type);
+
+ /**
+ * Gets a {@link RevocationOrder} to undo a punishment according to its ID.
+ *
+ * When the punishment type is known, {@link #revokeByIdAndType(int, PunishmentType)} should be used.
+ *
+ * @param id the id of the punishment to undo
+ * @return a revocation order using the ID
+ */
+ RevocationOrder revokeById(int id);
+
+ /**
+ * Gets a {@link RevocationOrder} to undo a punishment by its type and victim. This may only be used for singular punishments
+ * (bans and mutes), since it relies on the fact that a single victim cannot have more than 1 such punishment.
+ *
+ * @param type the punishment type, must be singular per {@link PunishmentType#isSingular()}
+ * @param victim the victim whose punishment to undo
+ * @return a revocation order using the type and victim
+ * @throws IllegalArgumentException if {@code type} is not singular (BAN or MUTE)
+ */
+ RevocationOrder revokeByTypeAndVictim(PunishmentType type, Victim victim);
+
+}
diff --git a/bans-api/src/main/java/space/arim/libertybans/api/revoke/RevocationOrder.java b/bans-api/src/main/java/space/arim/libertybans/api/revoke/RevocationOrder.java
new file mode 100644
index 000000000..f20b507e7
--- /dev/null
+++ b/bans-api/src/main/java/space/arim/libertybans/api/revoke/RevocationOrder.java
@@ -0,0 +1,107 @@
+/*
+ * LibertyBans-api
+ * Copyright © 2020 Anand Beh
+ *
+ * LibertyBans-api is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * LibertyBans-api 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with LibertyBans-api. If not, see
+ * and navigate to version 3 of the GNU Affero General Public License.
+ */
+package space.arim.libertybans.api.revoke;
+
+import space.arim.omnibus.util.concurrent.CentralisedFuture;
+
+import space.arim.libertybans.api.PunishmentType;
+import space.arim.libertybans.api.Victim;
+import space.arim.libertybans.api.punish.Punishment;
+
+/**
+ * An order to undo a punishment by some specific details. Obtained from {@link PunishmentRevoker}.
+ *
+ * See {@link space.arim.libertybans.api.punish} for a description of active and historical punishments.
+ *
+ * @author A248
+ *
+ */
+public interface RevocationOrder {
+
+ /**
+ * Gets the ID of the punishment which will be revoked, or {@code null} if no ID
+ * is known
+ *
+ * @return the ID of the punishment to be revoked, or null
+ */
+ Integer getID();
+
+ /**
+ * Gets the punishment type of the punishment which will be revoked, or {@code null}
+ * if the type is not known
+ *
+ * @return the type of the punishment to be revoked, or null
+ */
+ PunishmentType getType();
+
+ /**
+ * Gets the victim whose punishment will be revoked, or {@code null} if the victim
+ * is not known
+ *
+ * @return the victim, or null
+ */
+ Victim getVictim();
+
+ /**
+ * Revokes the punishment matching this revocation order, and "unenforces" it.
+ *
+ * If no active punishment in the database matched this revocation order, the future yields {@code false}.
+ *
+ * Unenforcement implies purging of this punishment from any local caches.
+ *
+ * @return a future which yields {@code true} if the punishment existed and has been revoked, {@code false} otherwise
+ */
+ CentralisedFuture undoPunishment();
+
+ /**
+ * Revokes the punishment matching this revocation order.
+ *
+ * If no active punishment in the database matched this revocation order, the future yields {@code false}.
+ *
+ * Most callers will want to use {@link #undoPunishment()} instead, which has the added effect
+ * of "unenforcing" the punishment. Unenforcement implies purging of this punishment from any local caches.
+ *
+ * @return a future which yields {@code true} if the punishment existed and has been revoked, {@code false} otherwise
+ */
+ CentralisedFuture undoPunishmentWithoutUnenforcement();
+
+ /**
+ * Revokes the punishment matching this revocation order, "unenforces" it, and gets the punishment revoked.
+ *
+ * If no active punishment in the database matched this revocation order, the future yields {@code null}.
+ *
+ * Unenforcement implies purging of this punishment from any local caches.
+ *
+ * @return a future which yields the punishment if it existed and was revoked, null otherwise
+ */
+ CentralisedFuture undoAndGetPunishment();
+
+ /**
+ * Revokes the punishment matching this revocation order, and gets the punishment revoked.
+ *
+ * If no active punishment in the database matched this revocation order, the future yields {@code null}.
+ *
+ * Most callers will want to use {@link #undoAndGetPunishment()} instead, which has the added effect
+ * of "unenforcing" the punishment. Unenforcement implies purging of this punishment from any local caches.
+ *
+ * @return a future which yields the punishment if it existed and was revoked, null otherwise
+ */
+ CentralisedFuture undoAndGetPunishmentWithoutUnenforcement();
+
+}
diff --git a/bans-api/src/main/java/space/arim/libertybans/api/PunishmentSelector.java b/bans-api/src/main/java/space/arim/libertybans/api/select/PunishmentSelector.java
similarity index 52%
rename from bans-api/src/main/java/space/arim/libertybans/api/PunishmentSelector.java
rename to bans-api/src/main/java/space/arim/libertybans/api/select/PunishmentSelector.java
index dac7b459f..f14217512 100644
--- a/bans-api/src/main/java/space/arim/libertybans/api/PunishmentSelector.java
+++ b/bans-api/src/main/java/space/arim/libertybans/api/select/PunishmentSelector.java
@@ -16,57 +16,37 @@
* along with LibertyBans-api. If not, see
* and navigate to version 3 of the GNU Affero General Public License.
*/
-package space.arim.libertybans.api;
+package space.arim.libertybans.api.select;
-import java.util.Set;
import java.util.UUID;
import space.arim.omnibus.util.concurrent.CentralisedFuture;
+import space.arim.libertybans.api.NetworkAddress;
+import space.arim.libertybans.api.PunishmentType;
+import space.arim.libertybans.api.punish.Punishment;
+
/**
* A manager for selecting punishments with specific details from the database. This,
- * along with {@link PunishmentSelection}, essentially provide an API for efficient
- * database queries.
+ * along with {@link SelectionOrder}, provide an API for efficient punishment queries.
*
- * Some methods refer to 'active' and/or 'historical' punishments. All punishments in the database
- * are historical, including those which have been undone. Only punishments which have not expired
- * and have not been undone are active.
+ * It is possible to select active and historical punishments. See
+ * {@link space.arim.libertybans.api.punish} for a description of active and historical punishments.
*
* @author A248
*
*/
public interface PunishmentSelector {
-
- /**
- * Gets the first punishment matching the given punishment selection, i.e. with the specified details.
- *
- * A punishment is said to match if it meets ALL of the following:
- * Its type matches that of the selection, or the selection's type is unspecified (null)
- * Its victim matches that of the selection, or the selection's victim is unspecified (null)
- * Its operator matches that of the selection, or the selection's operator is unspecified (null)
- * Its scope matches that of the selection, or the selection's scope is unspecified (null)
- *
- * @param selection the punishment selection whose details to match against
- * @return a future which yields the first punishment matching the selection, or {@code null} if none matched
- */
- CentralisedFuture getFirstSpecificPunishment(PunishmentSelection selection);
/**
- * Gets all punishments matching the given punishment selection, i.e. with the specified details.
- *
- * A punishment is said to match if it meets ALL of the following:
- * Its type matches that of the selection, or the selection's type is unspecified (null)
- * Its victim matches that of the selection, or the selection's victim is unspecified (null)
- * Its operator matches that of the selection, or the selection's operator is unspecified (null)
- * Its scope matches that of the selection, or the selection's scope is unspecified (null)
+ * Begins creating a selection order by returning a {@link SelectionOrderBuilder}
*
- * @param selection the punishment selection whose details to match against
- * @return a future which yields all punishments matching the selection, or an empty set if none matched
+ * @return a selection order builder
*/
- CentralisedFuture> getSpecificPunishments(PunishmentSelection selection);
+ SelectionOrderBuilder selectionBuilder();
/**
- * Gets a punishment matching a specific ID, if a punishment with such ID exists.
+ * Gets an active punishment matching a specific ID, if a punishment with such ID exists.
*
* When the type of the punishment is known, {@link #getActivePunishmentByIdAndType(int, PunishmentType)}
* should be preferred.
@@ -77,7 +57,7 @@ public interface PunishmentSelector {
CentralisedFuture getActivePunishmentById(int id);
/**
- * Gets a punishment matching a specific ID and type, if one exists with matching type and ID.
+ * Gets an active punishment matching a specific ID and type, if one exists with matching type and ID.
*
* Unlike {@link #getActivePunishmentById(int)}, this method may be more efficient when the type of
* the punishment is known beforehand.
@@ -95,22 +75,6 @@ public interface PunishmentSelector {
* @return a future which yields the historical punishment with the ID, or {@code null} if there is none
*/
CentralisedFuture getHistoricalPunishmentById(int id);
-
- /**
- * Gets all historical punishments applying to a specific {@link Victim}
- *
- * @param victim the victim
- * @return a future which yields all historical punishments applying to the victim, or an empty set
- */
- CentralisedFuture> getHistoryForVictim(Victim victim);
-
- /**
- * Gets all active punishments of a certain punishment type
- *
- * @param type the punishment type
- * @return a future which yields all active punishments matching the type, or an empty set
- */
- CentralisedFuture> getActivePunishmentsForType(PunishmentType type);
/**
* Gets the first punishment, of a certain type, which is applicable to a UUID and IP address,
@@ -121,17 +85,17 @@ public interface PunishmentSelector {
* banned, the player's UUID is banned, or the player has played on a banned IP and that IP is banned while strict
* address enforcement is enabled. That is, this method takes into account configuration options related to enforcement.
*
- * @param uuid the UUID
- * @param address the IP address
+ * @param uuid the player's UUID
+ * @param address the player's current address
* @param type the punishment type
* @return a future which yields the first applicable punishment for the id and address or {@code null} if there is none
*/
- CentralisedFuture getApplicablePunishment(UUID uuid, byte[] address, PunishmentType type);
+ CentralisedFuture getApplicablePunishment(UUID uuid, NetworkAddress address, PunishmentType type);
/**
* Gets a cached mute for an online player, including the player's UUID and address.
* The mute will be applicable to the combination of UUID and address, see
- * {@link #getApplicablePunishment(UUID, byte[], PunishmentType)} for a description of applicability.
+ * {@link #getApplicablePunishment(UUID, NetworkAddress, PunishmentType)} for a description of applicability.
*
* If the mute is cached, a completed future is returned. Else, an in-progress future is returned.
*
@@ -139,6 +103,6 @@ public interface PunishmentSelector {
* @param address the player's current address
* @return a future yielding either a cached value or the queried mute
*/
- CentralisedFuture getCachedMute(UUID uuid, byte[] address);
+ CentralisedFuture getCachedMute(UUID uuid, NetworkAddress address);
}
diff --git a/bans-api/src/main/java/space/arim/libertybans/api/select/SelectionOrder.java b/bans-api/src/main/java/space/arim/libertybans/api/select/SelectionOrder.java
new file mode 100644
index 000000000..037e1044e
--- /dev/null
+++ b/bans-api/src/main/java/space/arim/libertybans/api/select/SelectionOrder.java
@@ -0,0 +1,114 @@
+/*
+ * LibertyBans-api
+ * Copyright © 2020 Anand Beh
+ *
+ * LibertyBans-api is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * LibertyBans-api 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with LibertyBans-api. If not, see
+ * and navigate to version 3 of the GNU Affero General Public License.
+ */
+package space.arim.libertybans.api.select;
+
+import java.util.Set;
+
+import space.arim.omnibus.util.concurrent.CentralisedFuture;
+
+import space.arim.libertybans.api.Operator;
+import space.arim.libertybans.api.PunishmentType;
+import space.arim.libertybans.api.ServerScope;
+import space.arim.libertybans.api.Victim;
+import space.arim.libertybans.api.punish.Punishment;
+
+/**
+ * A selection which will match punishments in the database with certain details.
+ *
+ * To retrieve an instance, use the {@link SelectionOrderBuilder} obtained from
+ * {@link PunishmentSelector#selectionBuilder()}
+ *
+ * @author A248
+ *
+ */
+public interface SelectionOrder {
+
+ /**
+ * Gets the punishment type matched, or {@code null} to match all types
+ *
+ * @return the punishment type or {@code null} for all types
+ */
+ PunishmentType getType();
+
+ /**
+ * Gets the victim matched, or {@code null} to match all victims
+ *
+ * @return the victim or {@code null} for all victims
+ */
+ Victim getVictim();
+
+ /**
+ * Gets the operator matched, or {@code null} to match all operators
+ *
+ * @return the operator or {@code null} for all operators
+ */
+ Operator getOperator();
+
+ /**
+ * Gets the scope matched, or {@code null} to match all scopes
+ *
+ * @return the scope or {@code null} for all scopes
+ */
+ ServerScope getScope();
+
+ /**
+ * Whether this selection will match only active, non-expired punishments. If {@code false},
+ * then this selection may match any punishments, including undone or expired punishments.
+ *
+ * @return true to select only active and non-expired punishments, false to select all punishments
+ */
+ boolean selectActiveOnly();
+
+ /**
+ * Gets the first punishment matching this selection, i.e. with the specified details.
+ *
+ * A punishment is said to match if it meets ALL of the following:
+ * Its type matches that of this selection, or this selection's type is unspecified (null)
+ * Its victim matches that of this selection, or this selection's victim is unspecified (null)
+ * Its operator matches that of this selection, or this selection's operator is unspecified (null)
+ * Its scope matches that of this selection, or this selection's scope is unspecified (null)
+ *
+ * @return a future which yields the first punishment matching this selection, or {@code null} if none matched
+ */
+ CentralisedFuture getFirstSpecificPunishment();
+
+ /**
+ * Gets all punishments matching the given punishment selection, i.e. with the specified details.
+ *
+ * A punishment is said to match if it meets ALL of the following:
+ * Its type matches that of this selection, or this selection's type is unspecified (null)
+ * Its victim matches that of this selection, or this selection's victim is unspecified (null)
+ * Its operator matches that of this selection, or this selection's operator is unspecified (null)
+ * Its scope matches that of this selection, or this selection's scope is unspecified (null)
+ *
+ * @return a future which yields all punishments matching this selection, or an empty set if none matched
+ */
+ CentralisedFuture> getAllSpecificPunishments();
+
+ /**
+ * Whether this punishment selection is equal to another, i.e. if the other selection would match
+ * the same punishments in all circumstances.
+ *
+ * @param object the other object
+ * @return true if the objects are equal, false otherwise
+ */
+ @Override
+ boolean equals(Object object);
+
+}
diff --git a/bans-api/src/main/java/space/arim/libertybans/api/select/SelectionOrderBuilder.java b/bans-api/src/main/java/space/arim/libertybans/api/select/SelectionOrderBuilder.java
new file mode 100644
index 000000000..55f15438b
--- /dev/null
+++ b/bans-api/src/main/java/space/arim/libertybans/api/select/SelectionOrderBuilder.java
@@ -0,0 +1,114 @@
+/*
+ * LibertyBans-api
+ * Copyright © 2020 Anand Beh
+ *
+ * LibertyBans-api is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * LibertyBans-api 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with LibertyBans-api. If not, see
+ * and navigate to version 3 of the GNU Affero General Public License.
+ */
+package space.arim.libertybans.api.select;
+
+import space.arim.libertybans.api.Operator;
+import space.arim.libertybans.api.PunishmentType;
+import space.arim.libertybans.api.ServerScope;
+import space.arim.libertybans.api.Victim;
+
+/**
+ * Builder of {@link SelectionOrder}s
+ *
+ * @author A248
+ *
+ */
+public interface SelectionOrderBuilder {
+
+ /**
+ * Sets the punishment type matched. Use {@code null} to match all types,
+ * which is the default behaviour
+ *
+ * @param type the punishment type or {@code null} for all types
+ * @return this builder
+ */
+ SelectionOrderBuilder type(PunishmentType type);
+
+ /**
+ * Sets the victim matched. Use {@code null} to match all victims,
+ * which is the default behaviour
+ *
+ * @param victim the victim or {@code null} for all victims
+ * @return this builder
+ */
+ SelectionOrderBuilder victim(Victim victim);
+
+ /**
+ * Sets the operator matched. Use {@code null} to match all operators,
+ * which is the default behaviour
+ *
+ * @param operator the operator or {@code null} for all operators
+ * @return this builder
+ */
+ SelectionOrderBuilder operator(Operator operator);
+
+ /**
+ * Sets the scope matched. Use {@code null} to match all scopes,
+ * which is the default behaviour
+ *
+ * @param scope the scope or {@code null} for all scopes
+ * @return this builder
+ */
+ SelectionOrderBuilder scope(ServerScope scope);
+
+ /**
+ * Sets whether only active punishments should be matched. True by default,
+ * meaning only active punishments are selected.
+ *
+ * Active punishments are those not expired and not undone.
+ *
+ * @param selectActiveOnly whether to select active punishments only
+ * @return this builder
+ */
+ SelectionOrderBuilder selectActiveOnly(boolean selectActiveOnly);
+
+ /**
+ * Sets that only active punishments should be matched. Enabled by default,
+ * meaning only active punishments are selected.
+ *
+ * Active punishments are those not expired and not undone.
+ *
+ * @return this builder
+ */
+ default SelectionOrderBuilder selectActiveOnly() {
+ return selectActiveOnly(true);
+ }
+
+
+ /**
+ * Sets that all punishments, not just those active, should be matched. When this
+ * method is used, historical punishments and expired punishments are selected.
+ *
+ * Active punishments are those not expired and not undone.
+ *
+ * @return this builder
+ */
+ default SelectionOrderBuilder selectAll() {
+ return selectActiveOnly(false);
+ }
+
+ /**
+ * Builds a {@link SelectionOrder} from the details of this builder. May be used repeatedly
+ * without side effects.
+ *
+ * @return a selection order from this builder's details
+ */
+ SelectionOrder build();
+
+}
diff --git a/bans-api/src/test/java/space/arim/libertybans/api/VictimTest.java b/bans-api/src/test/java/space/arim/libertybans/api/VictimTest.java
deleted file mode 100644
index 750c26128..000000000
--- a/bans-api/src/test/java/space/arim/libertybans/api/VictimTest.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * LibertyBans-api
- * Copyright © 2020 Anand Beh
- *
- * LibertyBans-api is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * LibertyBans-api 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 Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with LibertyBans-api. If not, see
- * and navigate to version 3 of the GNU Affero General Public License.
- */
-package space.arim.libertybans.api;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-import org.junit.jupiter.api.Test;
-
-import space.arim.libertybans.api.Victim.VictimType;
-
-public class VictimTest {
-
- @Test
- public void testVictimTypeOrdinals() {
- for (VictimType type : VictimType.values()) {
- assertEquals(type, VictimType.fromOrdinal(type.ordinal()));
- }
- }
-
-}
diff --git a/bans-api/src/test/java/space/arim/libertybans/api/example/WikiExamples.java b/bans-api/src/test/java/space/arim/libertybans/api/example/WikiExamples.java
new file mode 100644
index 000000000..162d66557
--- /dev/null
+++ b/bans-api/src/test/java/space/arim/libertybans/api/example/WikiExamples.java
@@ -0,0 +1,98 @@
+/*
+ * LibertyBans-api
+ * Copyright © 2020 Anand Beh
+ *
+ * LibertyBans-api is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * LibertyBans-api 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with LibertyBans-api. If not, see
+ * and navigate to version 3 of the GNU Affero General Public License.
+ */
+package space.arim.libertybans.api.example;
+
+import java.util.Set;
+import java.util.UUID;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import space.arim.omnibus.Omnibus;
+import space.arim.omnibus.OmnibusProvider;
+import space.arim.omnibus.util.ThisClass;
+import space.arim.omnibus.util.concurrent.CentralisedFuture;
+
+import space.arim.libertybans.api.LibertyBans;
+import space.arim.libertybans.api.PlayerOperator;
+import space.arim.libertybans.api.PlayerVictim;
+import space.arim.libertybans.api.PunishmentType;
+import space.arim.libertybans.api.punish.DraftPunishment;
+import space.arim.libertybans.api.punish.Punishment;
+import space.arim.libertybans.api.punish.PunishmentDrafter;
+import space.arim.libertybans.api.revoke.PunishmentRevoker;
+import space.arim.libertybans.api.revoke.RevocationOrder;
+import space.arim.libertybans.api.select.PunishmentSelector;
+
+public class WikiExamples {
+
+ private final LibertyBans libertyBans = findLibertyBansInstance();
+
+ private static LibertyBans findLibertyBansInstance() {
+ Omnibus omnibus = OmnibusProvider.getOmnibus();
+ LibertyBans instance = omnibus.getRegistry().getProvider(LibertyBans.class);
+ if (instance == null) {
+ throw new IllegalStateException("LibertyBans not found");
+ }
+ return instance;
+ }
+
+ private final Logger logger = LoggerFactory.getLogger(ThisClass.get());
+
+ public void banPlayerUsingLibertyBans(UUID uuidToBan) {
+
+ PunishmentDrafter drafter = libertyBans.getDrafter();
+
+ DraftPunishment draftBan = drafter.draftBuilder().type(PunishmentType.BAN).victim(PlayerVictim.of(uuidToBan))
+ .reason("Because I said so").build();
+
+ draftBan.enactPunishment().thenAcceptSync((punishment) -> {
+
+ // In this example it is assumed you have a logger
+ // You should not copy and paste examples verbatim
+ if (punishment == null) {
+ logger.info("UUID {} is already banned", uuidToBan);
+ return;
+ }
+ logger.info("ID of the enacted punishment is {}", punishment.getID());
+ });
+ }
+
+ public CentralisedFuture> getMutesFrom(UUID staffMemberUuid) {
+ PunishmentSelector selector = libertyBans.getSelector();
+
+ return selector.selectionBuilder().operator(PlayerOperator.of(staffMemberUuid)).type(PunishmentType.MUTE)
+ .build().getAllSpecificPunishments();
+ }
+
+ public CentralisedFuture> revokeBanFor(UUID bannedPlayer) {
+ PunishmentRevoker revoker = libertyBans.getRevoker();
+
+ // Relies on the fact a single victim can only have 1 active ban
+ RevocationOrder revocationOrder = revoker.revokeByTypeAndVictim(PunishmentType.BAN, PlayerVictim.of(bannedPlayer));
+ return revocationOrder.undoPunishment().thenAccept((undone) -> {
+ if (undone) {
+ // ban existed and was undone
+ } else {
+ // there was no ban
+ }
+ });
+ }
+
+}
diff --git a/bans-bootstrap/pom.xml b/bans-bootstrap/pom.xml
index 9f39bcba9..7447f0d3f 100644
--- a/bans-bootstrap/pom.xml
+++ b/bans-bootstrap/pom.xml
@@ -6,7 +6,7 @@
space.arim.libertybans
bans-parent
- 0.1.4-SNAPSHOT
+ 0.2.0-SNAPSHOT
bans-bootstrap
@@ -35,19 +35,21 @@
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+
- space.arim.libertybans
- bans-api
+ org.junit.jupiter
+ junit-jupiter-api
-
org.junit.jupiter
- junit-jupiter-api
- test
+ junit-jupiter-params
\ No newline at end of file
diff --git a/bans-bootstrap/src/main/java/space/arim/libertybans/bootstrap/InternalDependency.java b/bans-bootstrap/src/main/java/space/arim/libertybans/bootstrap/InternalDependency.java
index a55c37bd9..6e201c8b5 100644
--- a/bans-bootstrap/src/main/java/space/arim/libertybans/bootstrap/InternalDependency.java
+++ b/bans-bootstrap/src/main/java/space/arim/libertybans/bootstrap/InternalDependency.java
@@ -20,7 +20,7 @@
import space.arim.libertybans.bootstrap.depend.Repository;
-public enum InternalDependency {
+enum InternalDependency {
HIKARICP("HikariCP", "com.zaxxer.hikari.HikariConfig", "hikaricp"),
@@ -30,10 +30,12 @@ public enum InternalDependency {
CAFFEINE("Caffeine", "com.github.benmanes.caffeine.cache.Caffeine", "caffeine"),
JDBCAESAR("jdbcaesar", Repositories.ARIM_LESSER_GPL3),
+ DAZZLECONF_CORE("dazzleconf-core", Repositories.ARIM_LESSER_GPL3),
+ DAZZLECONF_EXT_SNAKEYAML("dazzleconf-ext-snakeyaml", Repositories.ARIM_LESSER_GPL3),
ARIMAPI("arimapi", Repositories.ARIM_GPL3),
MOREPAPERLIB("morepaperlib", Repositories.ARIM_LESSER_GPL3),
- SELF("self", Repositories.ARIM_AFFERO_GPL3);
+ SELF_CORE("self-core", Repositories.ARIM_AFFERO_GPL3);
final String name;
final String clazz;
diff --git a/bans-bootstrap/src/main/java/space/arim/libertybans/bootstrap/LibertyBansLauncher.java b/bans-bootstrap/src/main/java/space/arim/libertybans/bootstrap/LibertyBansLauncher.java
index 7ec8a99ae..b139d7919 100644
--- a/bans-bootstrap/src/main/java/space/arim/libertybans/bootstrap/LibertyBansLauncher.java
+++ b/bans-bootstrap/src/main/java/space/arim/libertybans/bootstrap/LibertyBansLauncher.java
@@ -18,8 +18,10 @@
*/
package space.arim.libertybans.bootstrap;
+import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
+import java.io.InputStreamReader;
import java.io.UncheckedIOException;
import java.lang.reflect.InaccessibleObjectException;
import java.lang.reflect.InvocationTargetException;
@@ -29,7 +31,9 @@
import java.net.URLClassLoader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
+import java.util.ArrayList;
import java.util.EnumMap;
+import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
@@ -58,7 +62,7 @@ public LibertyBansLauncher(DependencyPlatform platform, Path folder, Executor ex
DependencyLoaderBuilder internalDepLoader = new DependencyLoaderBuilder()
.setExecutor(executor).setOutputDirectory(libsFolder.resolve("internal"));
- var internalDeps = addInternalDepsStart();
+ Map> internalDeps = addInternalDepsStart();
addApiDeps(apiDepLoader);
addInternalDepsFinish(internalDepLoader, internalDeps);
@@ -109,19 +113,28 @@ private static boolean classExists(String clazzName) {
private void warnRelocation(String libName, String clazzName) {
Class> libClass = forName(clazzName);
if (libClass != null) {
- String plugin = getPluginFor.apply(libClass);
- warn("Plugin '" + ((plugin == null) ? "Unknown" : plugin) + "' has shaded the library '" + libName
- + "' but did not relocate it. This may or may not pose any problems. "
+ String pluginName = getPluginFor.apply(libClass);
+ warn("Plugin '" + ((pluginName.isEmpty()) ? "Unknown" : pluginName) + "' has shaded the library '"
+ + libName + "' but did not relocate it. This may or may not pose any problems. "
+ "Contact the author of this plugin and tell them to relocate their dependencies.");
}
}
private Dependency readDependency0(String simpleDependencyName) {
URL url = getClass().getClassLoader().getResource("dependencies/" + simpleDependencyName);
- try (InputStream is = url.openStream()) {
+ try (InputStream inputStream = url.openStream();
+ InputStreamReader inputReader = new InputStreamReader(inputStream, StandardCharsets.US_ASCII);
+ BufferedReader buffReader = new BufferedReader(inputReader)) {
- String[] value = new String(is.readAllBytes(), StandardCharsets.US_ASCII).split("\n");
- return Dependency.of(value[0], value[1], value[2], value[3]);
+ List lines = new ArrayList<>();
+ String line;
+ while ((line = buffReader.readLine()) != null) {
+ lines.add(line);
+ }
+ if (lines.size() < 4) {
+ throw new IllegalArgumentException("Dependency file for " + simpleDependencyName + " is malformatted");
+ }
+ return Dependency.of(lines.get(0), lines.get(1), lines.get(2), lines.get(3));
} catch (IOException ex) {
throw new UncheckedIOException(ex);
@@ -138,12 +151,16 @@ private CompletableFuture readDependency(String simpleDependencyName
*
*/
private void addApiDeps(DependencyLoaderBuilder loader) {
+ CompletableFuture selfApi = readDependency("self-api");
if (!classExists("space.arim.omnibus.Omnibus")) {
- loader.addPair(readDependency0("omnibus"), Repositories.ARIM_GPL3);
+ loader.addPair(readDependency0("omnibus"), Repositories.ARIM_LESSER_GPL3);
}
if (!classExists("space.arim.uuidvault.api.UUIDVault")) {
loader.addPair(readDependency0("uuidvault"), Repositories.ARIM_LESSER_GPL3);
}
+ if (!skipSelfDependencies()) {
+ loader.addPair(selfApi.join(), Repositories.ARIM_AFFERO_GPL3);
+ }
}
/*
@@ -174,10 +191,18 @@ private void addInternalDepsFinish(DependencyLoaderBuilder loader,
if (internalDep.clazz != null) {
warnRelocation(internalDep.name, internalDep.clazz);
}
+ if (skipSelfDependencies() && internalDep == InternalDependency.SELF_CORE) {
+ continue;
+ }
loader.addPair(internalDeps.get(internalDep).join(), internalDep.repo);
}
}
+ /* Used in testing */
+ protected boolean skipSelfDependencies() {
+ return false;
+ }
+
// Must use System.err because we do not know whether the platform uses slf4j or JUL
private void warn(String message) {
System.err.println("[LibertyBans] " + message);
diff --git a/bans-bootstrap/src/main/resources/dependencies/arimapi b/bans-bootstrap/src/main/resources/dependencies/arimapi
index fdd331844..6658eab7e 100644
--- a/bans-bootstrap/src/main/resources/dependencies/arimapi
+++ b/bans-bootstrap/src/main/resources/dependencies/arimapi
@@ -1,4 +1,4 @@
space.arim.api
-arimapi-assemble-noplugin
-0.19.13-SNAPSHOT
-06ae2044af08804a1e7b4a3f69a864f4d8f6fb3cbe059435871179660985a88e944d5d9e89a83767cb0121cdc7e8e7243eb1038742b85acf692c7ea431634722
+arimapi-all
+${arimapi.version}
+743efbe65da49ddee948b7bbd1be616b89cdba4743a2ac3ee9363c6a3e09c6caf8aa469ced08674521ac262df0cd0df87521339e656e834f4ac194563bf8057d
diff --git a/bans-bootstrap/src/main/resources/dependencies/caffeine b/bans-bootstrap/src/main/resources/dependencies/caffeine
index 577226bec..c60852aff 100644
--- a/bans-bootstrap/src/main/resources/dependencies/caffeine
+++ b/bans-bootstrap/src/main/resources/dependencies/caffeine
@@ -1,4 +1,4 @@
com.github.ben-manes.caffeine
caffeine
-2.8.5
+${caffeine.version}
9351c0e57aefd58f468ad5eced9c828254e7d19692654eac81cfeba0d72d4d91f5021eaaad533d6c292553f0487a31f92d73b0ee711296ab76ba6d168056a5c6
diff --git a/bans-bootstrap/src/main/resources/dependencies/dazzleconf-core b/bans-bootstrap/src/main/resources/dependencies/dazzleconf-core
new file mode 100644
index 000000000..7616b70b1
--- /dev/null
+++ b/bans-bootstrap/src/main/resources/dependencies/dazzleconf-core
@@ -0,0 +1,4 @@
+space.arim.dazzleconf
+dazzleconf-core
+${dazzleconf.version}
+f7bd1349151f7bd6f2fd1fbc7474cd841095e54b0ca143e2e4d0dde2ba5c22da0e143dc136a5a37c4801d4a2b2763ad01be846e83cb947d393954e54fcdec339
diff --git a/bans-bootstrap/src/main/resources/dependencies/dazzleconf-ext-snakeyaml b/bans-bootstrap/src/main/resources/dependencies/dazzleconf-ext-snakeyaml
new file mode 100644
index 000000000..d4e8e08f4
--- /dev/null
+++ b/bans-bootstrap/src/main/resources/dependencies/dazzleconf-ext-snakeyaml
@@ -0,0 +1,4 @@
+space.arim.dazzleconf
+dazzleconf-ext-snakeyaml
+${dazzleconf.version}
+f90e0550309e4e6e50ad4589d9b00b37a0454725d914596b5b2899c1dffda2854758f54301bb74e149ca2aad4ab142a218302eff2f1d989f5189810a3709bb8d
diff --git a/bans-bootstrap/src/main/resources/dependencies/hikaricp b/bans-bootstrap/src/main/resources/dependencies/hikaricp
index 712f7586a..9c90f2cc7 100644
--- a/bans-bootstrap/src/main/resources/dependencies/hikaricp
+++ b/bans-bootstrap/src/main/resources/dependencies/hikaricp
@@ -1,4 +1,4 @@
com.zaxxer
HikariCP
-3.4.5
+${hikari.version}
4e70e08544e199eed09f5f3681ab5a39681d8d6eb6c9ca2cb6a78535aedbcc66b4292330bbcb40bfb8bf8a6f284d013e55a039542ffc85222d6ac26f816eafd3
diff --git a/bans-bootstrap/src/main/resources/dependencies/hsqldb b/bans-bootstrap/src/main/resources/dependencies/hsqldb
index 407e29cbe..07a35eba5 100644
--- a/bans-bootstrap/src/main/resources/dependencies/hsqldb
+++ b/bans-bootstrap/src/main/resources/dependencies/hsqldb
@@ -1,4 +1,4 @@
org.hsqldb
hsqldb
-2.5.1
+${hsqldb.version}
5539ef60987d6bd801c4ced80ede3138578307122be92dedbf2c6a48ea58db6e5e6c064d36de6f1fc0ccb6991213eb801ec036957edde48a358657e0cb8d4e62
diff --git a/bans-bootstrap/src/main/resources/dependencies/jdbcaesar b/bans-bootstrap/src/main/resources/dependencies/jdbcaesar
index 909827fe0..7768d13dd 100644
--- a/bans-bootstrap/src/main/resources/dependencies/jdbcaesar
+++ b/bans-bootstrap/src/main/resources/dependencies/jdbcaesar
@@ -1,4 +1,4 @@
space.arim.jdbcaesar
jdbcaesar
-0.5.0
+${jdbcaesar.version}
122f9bcf21c0211c7168d9584c3cea6dbd7640c00a844a26e101980fe33f2bff355edd79337135f73352a3a2fe975c01d9aeb31a8bbe740b47507eac81c0445a
diff --git a/bans-bootstrap/src/main/resources/dependencies/mariadb-connector b/bans-bootstrap/src/main/resources/dependencies/mariadb-connector
index b00f9dab7..e02dc235c 100644
--- a/bans-bootstrap/src/main/resources/dependencies/mariadb-connector
+++ b/bans-bootstrap/src/main/resources/dependencies/mariadb-connector
@@ -1,4 +1,4 @@
org.mariadb.jdbc
mariadb-java-client
-2.6.2
+${mariadb-connector.version}
d96c4529e5547ea02a0d0b66c518741a63e427156b422bcb6d879b48e42b107adf9186d67f4cf00e1a99fcb6190e398a893d3623c841e2dedf0b0781f0daa363
diff --git a/bans-bootstrap/src/main/resources/dependencies/morepaperlib b/bans-bootstrap/src/main/resources/dependencies/morepaperlib
index d37877c54..d4c1e5599 100644
--- a/bans-bootstrap/src/main/resources/dependencies/morepaperlib
+++ b/bans-bootstrap/src/main/resources/dependencies/morepaperlib
@@ -1,4 +1,4 @@
space.arim.morepaperlib
morepaperlib
-0.1.0-SNAPSHOT
+${morepaperlib.version}
289bd60f519d1151ca6957a839339a168828cd983f30321ccc785f48223b5282b0bd6f6516871c0424948373ddd0e3cda824f6ce0e48ebc3593d4890101752a6
diff --git a/bans-bootstrap/src/main/resources/dependencies/omnibus b/bans-bootstrap/src/main/resources/dependencies/omnibus
index 88931b12b..190057a00 100644
--- a/bans-bootstrap/src/main/resources/dependencies/omnibus
+++ b/bans-bootstrap/src/main/resources/dependencies/omnibus
@@ -1,4 +1,4 @@
space.arim.omnibus
-omnibus-all-shaded
-0.15.5-SNAPSHOT
-3067ddde771f4d80e54f4ec036b6c7e24f79596a7028134d1248ddae6aae700748ea60b99f38f9ff7c57122c5cdb286c0417c3025328c932a04fe56b64a24d14
+omnibus
+${omnibus.version}
+93b85e4bd8bca9de875af176cac546153405f0018382ad2de061f633796e2277632bd0b3d0db0abd6f32313bab91474eebdd40a5c8a25f4a789926524c11f856
diff --git a/bans-bootstrap/src/main/resources/dependencies/self b/bans-bootstrap/src/main/resources/dependencies/self
deleted file mode 100644
index 0987e2522..000000000
--- a/bans-bootstrap/src/main/resources/dependencies/self
+++ /dev/null
@@ -1,4 +0,0 @@
-space.arim.libertybans
-bans-dl
-${project.version}
-abd2f6503a35d4c7d21b22fa21868154f736ac212af19e3cdb89b83803a8074217399bc6527205720c9d1dd2b207b7b69aec4a7e823bd20880d4c1cfdf9b5d32
diff --git a/bans-bootstrap/src/main/resources/dependencies/self-api b/bans-bootstrap/src/main/resources/dependencies/self-api
new file mode 100644
index 000000000..42ab4ea2f
--- /dev/null
+++ b/bans-bootstrap/src/main/resources/dependencies/self-api
@@ -0,0 +1,4 @@
+space.arim.libertybans
+bans-api
+${project.version}
+7372160e3521b3d72a5a59248b5c67ea23f1c86c9b925229e041848c65355fcaf3e5907fb6d77252b2d26244a8d6b5fc759c676a480a30ce500d66809995083e
diff --git a/bans-bootstrap/src/main/resources/dependencies/self-core b/bans-bootstrap/src/main/resources/dependencies/self-core
new file mode 100644
index 000000000..6190e191a
--- /dev/null
+++ b/bans-bootstrap/src/main/resources/dependencies/self-core
@@ -0,0 +1,4 @@
+space.arim.libertybans
+bans-dl
+${project.version}
+e63a68bc4a1568dd5767ac739dbc5c1ddf95b864082b364989032e626889cddea034bdc3c59d3ca786b877180d77ab5f5cace062a04e1a4a517c91e6d0139ac4
diff --git a/bans-bootstrap/src/main/resources/dependencies/slf4j-api b/bans-bootstrap/src/main/resources/dependencies/slf4j-api
index c0d894aaf..78b206013 100644
--- a/bans-bootstrap/src/main/resources/dependencies/slf4j-api
+++ b/bans-bootstrap/src/main/resources/dependencies/slf4j-api
@@ -1,4 +1,4 @@
org.slf4j
slf4j-api
-1.7.30
+${slf4j.version}
e5435852569dda596ba46138af8ee9c4ecba8a7a43f4f1e7897aeb4430523a0f037088a7b63877df5734578f19d331f03d7b0f32d5ae6c425df211947b3e6173
diff --git a/bans-bootstrap/src/main/resources/dependencies/slf4j-jdk14 b/bans-bootstrap/src/main/resources/dependencies/slf4j-jdk14
index 081b787e1..553064f0d 100644
--- a/bans-bootstrap/src/main/resources/dependencies/slf4j-jdk14
+++ b/bans-bootstrap/src/main/resources/dependencies/slf4j-jdk14
@@ -1,4 +1,4 @@
org.slf4j
slf4j-jdk14
-1.7.30
+${slf4j.version}
e6d624b0c891809ae6d62fb2785b1562f01ea92048ef39df0b2cd1aa607135c62eaa4c1964c0a399c7a8f329be55edff6cf572a910af4547d82ae43ff3c7727a
diff --git a/bans-bootstrap/src/main/resources/dependencies/uuidvault b/bans-bootstrap/src/main/resources/dependencies/uuidvault
index cf6978dc5..a1b2adfbc 100644
--- a/bans-bootstrap/src/main/resources/dependencies/uuidvault
+++ b/bans-bootstrap/src/main/resources/dependencies/uuidvault
@@ -1,4 +1,4 @@
space.arim.uuidvault
assemble
-0.5.2-SNAPSHOT
-a91af1c02b604b4f16273ab0a76cefa9c24ea1cc8c06f1c383b8ae608ddd405661bd9ebd4c7f8cbf37dbd68e84e81755471cc441d8b73215a6c517786a3425f5
+${uuidvault.version}
+fb4ba1ffa24d1f0bba8ebbc2de93849d53278cc6b54cc686559cf1f53d4155c5bb0d7688072bdc6bdf65439a27adc51ae8c16d0bacff93630c7f2ed1f8a22106
diff --git a/bans-bootstrap/src/test/java/space/arim/libertybans/bootstrap/DownloadDependenciesIT.java b/bans-bootstrap/src/test/java/space/arim/libertybans/bootstrap/DownloadDependenciesIT.java
new file mode 100644
index 000000000..28300021b
--- /dev/null
+++ b/bans-bootstrap/src/test/java/space/arim/libertybans/bootstrap/DownloadDependenciesIT.java
@@ -0,0 +1,72 @@
+/*
+ * LibertyBans-bootstrap
+ * Copyright © 2020 Anand Beh
+ *
+ * LibertyBans-bootstrap is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * LibertyBans-bootstrap 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with LibertyBans-bootstrap. If not, see
+ * and navigate to version 3 of the GNU Affero General Public License.
+ */
+package space.arim.libertybans.bootstrap;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.nio.file.Path;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.io.TempDir;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+
+public class DownloadDependenciesIT {
+
+ @TempDir
+ public Path folder;
+
+ private ExecutorService executor;
+
+ @BeforeEach
+ public void setup() {
+ executor = Executors.newCachedThreadPool();
+ }
+
+ @ParameterizedTest
+ @EnumSource(DependencyPlatform.class)
+ public void testDownloadAllDependencies(DependencyPlatform platform) {
+ LibertyBansLauncher launcher = new LibertyBansLauncher(platform, folder, executor, (clazz) -> "") {
+ @Override
+ protected boolean addUrlsToExternalClassLoader(ClassLoader apiClassLoader, Path[] paths) {
+ for (Path path : paths) {
+ System.out.println("Downloaded to " + path.getFileName());
+ }
+ return true;
+ }
+ @Override
+ protected boolean skipSelfDependencies() {
+ return true;
+ }
+ };
+ ClassLoader classLoader = launcher.attemptLaunch().join();
+ assertNotNull(classLoader, "Failed to download dependencies");
+ }
+
+ @AfterEach
+ public void tearDown() throws InterruptedException {
+ executor.shutdown();
+ executor.awaitTermination(3, TimeUnit.SECONDS);
+ }
+
+}
diff --git a/bans-core/pom.xml b/bans-core/pom.xml
index ae0cbbcaa..5c3c48ba2 100644
--- a/bans-core/pom.xml
+++ b/bans-core/pom.xml
@@ -6,7 +6,7 @@
space.arim.libertybans
bans-parent
- 0.1.4-SNAPSHOT
+ 0.2.0-SNAPSHOT
bans-core
@@ -26,14 +26,14 @@
space.arim.libertybans
bans-bootstrap
-
- space.arim.libertybans
- bans-database
-
space.arim.jdbcaesar
jdbcaesar
+
+ space.arim.dazzleconf
+ dazzleconf-ext-snakeyaml
+
space.arim.api
arimapi-all
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/LibertyBansCore.java b/bans-core/src/main/java/space/arim/libertybans/core/LibertyBansCore.java
index f9a8ba12e..f1770d5fb 100644
--- a/bans-core/src/main/java/space/arim/libertybans/core/LibertyBansCore.java
+++ b/bans-core/src/main/java/space/arim/libertybans/core/LibertyBansCore.java
@@ -19,29 +19,30 @@
package space.arim.libertybans.core;
import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.TimeUnit;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import java.util.concurrent.ThreadFactory;
import space.arim.omnibus.Omnibus;
-import space.arim.omnibus.util.ThisClass;
+import space.arim.omnibus.util.concurrent.CentralisedFuture;
+import space.arim.omnibus.util.concurrent.EnhancedExecutor;
import space.arim.omnibus.util.concurrent.FactoryOfTheFuture;
import space.arim.libertybans.api.LibertyBans;
+import space.arim.libertybans.api.punish.PunishmentDrafter;
+import space.arim.libertybans.api.revoke.PunishmentRevoker;
import space.arim.libertybans.core.commands.Commands;
import space.arim.libertybans.core.config.Configs;
import space.arim.libertybans.core.config.Formatter;
+import space.arim.libertybans.core.config.MainConfig;
+import space.arim.libertybans.core.config.MessagesConfig;
import space.arim.libertybans.core.database.DatabaseManager;
import space.arim.libertybans.core.database.Database;
import space.arim.libertybans.core.env.AbstractEnv;
import space.arim.libertybans.core.env.EnvironmentManager;
+import space.arim.libertybans.core.punish.EnforcementCenter;
+import space.arim.libertybans.core.punish.Scoper;
import space.arim.libertybans.core.selector.MuteCacher;
import space.arim.libertybans.core.selector.Selector;
-import space.arim.libertybans.core.uuid.UUIDMaster;
+import space.arim.libertybans.core.uuid.UUIDManager;
public class LibertyBansCore implements LibertyBans, Part {
@@ -52,23 +53,17 @@ public class LibertyBansCore implements LibertyBans, Part {
private final Resources resources;
private final Configs configs;
private final DatabaseManager databaseManager;
- private final UUIDMaster uuidMaster;
+ private final UUIDManager uuidMaster;
private final EnvironmentManager envManager;
+ private final EnforcementCenter enforcement;
private final Selector selector;
private final MuteCacher cacher;
- private final Enactor enactor;
- private final Enforcer enforcer;
private final Scoper scoper;
private final Formatter formatter;
private final Commands commands;
- /** Delayed shutdown hooks, must be synchronized on before access */
- private final List delayedShutdownHooks = new ArrayList<>();
-
- private static final Logger logger = LoggerFactory.getLogger(ThisClass.get());
-
public LibertyBansCore(Omnibus omnibus,
Path folder, AbstractEnv environment) {
this.omnibus = omnibus;
@@ -76,15 +71,14 @@ public LibertyBansCore(Omnibus omnibus,
this.environment = environment;
resources = new Resources(this);
- configs = new Configs(this);
+ configs = new Configs(folder);
databaseManager = new DatabaseManager(this);
- uuidMaster = new UUIDMaster(this);
+ uuidMaster = new UUIDManager(this);
envManager = new EnvironmentManager(this);
selector = new Selector(this);
cacher = new MuteCacher(this);
- enactor = new Enactor(this);
- enforcer = new Enforcer(this);
+ enforcement = new EnforcementCenter(this);
scoper = new Scoper();
formatter = new Formatter(this);
@@ -93,39 +87,31 @@ public LibertyBansCore(Omnibus omnibus,
@Override
public void startup() {
- getResources().startup();
+ resources.startup();
getConfigs().startup();
- getDatabaseManager().startup();
- getUUIDMaster().startup();
+ databaseManager.startup();
+ uuidMaster.startup();
envManager.startup();
}
@Override
public void restart() {
envManager.shutdown();
- getResources().restart();
- getUUIDMaster().restart();
+ resources.restart();
+ uuidMaster.restart();
getConfigs().restart();
- getDatabaseManager().restart();
+ databaseManager.restart();
envManager.startup();
}
@Override
public void shutdown() {
envManager.shutdown();
- getResources().shutdown();
- getUUIDMaster().shutdown();
+ uuidMaster.shutdown();
getConfigs().shutdown();
- getDatabaseManager().shutdown();
+ databaseManager.shutdown();
- // 4 seconds should be sufficient
- CompletableFuture.runAsync(() -> {
- synchronized (delayedShutdownHooks) {
- for (Runnable hook : delayedShutdownHooks) {
- hook.run();
- }
- }
- }, CompletableFuture.delayedExecutor(4, TimeUnit.SECONDS)).join();
+ resources.shutdown();
}
public Path getFolder() {
@@ -143,17 +129,25 @@ public Omnibus getOmnibus() {
@Override
public FactoryOfTheFuture getFuturesFactory() {
- return getResources().getFuturesFactory();
+ return resources.getFuturesFactory();
}
- public Resources getResources() {
- return resources;
+ public EnhancedExecutor getEnhancedExecutor() {
+ return resources.getEnhancedExecutor();
}
public Configs getConfigs() {
return configs;
}
+ public MainConfig getMainConfig() {
+ return getConfigs().getMainConfig();
+ }
+
+ public MessagesConfig getMessagesConfig() {
+ return getConfigs().getMessagesConfig();
+ }
+
@Override
public Database getDatabase() {
return databaseManager.getCurrentDatabase();
@@ -163,7 +157,7 @@ public DatabaseManager getDatabaseManager() {
return databaseManager;
}
- public UUIDMaster getUUIDMaster() {
+ public UUIDManager getUUIDMaster() {
return uuidMaster;
}
@@ -177,20 +171,25 @@ public MuteCacher getMuteCacher() {
}
@Override
- public Enactor getEnactor() {
- return enactor;
+ public PunishmentDrafter getDrafter() {
+ return enforcement;
}
+ public EnforcementCenter getEnforcementCenter() {
+ return enforcement;
+ }
+
@Override
- public Enforcer getEnforcer() {
- return enforcer;
+ public PunishmentRevoker getRevoker() {
+ return enforcement.getRevoker();
}
-
+
@Override
public Scoper getScopeManager() {
return scoper;
}
+ @Override
public Formatter getFormatter() {
return formatter;
}
@@ -199,16 +198,12 @@ public Commands getCommands() {
return commands;
}
- public void addDelayedShutdownHook(Runnable hook) {
- synchronized (delayedShutdownHooks) {
- delayedShutdownHooks.add(hook);
- }
+ public void postFuture(CentralisedFuture> future) {
+ resources.postFuture(future);
}
- public void debugFuture(@SuppressWarnings("unused") T value, Throwable ex) {
- if (ex != null) {
- logger.warn("Miscellaneous issue executing asynchronous computation", ex);
- }
+ public ThreadFactory newThreadFactory(String component) {
+ return new ThreadFactoryImpl("LibertyBans-" + component + "-");
}
}
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/Resources.java b/bans-core/src/main/java/space/arim/libertybans/core/Resources.java
index ea730d624..fd0bd4b3e 100644
--- a/bans-core/src/main/java/space/arim/libertybans/core/Resources.java
+++ b/bans-core/src/main/java/space/arim/libertybans/core/Resources.java
@@ -18,52 +18,55 @@
*/
package space.arim.libertybans.core;
-import space.arim.omnibus.resourcer.ResourceHook;
-import space.arim.omnibus.resourcer.Resourcer;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import space.arim.omnibus.util.concurrent.CentralisedFuture;
import space.arim.omnibus.util.concurrent.EnhancedExecutor;
import space.arim.omnibus.util.concurrent.FactoryOfTheFuture;
-import space.arim.api.env.PlatformHandle;
+import space.arim.libertybans.bootstrap.ShutdownException;
-public class Resources implements Part {
+class Resources implements Part {
private final LibertyBansCore core;
- private volatile HookBundle bundle;
+ private FactoryOfTheFuture futuresFactory;
+ private EnhancedExecutor enhancedExecutor;
+
+ /**
+ * Awaits the execution of all independent asynchronous execution chains.
+ * Ensures all plugin operations can be shutdown.
+ */
+ private final ExecutorService universalJoiner;
Resources(LibertyBansCore core) {
this.core = core;
+ universalJoiner = Executors.newFixedThreadPool(1, core.newThreadFactory("Joiner"));
}
FactoryOfTheFuture getFuturesFactory() {
- return bundle.futuresFactory.getResource();
+ return futuresFactory;
}
- public EnhancedExecutor getEnhancedExecutor() {
- return bundle.enhancedExecutor.getResource();
- }
-
- private Resourcer getResourcer() {
- return core.getOmnibus().getResourcer();
+ synchronized EnhancedExecutor getEnhancedExecutor() {
+ if (enhancedExecutor == null) {
+ enhancedExecutor = core.getEnvironment().getPlatformHandle().createEnhancedExecutor();
+ }
+ return enhancedExecutor;
}
- private static class HookBundle {
- final ResourceHook futuresFactory;
- final ResourceHook enhancedExecutor;
-
- HookBundle(ResourceHook futuresFactory, ResourceHook enhancedExecutor) {
- this.futuresFactory = futuresFactory;
- this.enhancedExecutor = enhancedExecutor;
- }
+ void postFuture(CentralisedFuture> future) {
+ universalJoiner.execute(future::join);
}
@Override
public void startup() {
- Resourcer resourcer = getResourcer();
- PlatformHandle handle = core.getEnvironment().getPlatformHandle();
- ResourceHook futuresFactory = handle.hookPlatformResource(resourcer, FactoryOfTheFuture.class);
- ResourceHook enhancedExecutor = handle.hookPlatformResource(resourcer, EnhancedExecutor.class);
- bundle = new HookBundle(futuresFactory, enhancedExecutor);
+ futuresFactory = core.getEnvironment().getPlatformHandle().createFuturesFactory();
}
@Override
@@ -71,13 +74,41 @@ public void restart() {}
@Override
public void shutdown() {
- final HookBundle bundle = this.bundle;
- Resourcer resourcer = getResourcer();
- core.addDelayedShutdownHook(() -> {
- resourcer.unhookUsage(bundle.futuresFactory);
- resourcer.unhookUsage(bundle.enhancedExecutor);
- });
- this.bundle = null;
+ universalJoiner.shutdown();
+ /*
+ * On Bukkit, this prevents deadlocks.
+ *
+ * By awaiting termination through the futures factory, the managed wait
+ * implementation breaks dependencies arising from tasks needing the main thread
+ * The main thread executes this shutdown() method, so if it is simply blocked,
+ * tasks depending on it cannot complete.
+ *
+ * On any platform other than Bukkit, this is uninmportant.
+ */
+ boolean termination = futuresFactory.supplyAsync(() -> {
+ try {
+ return universalJoiner.awaitTermination(6L, TimeUnit.SECONDS);
+ } catch (InterruptedException ex) {
+ Thread.currentThread().interrupt();
+ getLogger().warn("Failed to shutdown all chains of asynchronous execution (Interrupted)", ex);
+ return true;
+ }
+ }).join();
+
+ if (!termination) {
+ getLogger().warn("Failed to shutdown all chains of asynchronous execution");
+ }
+ if (futuresFactory instanceof AutoCloseable) {
+ try {
+ ((AutoCloseable) futuresFactory).close();
+ } catch (Exception ex) {
+ throw new ShutdownException("Cannot shutdown factory of the future", ex);
+ }
+ }
+ }
+
+ private Logger getLogger() {
+ return LoggerFactory.getLogger(getClass());
}
}
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/SecurePunishment.java b/bans-core/src/main/java/space/arim/libertybans/core/SecurePunishment.java
deleted file mode 100644
index cfba05434..000000000
--- a/bans-core/src/main/java/space/arim/libertybans/core/SecurePunishment.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * LibertyBans-core
- * Copyright © 2020 Anand Beh
- *
- * LibertyBans-core is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * LibertyBans-core 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 Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with LibertyBans-core. If not, see
- * and navigate to version 3 of the GNU Affero General Public License.
- */
-package space.arim.libertybans.core;
-
-import space.arim.libertybans.api.AbstractPunishment;
-import space.arim.libertybans.api.Operator;
-import space.arim.libertybans.api.Punishment;
-import space.arim.libertybans.api.PunishmentType;
-import space.arim.libertybans.api.Scope;
-import space.arim.libertybans.api.Victim;
-
-public class SecurePunishment extends AbstractPunishment implements Punishment {
-
- private final int id;
-
- public SecurePunishment(int id, PunishmentType type, Victim victim, Operator operator, String reason, Scope scope,
- long start, long end) {
- super(type, victim, operator, reason, scope, start, end);
- this.id = id;
- }
-
- @Override
- public int getID() {
- return id;
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + id;
- return result;
- }
-
- @Override
- public boolean equals(Object object) {
- return this == object || object instanceof SecurePunishment && id == ((SecurePunishment) object).id;
- }
-
-}
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/ThreadFactoryImpl.java b/bans-core/src/main/java/space/arim/libertybans/core/ThreadFactoryImpl.java
new file mode 100644
index 000000000..75f0db68a
--- /dev/null
+++ b/bans-core/src/main/java/space/arim/libertybans/core/ThreadFactoryImpl.java
@@ -0,0 +1,50 @@
+/*
+ * LibertyBans-core
+ * Copyright © 2020 Anand Beh
+ *
+ * LibertyBans-core is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * LibertyBans-core 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with LibertyBans-core. If not, see
+ * and navigate to version 3 of the GNU Affero General Public License.
+ */
+package space.arim.libertybans.core;
+
+import java.util.concurrent.ThreadFactory;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import space.arim.omnibus.util.ThisClass;
+
+class ThreadFactoryImpl implements ThreadFactory {
+
+ private final String prefix;
+ private int threadId;
+
+ private static final Logger logger = LoggerFactory.getLogger(ThisClass.get());
+
+ ThreadFactoryImpl(String prefix) {
+ this.prefix = prefix;
+ }
+
+ private synchronized int nextId() {
+ return ++threadId;
+ }
+
+ @Override
+ public Thread newThread(Runnable r) {
+ String name = prefix + nextId();
+ logger.debug("Spawning new thread {}", name);
+ return new Thread(r, name);
+ }
+
+}
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/commands/AbstractCommandExecution.java b/bans-core/src/main/java/space/arim/libertybans/core/commands/AbstractCommandExecution.java
new file mode 100644
index 000000000..4f1c8f523
--- /dev/null
+++ b/bans-core/src/main/java/space/arim/libertybans/core/commands/AbstractCommandExecution.java
@@ -0,0 +1,41 @@
+/*
+ * LibertyBans-core
+ * Copyright © 2020 Anand Beh
+ *
+ * LibertyBans-core is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * LibertyBans-core 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with LibertyBans-core. If not, see
+ * and navigate to version 3 of the GNU Affero General Public License.
+ */
+package space.arim.libertybans.core.commands;
+
+import space.arim.libertybans.core.env.CmdSender;
+
+abstract class AbstractCommandExecution implements CommandExecution {
+
+ private final CmdSender sender;
+ private final CommandPackage command;
+
+ AbstractCommandExecution(CmdSender sender, CommandPackage command) {
+ this.sender = sender;
+ this.command = command;
+ }
+
+ CmdSender sender() {
+ return sender;
+ }
+
+ CommandPackage command() {
+ return command;
+ }
+
+}
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/commands/AbstractSubCommandGroup.java b/bans-core/src/main/java/space/arim/libertybans/core/commands/AbstractSubCommandGroup.java
index 90d9d9f72..d85136e76 100644
--- a/bans-core/src/main/java/space/arim/libertybans/core/commands/AbstractSubCommandGroup.java
+++ b/bans-core/src/main/java/space/arim/libertybans/core/commands/AbstractSubCommandGroup.java
@@ -19,12 +19,14 @@
package space.arim.libertybans.core.commands;
import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
import space.arim.omnibus.util.concurrent.CentralisedFuture;
-import space.arim.api.configure.ConfigAccessor;
-
import space.arim.libertybans.core.LibertyBansCore;
+import space.arim.libertybans.core.config.MainConfig;
+import space.arim.libertybans.core.config.MessagesConfig;
public abstract class AbstractSubCommandGroup implements SubCommandGroup {
@@ -35,9 +37,17 @@ public abstract class AbstractSubCommandGroup implements SubCommandGroup {
*/
private final Set matches;
- AbstractSubCommandGroup(Commands commands, String...matches) {
+ AbstractSubCommandGroup(Commands commands, Set matches) {
this.commands = commands;
- this.matches = Set.of(matches);
+ this.matches = Set.copyOf(matches);
+ }
+
+ AbstractSubCommandGroup(Commands commands, String...matches) {
+ this(commands, Set.of(matches));
+ }
+
+ AbstractSubCommandGroup(Commands commands, Stream matches) {
+ this(commands, matches.collect(Collectors.toUnmodifiableSet()));
}
@Override
@@ -52,19 +62,19 @@ public boolean matches(String arg) {
*/
CentralisedFuture completedFuture(T value) {
- return commands.core.getFuturesFactory().completedFuture(value);
+ return core().getFuturesFactory().completedFuture(value);
}
LibertyBansCore core() {
return commands.core;
}
- ConfigAccessor config() {
- return commands.core.getConfigs().getConfig();
+ MainConfig config() {
+ return core().getConfigs().getMainConfig();
}
- ConfigAccessor messages() {
- return commands.core.getConfigs().getMessages();
+ MessagesConfig messages() {
+ return core().getMessagesConfig();
}
}
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/commands/AdminCommands.java b/bans-core/src/main/java/space/arim/libertybans/core/commands/AdminCommands.java
new file mode 100644
index 000000000..c80c5345e
--- /dev/null
+++ b/bans-core/src/main/java/space/arim/libertybans/core/commands/AdminCommands.java
@@ -0,0 +1,93 @@
+/*
+ * LibertyBans-core
+ * Copyright © 2020 Anand Beh
+ *
+ * LibertyBans-core is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * LibertyBans-core 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with LibertyBans-core. If not, see
+ * and navigate to version 3 of the GNU Affero General Public License.
+ */
+package space.arim.libertybans.core.commands;
+
+import java.util.concurrent.CompletableFuture;
+
+import space.arim.libertybans.core.config.MessagesConfig;
+import space.arim.libertybans.core.env.CmdSender;
+
+public class AdminCommands extends AbstractSubCommandGroup {
+
+ AdminCommands(Commands commands) {
+ super(commands, "reload", "restart");
+ }
+
+ private MessagesConfig.Admin cfg() {
+ return messages().admin();
+ }
+
+ @Override
+ public CommandExecution execute(CmdSender sender, CommandPackage command, String arg) {
+ return new Execution(sender, command, arg);
+ }
+
+ private class Execution extends AbstractCommandExecution {
+
+ private final String arg;
+
+ Execution(CmdSender sender, CommandPackage command, String arg) {
+ super(sender, command);
+ this.arg = arg;
+ }
+
+ @Override
+ public void execute() {
+ if (!sender().hasPermission("libertybans.admin." + arg)) {
+ sender().sendMessage(cfg().noPermission());
+ return;
+ }
+ switch (arg) {
+ case "restart":
+ restartCmd();
+ break;
+ case "reload":
+ reloadCmd();
+ break;
+ default:
+ throw new IllegalArgumentException("Command mismatch");
+ }
+ }
+
+ private void restartCmd() {
+ sender().sendMessage(cfg().ellipses());
+ boolean restarted = core().getEnvironment().fullRestart();
+ if (restarted) {
+ sender().sendMessage(cfg().restarted());
+ } else {
+ sender().sendLiteralMessage("Not restarting because loading already in process");
+ }
+ }
+
+ private void reloadCmd() {
+ sender().sendMessage(cfg().ellipses());
+ CompletableFuture> reloadFuture = core().getConfigs().reloadConfigs().thenAccept((result) -> {
+ if (result) {
+ sender().sendMessage(cfg().reloaded());
+ } else {
+ sender().sendLiteralMessage(
+ "&cAn error occurred reloading the configuration. Please check the server console.");
+ }
+ });
+ core().postFuture(core().getFuturesFactory().copyFuture(reloadFuture));
+ }
+
+ }
+
+}
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/commands/CommandExecution.java b/bans-core/src/main/java/space/arim/libertybans/core/commands/CommandExecution.java
new file mode 100644
index 000000000..5917a9ebe
--- /dev/null
+++ b/bans-core/src/main/java/space/arim/libertybans/core/commands/CommandExecution.java
@@ -0,0 +1,29 @@
+/*
+ * LibertyBans-core
+ * Copyright © 2020 Anand Beh
+ *
+ * LibertyBans-core is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * LibertyBans-core 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with LibertyBans-core. If not, see
+ * and navigate to version 3 of the GNU Affero General Public License.
+ */
+package space.arim.libertybans.core.commands;
+
+interface CommandExecution {
+
+ void execute();
+
+ static CommandExecution empty() {
+ return () -> {};
+ }
+
+}
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/commands/Commands.java b/bans-core/src/main/java/space/arim/libertybans/core/commands/Commands.java
index 23c6510ed..ca06f692f 100644
--- a/bans-core/src/main/java/space/arim/libertybans/core/commands/Commands.java
+++ b/bans-core/src/main/java/space/arim/libertybans/core/commands/Commands.java
@@ -40,7 +40,7 @@ public class Commands {
public Commands(LibertyBansCore core) {
this.core = core;
- subCommands = List.of(new PunishCommands(this), new UnpunishCommands(this), new ReloadCommands(this));
+ subCommands = List.of(new PunishCommands(this), new UnpunishCommands(this), new AdminCommands(this));
}
/*
@@ -49,21 +49,45 @@ public Commands(LibertyBansCore core) {
public void execute(CmdSender sender, CommandPackage command) {
if (!sender.hasPermission("libertybans.commands")) {
- sender.parseThenSend(core.getConfigs().getMessages().getString("all.base-permission-message"));
+ sender.sendMessage(core.getMessagesConfig().all().basePermissionMessage());
return;
}
if (!command.hasNext()) {
- sender.parseThenSend("&7&lLibertyBans version " + PluginInfo.VERSION);
+ sender.sendLiteralMessage("&7&lLibertyBans version " + PluginInfo.VERSION);
return;
}
- String firstArg = command.next().toLowerCase(Locale.ENGLISH);
+ String firstArg = command.next().toLowerCase(Locale.ROOT);
for (SubCommandGroup subCommand : subCommands) {
if (subCommand.matches(firstArg)) {
- subCommand.execute(sender, command, firstArg);
+ CommandExecution execution = subCommand.execute(sender, command, firstArg);
+ execution.execute();
return;
}
}
- sender.parseThenSend(core.getConfigs().getMessages().getString("all.usage"));
+ sendUsage(sender, command, firstArg.equals("usage"));
+ }
+
+ /*
+ * Usage
+ */
+
+ private void sendUsage(CmdSender sender, CommandPackage command, boolean explicit) {
+ if (!explicit) {
+ sender.sendMessage(core.getMessagesConfig().all().usage());
+ }
+ UsageSection[] sections = UsageSection.values();
+ int page = 1;
+ if (command.hasNext()) {
+ try {
+ page = Integer.parseInt(command.next());
+ } catch (NumberFormatException ignored) {}
+ if (page <= 0 || page > sections.length) {
+ page = 1;
+ }
+ }
+ UsageSection section = sections[page - 1];
+ sender.sendMessage(section.getContent());
+ sender.sendLiteralMessage("&ePage " + page + "/4. &7Use /libertybans usage to navigate");
}
/*
@@ -92,8 +116,7 @@ CentralisedFuture parseVictim(CmdSender sender, String targetArg) {
}
return core.getUUIDMaster().fullLookupUUID(targetArg).thenApply((uuid) -> {
if (uuid == null) {
- sender.parseThenSend(core.getConfigs().getMessages().getString("all.not-found.uuid")
- .replace("%TARGET%", targetArg));
+ sender.sendMessage(core.getMessagesConfig().all().notFound().uuid().replaceText("%TARGET%", targetArg));
return null;
}
return PlayerVictim.of(uuid);
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/commands/DurationParser.java b/bans-core/src/main/java/space/arim/libertybans/core/commands/DurationParser.java
new file mode 100644
index 000000000..5f81d16a5
--- /dev/null
+++ b/bans-core/src/main/java/space/arim/libertybans/core/commands/DurationParser.java
@@ -0,0 +1,69 @@
+/*
+ * LibertyBans-core
+ * Copyright © 2020 Anand Beh
+ *
+ * LibertyBans-core is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * LibertyBans-core 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with LibertyBans-core. If not, see
+ * and navigate to version 3 of the GNU Affero General Public License.
+ */
+package space.arim.libertybans.core.commands;
+
+import java.time.Duration;
+import java.util.Set;
+
+class DurationParser {
+
+ private final String argument;
+ private final Set permanentArguments;
+
+ DurationParser(String argument, Set permanentArguments) {
+ this.argument = argument;
+ this.permanentArguments = permanentArguments;
+ }
+
+ Duration parse() {
+ if (permanentArguments.contains(argument)) {
+ return Duration.ZERO;
+ }
+ char[] characters = argument.toCharArray();
+ int unitIndex = 0;
+ for (int n = 0; n < characters.length; n++) {
+ if (!Character.isDigit(characters[n])) {
+ unitIndex = n;
+ break;
+ }
+ }
+ if (unitIndex == 0) {
+ return Duration.ZERO;
+ }
+ long number = Long.parseLong(argument.substring(0, unitIndex));
+ switch (argument.substring(unitIndex)) {
+ case "Y":
+ case "y":
+ return Duration.ofDays(365L * number);
+ case "MO":
+ case "mo":
+ return Duration.ofDays(30L * number);
+ case "D":
+ case "d":
+ return Duration.ofDays(number);
+ case "M":
+ case "m":
+ return Duration.ofMinutes(number);
+ default:
+ break;
+ }
+ return Duration.ZERO;
+ }
+
+}
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/commands/PunishCommands.java b/bans-core/src/main/java/space/arim/libertybans/core/commands/PunishCommands.java
index 43e1c3cad..31040877c 100644
--- a/bans-core/src/main/java/space/arim/libertybans/core/commands/PunishCommands.java
+++ b/bans-core/src/main/java/space/arim/libertybans/core/commands/PunishCommands.java
@@ -18,95 +18,133 @@
*/
package space.arim.libertybans.core.commands;
-import java.util.Arrays;
+import java.time.Duration;
import java.util.Locale;
-import java.util.concurrent.CompletableFuture;
import space.arim.omnibus.util.concurrent.CentralisedFuture;
import space.arim.api.chat.SendableMessage;
-import space.arim.libertybans.api.DraftPunishment;
import space.arim.libertybans.api.PunishmentType;
-import space.arim.libertybans.core.MiscUtil;
+import space.arim.libertybans.api.Victim;
+import space.arim.libertybans.api.punish.DraftPunishment;
+import space.arim.libertybans.api.punish.Punishment;
+import space.arim.libertybans.core.config.AdditionsSection.PunishmentAddition;
+import space.arim.libertybans.core.config.AdditionsSection.ExclusivePunishmentAddition;
+import space.arim.libertybans.core.config.MainConfig;
import space.arim.libertybans.core.env.CmdSender;
+import space.arim.libertybans.core.punish.MiscUtil;
public class PunishCommands extends AbstractSubCommandGroup {
PunishCommands(Commands commands) {
- super(commands, Arrays.stream(MiscUtil.punishmentTypes()).map((type) -> type.name().toLowerCase(Locale.ENGLISH))
- .toArray(String[]::new));
+ super(commands, MiscUtil.punishmentTypes().stream().map((type) -> type.name().toLowerCase(Locale.ROOT)));
}
@Override
- public void execute(CmdSender sender, CommandPackage command, String arg) {
- execute(sender, command, PunishmentType.valueOf(arg.toUpperCase(Locale.ENGLISH)));
+ public CommandExecution execute(CmdSender sender, CommandPackage command, String arg) {
+ return new Execution(sender, command, PunishmentType.valueOf(arg.toUpperCase(Locale.ROOT)));
}
- private void execute(CmdSender sender, CommandPackage command, PunishmentType type) {
- if (!sender.hasPermission("libertybans." + type.getLowercaseName() + ".do")) { // libertybans.ban.do
- String path = "additions." + type.getLowercaseNamePlural() + ".permission.command"; // additions.bans.permission.command
- sender.parseThenSend(messages().getString(path));
- return;
+ private class Execution extends TypeSpecificExecution {
+
+ private final PunishmentAddition section;
+
+ Execution(CmdSender sender, CommandPackage command, PunishmentType type) {
+ super(sender, command, type);
+ section = messages().additions().forType(type);
+ }
+
+ @Override
+ public void execute() {
+ if (!sender().hasPermission("libertybans." + type().getLowercaseName() + ".command")) { // libertybans.ban.command
+ sender().sendMessage(section.permissionCommand());
+ return;
+ }
+ if (!command().hasNext()) {
+ sender().sendMessage(section.usage());
+ return;
+ }
+ execute0();
}
- if (!command.hasNext()) {
- sender.parseThenSend(messages().getString(
- "additions." + type.getLowercaseNamePlural() + ".usage")); // additions.bans.usage
- return;
+
+ private void execute0() {
+ String targetArg = command().next();
+ CentralisedFuture> future = commands.parseVictim(sender(), targetArg).thenCompose((victim) -> {
+ if (victim == null) {
+ return completedFuture(null);
+ }
+ return performEnact(victim, targetArg);
+
+ }).thenCompose((punishment) -> {
+ if (punishment == null) {
+ return completedFuture(null);
+ }
+ return enforceAndSendSuccess(punishment);
+ });
+ core().postFuture(future);
}
- String targetArg = command.next();
- commands.parseVictim(sender, targetArg).thenCompose((victim) -> {
- if (victim == null) {
- return completedFuture(null);
+
+ private CentralisedFuture performEnact(Victim victim, String targetArg) {
+ final Duration duration;
+ if (type() != PunishmentType.KICK && command().hasNext()) {
+ String time = command().peek();
+ duration = new DurationParser(time, messages().formatting().permanentArguments()).parse();
+ if (!duration.isZero()) {
+ command().next();
+ }
+ } else {
+ duration = Duration.ZERO;
}
String reason;
- if (command.hasNext()) {
- reason = command.allRemaining();
- } else if (core().getConfigs().getConfig().getBoolean("reasons.permit-blank")) {
+ MainConfig.Reasons reasonsCfg;
+ if (command().hasNext()) {
+ reason = command().allRemaining();
+ } else if ((reasonsCfg = core().getMainConfig().reasons()).permitBlank()) {
reason = "";
} else {
- reason = core().getConfigs().getConfig().getString("reasons.default-reason");
+ reason = reasonsCfg.defaultReason();
}
- DraftPunishment draftPunishment = new DraftPunishment.Builder().victim(victim)
- .operator(sender.getOperator()).type(type).reason(reason)
- .scope(core().getScopeManager().globalScope()).build();
+ DraftPunishment draftPunishment = core().getDrafter().draftBuilder()
+ .victim(victim).operator(sender().getOperator()).type(type()).reason(reason)
+ .duration(duration)
+ .build();
- return core().getEnactor().enactPunishment(draftPunishment).thenApply((nullIfConflict) -> {
+ return draftPunishment.enactPunishmentWithoutEnforcement().thenApply((nullIfConflict) -> {
if (nullIfConflict == null) {
- if (type.isSingular()) {
- String configPath = "additions." + type.getLowercaseNamePlural() + ".error.conflicting"; // additions.bans.error.conflicting
- sender.parseThenSend(messages().getString(configPath).replace("%TARGET%", targetArg));
- } else {
- sender.parseThenSend(messages().getString("misc.unknown-error"));
- }
+ sendConflict(targetArg);
}
return nullIfConflict;
});
- }).thenCompose((punishment) -> {
- if (punishment == null) {
- return completedFuture(null);
+ }
+
+ private void sendConflict(String targetArg) {
+ SendableMessage message;
+ if (type().isSingular()) {
+ message = ((ExclusivePunishmentAddition) section).conflicting().replaceText("%TARGET%", targetArg);
+ } else {
+ message = messages().misc().unknownError();
}
- // Success message
- String rawMsg = messages().getString(
- "additions." + type.getLowercaseNamePlural() + ".successful.message"); // additions.bans.successful.message
- CentralisedFuture futureMsg = core().getFormatter().formatWithPunishment(rawMsg, punishment);
-
- // Enforcement
- CentralisedFuture> enforcement = core().getEnforcer().enforce(punishment);
+ sender().sendMessage(message);
+ }
+
+ private CentralisedFuture> enforceAndSendSuccess(Punishment punishment) {
- // Notification
- String configMsgPath = "additions." + type.getLowercaseNamePlural() + ".successful.notification"; // addition.bans.successful.notification
- String rawNotify = messages().getString(configMsgPath);
- CentralisedFuture futureNotify = core().getFormatter().formatWithPunishment(rawNotify, punishment);
+ CentralisedFuture> enforcement = punishment.enforcePunishment();
+ CentralisedFuture successMessage = core().getFormatter().formatWithPunishment(
+ section.successMessage(), punishment);
+ CentralisedFuture successNotify = core().getFormatter().formatWithPunishment(
+ section.successNotification(), punishment);
- // Conclusion
- return CompletableFuture.allOf(futureMsg, enforcement, futureNotify).thenAccept((ignore) -> {
- sender.sendMessage(futureMsg.join());
+ return core().getFuturesFactory().allOf(enforcement, successMessage, successNotify).thenAccept((ignore) -> {
+ sender().sendMessage(successMessage.join());
- String notifyPerm = "libertybans." + type.getLowercaseName() + ".notify"; // libertybans.ban.notify
- core().getEnvironment().getEnforcer().sendToThoseWithPermission(notifyPerm, futureNotify.join());
+ SendableMessage fullNotify = core().getFormatter().prefix(successNotify.join());
+ String notifyPerm = "libertybans." + type().getLowercaseName() + ".notify";
+ core().getEnvironment().getEnforcer().sendToThoseWithPermission(notifyPerm, fullNotify);
});
- }).whenComplete(core()::debugFuture);
+ }
+
}
}
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/commands/ReloadCommands.java b/bans-core/src/main/java/space/arim/libertybans/core/commands/ReloadCommands.java
deleted file mode 100644
index f588c6d41..000000000
--- a/bans-core/src/main/java/space/arim/libertybans/core/commands/ReloadCommands.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * LibertyBans-core
- * Copyright © 2020 Anand Beh
- *
- * LibertyBans-core is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * LibertyBans-core 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 Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with LibertyBans-core. If not, see
- * and navigate to version 3 of the GNU Affero General Public License.
- */
-package space.arim.libertybans.core.commands;
-
-import space.arim.api.chat.SendableMessage;
-
-import space.arim.libertybans.core.env.CmdSender;
-
-public class ReloadCommands extends AbstractSubCommandGroup {
-
- ReloadCommands(Commands commands) {
- super(commands, "reload", "restart");
- }
-
- private SendableMessage ellipses() {
- return core().getFormatter().parseMessage(messages().getString("admin.ellipses"));
- }
-
- @Override
- public void execute(CmdSender sender, CommandPackage command, String arg) {
- if (!sender.hasPermission("libertybans.admin." + arg)) {
- sender.parseThenSend(messages().getString("admin.no-permission"));
- return;
- }
- switch (arg) {
- case "restart":
- restartCmd(sender);
- break;
- case "reload":
- reloadCmd(sender);
- break;
- default:
- throw new IllegalStateException("Command mismatch");
- }
- }
-
- private void restartCmd(CmdSender sender) {
- sender.sendMessage(ellipses());
- boolean restarted = core().getEnvironment().fullRestart();
- sender.parseThenSend((restarted) ? messages().getString("admin.restarted")
- : "Not restarting because loading already in process");
- }
-
- private void reloadCmd(CmdSender sender) {
- sender.sendMessage(ellipses());
- core().getConfigs().reloadConfigs().thenAccept((result) -> {
- String message = (result) ? messages().getString("admin.reloaded")
- : "&cAn error occurred reloading the configuration. Please check the server console.";
- sender.parseThenSend(message);
- });
- }
-
-}
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/commands/SubCommandGroup.java b/bans-core/src/main/java/space/arim/libertybans/core/commands/SubCommandGroup.java
index 5c1962c2b..6f507e6a9 100644
--- a/bans-core/src/main/java/space/arim/libertybans/core/commands/SubCommandGroup.java
+++ b/bans-core/src/main/java/space/arim/libertybans/core/commands/SubCommandGroup.java
@@ -28,7 +28,7 @@
* @author A248
*
*/
-public interface SubCommandGroup {
+interface SubCommandGroup {
/**
* Whether this subcommand object matches the specified subcommand argument
@@ -44,8 +44,9 @@ public interface SubCommandGroup {
* @param sender the command sender
* @param command the command
* @param arg the argument, lowercased
+ * @return the execution
*/
- void execute(CmdSender sender, CommandPackage command, String arg);
+ CommandExecution execute(CmdSender sender, CommandPackage command, String arg);
/**
* Gets tab complete suggestions for the subcommand
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/commands/TypeSpecificExecution.java b/bans-core/src/main/java/space/arim/libertybans/core/commands/TypeSpecificExecution.java
new file mode 100644
index 000000000..bd71d638f
--- /dev/null
+++ b/bans-core/src/main/java/space/arim/libertybans/core/commands/TypeSpecificExecution.java
@@ -0,0 +1,37 @@
+/*
+ * LibertyBans-core
+ * Copyright © 2020 Anand Beh
+ *
+ * LibertyBans-core is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * LibertyBans-core 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with LibertyBans-core. If not, see
+ * and navigate to version 3 of the GNU Affero General Public License.
+ */
+package space.arim.libertybans.core.commands;
+
+import space.arim.libertybans.api.PunishmentType;
+import space.arim.libertybans.core.env.CmdSender;
+
+abstract class TypeSpecificExecution extends AbstractCommandExecution {
+
+ private final PunishmentType type;
+
+ TypeSpecificExecution(CmdSender sender, CommandPackage command, PunishmentType type) {
+ super(sender, command);
+ this.type = type;
+ }
+
+ PunishmentType type() {
+ return type;
+ }
+
+}
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/commands/UnpunishCommands.java b/bans-core/src/main/java/space/arim/libertybans/core/commands/UnpunishCommands.java
index 51c1c0a54..868129520 100644
--- a/bans-core/src/main/java/space/arim/libertybans/core/commands/UnpunishCommands.java
+++ b/bans-core/src/main/java/space/arim/libertybans/core/commands/UnpunishCommands.java
@@ -20,52 +20,82 @@
import java.util.Arrays;
import java.util.Locale;
-import java.util.concurrent.CompletableFuture;
import space.arim.omnibus.util.concurrent.CentralisedFuture;
import space.arim.api.chat.SendableMessage;
+import space.arim.api.chat.manipulator.SendableMessageManipulator;
-import space.arim.libertybans.api.Punishment;
import space.arim.libertybans.api.PunishmentType;
-import space.arim.libertybans.core.MiscUtil;
+import space.arim.libertybans.api.Victim;
+import space.arim.libertybans.api.punish.Punishment;
+import space.arim.libertybans.core.config.RemovalsSection.PunishmentRemoval;
+import space.arim.libertybans.core.config.RemovalsSection.WarnRemoval;
import space.arim.libertybans.core.env.CmdSender;
+import space.arim.libertybans.core.punish.MiscUtil;
public class UnpunishCommands extends AbstractSubCommandGroup {
UnpunishCommands(Commands commands) {
super(commands, Arrays.stream(MiscUtil.punishmentTypesExcludingKick())
- .map((type) -> "un" + type.name().toLowerCase(Locale.ENGLISH)).toArray(String[]::new));
+ .map((type) -> "un" + type.name().toLowerCase(Locale.ROOT)));
}
@Override
- public void execute(CmdSender sender, CommandPackage command, String arg) {
- execute(sender, command, PunishmentType.valueOf(arg.substring(2).toUpperCase(Locale.ENGLISH)));
+ public CommandExecution execute(CmdSender sender, CommandPackage command, String arg) {
+ return new Execution(sender, command, PunishmentType.valueOf(arg.substring(2).toUpperCase(Locale.ROOT)));
}
- private void execute(CmdSender sender, CommandPackage command,
- PunishmentType type) {
- if (!sender.hasPermission("libertybans." + type.getLowercaseName() + ".undo")) { // libertybans.ban.undo
- sender.parseThenSend(messages().getString(
- "removals." + type.getLowercaseNamePlural() + ".permission.command")); // removals.bans.permission.command
- return;
- }
- if (!command.hasNext()) {
- sender.parseThenSend(messages().getString(
- "removals." + type.getLowercaseNamePlural() + ".usage")); // removals.bans.usage
- return;
+ private class Execution extends TypeSpecificExecution {
+
+ private final PunishmentRemoval section;
+
+ Execution(CmdSender sender, CommandPackage command, PunishmentType type) {
+ super(sender, command, type);
+ section = messages().removals().forType(type);
}
- String targetArg = command.next();
- commands.parseVictim(sender, targetArg).thenCompose((victim) -> {
- if (victim == null) {
- return completedFuture(null);
+
+ @Override
+ public void execute() {
+ if (!sender().hasPermission("libertybans.un" + type().getLowercaseName() + ".command")) { // libertybans.unban.command
+ sender().sendMessage(section.permissionCommand());
+ return;
+ }
+ if (!command().hasNext()) {
+ sender().sendMessage(section.usage());
+ return;
}
+ execute0();
+ }
+
+ private void execute0() {
+ String targetArg = command().next();
+ CentralisedFuture> future = commands.parseVictim(sender(), targetArg).thenCompose((victim) -> {
+ if (victim == null) {
+ return completedFuture(null);
+ }
+ return performUndo(victim, targetArg);
+
+ }).thenCompose((punishment) -> {
+ if (punishment == null) {
+ return completedFuture(null);
+ }
+ return sendSuccess(punishment);
+ });
+ core().postFuture(future);
+ }
+
+ private CentralisedFuture performUndo(Victim victim, String targetArg) {
+ CmdSender sender = sender();
+ CommandPackage command = command();
+ PunishmentType type = type();
+
CentralisedFuture futureUndo = null;
final int finalId;
if (type == PunishmentType.WARN) {
if (!command.hasNext()) {
- sender.parseThenSend(messages().getString("removals.warns.usage"));
+ sender.sendMessage(section.usage());
return completedFuture(null);
}
String idArg = command.next();
@@ -73,53 +103,44 @@ private void execute(CmdSender sender, CommandPackage command,
try {
id = Integer.parseInt(idArg);
} catch (NumberFormatException ignored) {
- sender.parseThenSend(messages().getString("removals.warns.not-a-number").replace("%ID_ARG%", idArg));
+ sender.sendMessage(((WarnRemoval) section).notANumber().replaceText("%ID_ARG%", idArg));
return completedFuture(null);
}
- futureUndo = core().getEnactor().undoAndGetPunishmentByIdAndType(id, type);
+ futureUndo = core().getRevoker().revokeByIdAndType(id, type).undoAndGetPunishmentWithoutUnenforcement();
finalId = id;
} else {
assert type.isSingular() : type;
- futureUndo = core().getEnactor().undoAndGetPunishmentByTypeAndVictim(type, victim);
+ futureUndo = core().getRevoker().revokeByTypeAndVictim(type, victim).undoAndGetPunishmentWithoutUnenforcement();
finalId = -1;
}
return futureUndo.thenApply((nullIfNotFound) -> {
if (nullIfNotFound == null) {
- String configPath = "removals." + type.getLowercaseNamePlural() + ".not-found"; // removals.bans.not-found
- String rawMessage = messages().getString(configPath).replace("%TARGET%", targetArg);
+ SendableMessage notFound = section.notFound().replaceText("%TARGET%", targetArg);
if (type == PunishmentType.WARN) {
- rawMessage = rawMessage.replace("%ID%", Integer.toString(finalId));
+ notFound = SendableMessageManipulator.create(notFound).replaceText("%ID%", Integer.toString(finalId));
}
- sender.parseThenSend(rawMessage);
+ sender.sendMessage(notFound);
}
return nullIfNotFound;
});
- }).thenCompose((punishment) -> {
- if (punishment == null) {
- return completedFuture(null);
- }
- // Success message
- String rawMsg = messages().getString(
- "removals." + type.getLowercaseNamePlural() + ".successful.message"); // removals.bans.successful.message
- CentralisedFuture futureSuccessMessage = core().getFormatter().formatWithPunishment(rawMsg, punishment);
+ }
+
+ private CentralisedFuture> sendSuccess(Punishment punishment) {
- // Notification
- CentralisedFuture futureNotify = core().getFormatter().formatOperator(sender.getOperator())
- .thenCompose((operatorFormatted) -> {
- String rawNotify = messages()
- .getString("removals." + type.getLowercaseNamePlural() + ".successful.notification"); // removals.bans.successful.notification
- rawNotify = rawNotify.replace("%UNOPERATOR%", operatorFormatted);
- return core().getFormatter().formatWithPunishment(rawNotify, punishment);
- });
+ CentralisedFuture> unenforcement = punishment.unenforcePunishment();
+ CentralisedFuture successMessage = core().getFormatter().formatWithPunishment(
+ section.successMessage(), punishment);
+ CentralisedFuture successNotify = core().getFormatter().formatWithPunishmentAndUnoperator(
+ section.successNotification(), punishment, sender().getOperator());
- // Conclusion
- return CompletableFuture.allOf(futureSuccessMessage, futureNotify).thenAccept((ignore) -> {
- sender.sendMessage(futureSuccessMessage.join());
+ return core().getFuturesFactory().allOf(unenforcement, successMessage, successNotify).thenAccept((ignore) -> {
+ sender().sendMessage(successMessage.join());
- core().getEnvironment().getEnforcer().sendToThoseWithPermission(
- "libertybans." + type.getLowercaseName() + ".unnotify", futureNotify.join()); // libertybans.ban.unnotify
+ SendableMessage fullNotify = core().getFormatter().prefix(successNotify.join());
+ String notifyPerm = "libertybans." + type().getLowercaseName() + ".unnotify";
+ core().getEnvironment().getEnforcer().sendToThoseWithPermission(notifyPerm, fullNotify);
});
- }).whenComplete(core()::debugFuture);
+ }
}
}
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/commands/UsageSection.java b/bans-core/src/main/java/space/arim/libertybans/core/commands/UsageSection.java
new file mode 100644
index 000000000..4abaeb897
--- /dev/null
+++ b/bans-core/src/main/java/space/arim/libertybans/core/commands/UsageSection.java
@@ -0,0 +1,59 @@
+/*
+ * LibertyBans-core
+ * Copyright © 2020 Anand Beh
+ *
+ * LibertyBans-core is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * LibertyBans-core 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with LibertyBans-core. If not, see
+ * and navigate to version 3 of the GNU Affero General Public License.
+ */
+package space.arim.libertybans.core.commands;
+
+import space.arim.api.chat.SendableMessage;
+import space.arim.api.chat.serialiser.LegacyCodeSerialiser;
+import space.arim.api.chat.serialiser.SendableMessageSerialiser;
+
+enum UsageSection {
+
+ PUNISH("&e/ban &7- ban players",
+ "&e/mute &7- mute players",
+ "&e/warn &7- warn players",
+ "&e/kick &7- kick players"),
+ UNPUNISH("&e/unban &7- undo a ban",
+ "&e/unmute &7- undo a mute",
+ "&e/unwarn &7- undo a warn"),
+ LIST("&b/banlist &7- view active bans",
+ "&b/mutelist &7- view active mutes",
+ "&b/history &7- view a player's history",
+ "&b/warns &7- view a player's warns",
+ "&b/blame &7- view another staff member's punishments"),
+ OTHER("&7/libertybans usage - shows this",
+ "&7/libertybans reload - reload config.yml and messages configuration",
+ "&7/libertybans restart - perform a full restart, reloads everything including database connections");
+
+ private final SendableMessage content;
+
+ UsageSection(String...commands) {
+ String name = name();
+ String headerString = "&b" + Character.toUpperCase(name.charAt(0)) + name.substring(1) + " commands:";
+
+ SendableMessageSerialiser serialiser = LegacyCodeSerialiser.getInstance('&');
+ SendableMessage header = serialiser.deserialise(headerString);
+ SendableMessage body = serialiser.deserialise("\n" + String.join("\n", commands));
+ content = header.concatenate(body);
+ }
+
+ SendableMessage getContent() {
+ return content;
+ }
+
+}
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/config/AdditionsSection.java b/bans-core/src/main/java/space/arim/libertybans/core/config/AdditionsSection.java
new file mode 100644
index 000000000..9c0a816a1
--- /dev/null
+++ b/bans-core/src/main/java/space/arim/libertybans/core/config/AdditionsSection.java
@@ -0,0 +1,228 @@
+/*
+ * LibertyBans-core
+ * Copyright © 2020 Anand Beh
+ *
+ * LibertyBans-core is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * LibertyBans-core 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with LibertyBans-core. If not, see
+ * and navigate to version 3 of the GNU Affero General Public License.
+ */
+package space.arim.libertybans.core.config;
+
+import space.arim.api.chat.SendableMessage;
+import space.arim.api.chat.manipulator.SendableMessageManipulator;
+
+import space.arim.libertybans.api.PunishmentType;
+import space.arim.libertybans.core.punish.MiscUtil;
+
+import space.arim.dazzleconf.annote.ConfDefault.DefaultString;
+import space.arim.dazzleconf.annote.ConfDefault.DefaultStrings;
+import space.arim.dazzleconf.annote.ConfHeader;
+import space.arim.dazzleconf.annote.ConfKey;
+import space.arim.dazzleconf.annote.SubSection;
+
+@ConfHeader({
+ "",
+ "Messages regarding /ban, /mute, /warn, /kick",
+ "Includes punishment layouts",
+ "",
+ ""})
+public interface AdditionsSection {
+
+ interface PunishmentAddition {
+
+ SendableMessage usage();
+
+ SendableMessage permissionCommand();
+
+ SendableMessageManipulator successMessage();
+
+ SendableMessageManipulator successNotification();
+
+ SendableMessageManipulator layout();
+
+ }
+
+ interface ExclusivePunishmentAddition extends PunishmentAddition {
+
+ SendableMessageManipulator conflicting();
+
+ }
+
+ interface BanAddition extends ExclusivePunishmentAddition {
+
+ @Override
+ @DefaultString("&cUsage: /ban &e [time] &c.")
+ SendableMessage usage();
+
+ @Override
+ @ConfKey("permission.command")
+ @DefaultString("&cYou may not ban other players.")
+ SendableMessage permissionCommand();
+
+ @Override
+ @DefaultString("&c&o%TARGET%&r&7 is already banned.")
+ SendableMessageManipulator conflicting();
+
+ @Override
+ @ConfKey("success.message")
+ @DefaultString("&aBanned &c&o%VICTIM%&r&a for &a&o%DURATION%&r&a because of &e&o%REASON%&r&a.")
+ SendableMessageManipulator successMessage();
+
+ @Override
+ @ConfKey("success.notification")
+ @DefaultString("&c&o%OPERATOR%&r&7 banned &c&o%VICTIM%&r&7 for &a&o%DURATION%&r&7 because of &e&o%REASON%&r&7.")
+ SendableMessageManipulator successNotification();
+
+ @Override
+ @DefaultStrings({
+ "&7&lBanned",
+ "&cDuration: &e%TIME_REMAINING%",
+ "&7",
+ "&c&lReason",
+ "&7%REASON%",
+ "&7",
+ "&3&lAppeal Your Punishment",
+ "&cWebsite: &7website",
+ "&cDiscord: &7discord"})
+ SendableMessageManipulator layout();
+
+ }
+
+ interface MuteAddition extends ExclusivePunishmentAddition {
+
+ @Override
+ @DefaultString("&cUsage: /mute &e [time] &c.")
+ SendableMessage usage();
+
+ @Override
+ @ConfKey("permission.command")
+ @DefaultString("&cYou may not mute other players.")
+ SendableMessage permissionCommand();
+
+ @Override
+ @DefaultString("&c&o%TARGET%&r&7 is already muted.")
+ SendableMessageManipulator conflicting();
+
+ @Override
+ @ConfKey("success.message")
+ @DefaultString("&aMuted &c&o%VICTIM%&r&a for &a&o%DURATION%&r&a because of &e&o%REASON%&r&a.")
+ SendableMessageManipulator successMessage();
+
+ @Override
+ @ConfKey("success.notification")
+ @DefaultString("&c&o%OPERATOR%&r&7 muted &c&o%VICTIM%&r&7 for &a&o%DURATION%&r&7 because of &e&o%REASON%&r&7.")
+ SendableMessageManipulator successNotification();
+
+ @Override
+ @DefaultStrings({
+ "&7&lMuted",
+ "&cDuration: &e%TIME_REMAINING%",
+ "&7",
+ "&c&lReason",
+ "&7%REASON%"})
+ SendableMessageManipulator layout();
+
+ }
+
+ interface WarnAddition extends PunishmentAddition {
+
+ @Override
+ @DefaultString("&cUsage: /warn &e [time] &c.")
+ SendableMessage usage();
+
+ @Override
+ @ConfKey("permission.command")
+ @DefaultString("&cYou may not warn other players.")
+ SendableMessage permissionCommand();
+
+ @Override
+ @ConfKey("success.message")
+ @DefaultString("&aWarned &c&o%VICTIM%&r&a for &a&o%DURATION%&r&a because of &e&o%REASON%&r&a.")
+ SendableMessageManipulator successMessage();
+
+ @Override
+ @ConfKey("success.notification")
+ @DefaultString("&c&o%OPERATOR%&r&7 warned &c&o%VICTIM%&r&7 for &a&o%DURATION%&r&7 because of &e&o%REASON%&r&7.")
+ SendableMessageManipulator successNotification();
+
+ @Override
+ @DefaultStrings({
+ "&7&lWarned",
+ "&cDuration: &e%TIME_REMAINING%",
+ "&7",
+ "&c&lReason",
+ "&7%REASON%"})
+ SendableMessageManipulator layout();
+
+ }
+
+ interface KickAddition extends PunishmentAddition {
+
+ @Override
+ @DefaultString("&cUsage: /kick &e &c.")
+ SendableMessage usage();
+
+ @Override
+ @ConfKey("permission.command")
+ @DefaultString("&cYou may not kick other players.")
+ SendableMessage permissionCommand();
+
+ @Override
+ @ConfKey("success.message")
+ @DefaultString("&aKicked &c&o%VICTIM%&r&a because of &e&o%REASON%&r&a.")
+ SendableMessageManipulator successMessage();
+
+ @Override
+ @ConfKey("success.notification")
+ @DefaultString("&c&o%OPERATOR%&r&7 kicked &c&o%VICTIM%&r&7 because of &e&o%REASON%&r&7.")
+ SendableMessageManipulator successNotification();
+
+ @Override
+ @DefaultStrings({
+ "&7&lKicked",
+ "&cDuration: &e%TIME_REMAINING%",
+ "&7",
+ "&c&lReason",
+ "&7%REASON%"})
+ SendableMessageManipulator layout();
+
+ }
+
+ @SubSection
+ BanAddition bans();
+
+ @SubSection
+ MuteAddition mutes();
+
+ @SubSection
+ WarnAddition warns();
+
+ @SubSection
+ KickAddition kicks();
+
+ default PunishmentAddition forType(PunishmentType type) {
+ switch (type) {
+ case BAN:
+ return bans();
+ case MUTE:
+ return mutes();
+ case WARN:
+ return warns();
+ case KICK:
+ return kicks();
+ default:
+ throw MiscUtil.unknownType(type);
+ }
+ }
+
+}
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/config/ConfigHolder.java b/bans-core/src/main/java/space/arim/libertybans/core/config/ConfigHolder.java
new file mode 100644
index 000000000..8bd652cb4
--- /dev/null
+++ b/bans-core/src/main/java/space/arim/libertybans/core/config/ConfigHolder.java
@@ -0,0 +1,122 @@
+/*
+ * LibertyBans-core
+ * Copyright © 2020 Anand Beh
+ *
+ * LibertyBans-core is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * LibertyBans-core 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with LibertyBans-core. If not, see
+ * and navigate to version 3 of the GNU Affero General Public License.
+ */
+package space.arim.libertybans.core.config;
+
+import java.io.IOException;
+import java.nio.channels.FileChannel;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.concurrent.CompletableFuture;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import space.arim.omnibus.util.ThisClass;
+
+import space.arim.dazzleconf.AuxiliaryKeys;
+import space.arim.dazzleconf.ConfigurationFactory;
+import space.arim.dazzleconf.ConfigurationOptions;
+import space.arim.dazzleconf.error.ConfigFormatSyntaxException;
+import space.arim.dazzleconf.error.InvalidConfigException;
+import space.arim.dazzleconf.ext.snakeyaml.SnakeYamlConfigurationFactory;
+import space.arim.dazzleconf.ext.snakeyaml.SnakeYamlOptions;
+
+class ConfigHolder {
+
+ private final ConfigurationFactory factory;
+
+ private volatile C instance;
+
+ private static final ConfigurationOptions CONFIG_OPTIONS;
+ private static final SnakeYamlOptions YAML_OPTIONS = new SnakeYamlOptions.Builder().useCommentingWriter(true).build();
+ private static final Logger logger = LoggerFactory.getLogger(ThisClass.get());
+
+ static {
+ ConfigurationOptions.Builder optionsBuilder = new ConfigurationOptions.Builder()
+ .setCreateSingleElementCollections(true);
+ ConfigSerialisers.addTo(optionsBuilder);
+ CONFIG_OPTIONS = optionsBuilder.build();
+ }
+
+ ConfigHolder(Class configClass) {
+ factory = new SnakeYamlConfigurationFactory<>(configClass, CONFIG_OPTIONS, YAML_OPTIONS);
+ }
+
+ C getConfigData() {
+ return instance;
+ }
+
+ CompletableFuture reload(Path path) {
+ return CompletableFuture.supplyAsync(() -> {
+ C instance;
+ try {
+ instance = reload0(path);
+ } catch (IOException ex) {
+ logger.warn("Encountered I/O error while reloading the configuration", ex);
+ return false;
+ }
+ this.instance = instance;
+ return true;
+ });
+ }
+
+ private C reload0(Path path) throws IOException {
+ C defaults = factory.loadDefaults();
+ if (!Files.exists(path)) {
+ try (FileChannel fileChannel = FileChannel.open(path,
+ StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)) {
+
+ factory.write(defaults, fileChannel);
+ }
+ return defaults;
+ }
+ C config;
+ // Load existing configuration
+ try (FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.READ)) {
+
+ config = factory.load(fileChannel, defaults);
+
+ } catch (ConfigFormatSyntaxException ex) {
+ logger.warn(
+ "The YAML syntax in your configuration is invalid. "
+ + "Please use a YAML validator such as https://yaml-online-parser.appspot.com/. "
+ + "Paste your configuration there and use it to work through errors. "
+ + "Run /libertybans reload when done. For now, the default configuration will be used.", ex);
+ return defaults;
+
+ } catch (InvalidConfigException ex) {
+ logger.warn(
+ "The values in your configuration are invalid. "
+ + "Please correct the issue and run /libertybans reload. "
+ + "For now, the default configuration will be used.", ex);
+ return defaults;
+ }
+ if (config instanceof AuxiliaryKeys) {
+ // Update existing configuration with missing keys
+ try (FileChannel fileChannel = FileChannel.open(path,
+ StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)) {
+
+ factory.write(config, fileChannel);
+ }
+ }
+ return config;
+ }
+
+}
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/config/ConfigPackage.java b/bans-core/src/main/java/space/arim/libertybans/core/config/ConfigPackage.java
deleted file mode 100644
index 6622c9bec..000000000
--- a/bans-core/src/main/java/space/arim/libertybans/core/config/ConfigPackage.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * LibertyBans-core
- * Copyright © 2020 Anand Beh
- *
- * LibertyBans-core is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * LibertyBans-core 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 Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with LibertyBans-core. If not, see
- * and navigate to version 3 of the GNU Affero General Public License.
- */
-package space.arim.libertybans.core.config;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.channels.Channels;
-import java.nio.channels.FileChannel;
-import java.nio.channels.ReadableByteChannel;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.StandardOpenOption;
-import java.util.HashSet;
-import java.util.Locale;
-import java.util.Set;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionException;
-import java.util.concurrent.Executor;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import space.arim.omnibus.util.ThisClass;
-
-import space.arim.api.configure.ConfigResult;
-import space.arim.api.configure.Configuration;
-import space.arim.api.configure.configs.SingularConfig;
-import space.arim.api.configure.yaml.YamlSyntaxException;
-
-public class ConfigPackage {
-
- final SingularConfig sql;
- final SingularConfig config;
- final Configuration messages;
-
- /**
- * The executor or executor service for config IO
- *
- * LibertyBans tries to use a platform thread pool for this. However,
- * not all platforms make this easy. In the worst case, an own thread pool
- * is created and this field is an instance of ExecutorService.
- *
- * At shutdown, check if the field is ExecutorService and if so shut it down.
- *
- */
- final Executor readWriteService;
-
- private final Path langFolder;
-
- private static final Logger logger = LoggerFactory.getLogger(ThisClass.get());
-
- ConfigPackage(SingularConfig sql, SingularConfig config, Configuration messages,
- Executor readWriteService, Path langFolder) {
- this.sql = sql;
- this.config = config;
- this.messages = messages;
- this.readWriteService = readWriteService;
- this.langFolder = langFolder;
- }
-
- public CompletableFuture reloadConfigs() {
- Set> futureLangFiles = new HashSet<>();
- for (Translation translation : Translation.values()) {
- final String name = "messages_" + translation.name().toLowerCase(Locale.ENGLISH) + ".yml";
- Path messagesPath = langFolder.resolve(name);
- if (!Files.exists(messagesPath)) {
- futureLangFiles.add(CompletableFuture.runAsync(() -> {
-
- try (InputStream inputStream = getClass().getResource("/lang/" + name).openStream();
- ReadableByteChannel source = Channels.newChannel(inputStream);
- FileChannel dest = FileChannel.open(messagesPath, StandardOpenOption.WRITE,
- StandardOpenOption.CREATE_NEW)) {
-
- dest.transferFrom(source, 0, Long.MAX_VALUE);
- } catch (IOException ex) {
- logger.warn("Unable to copy language file for language {}", name, ex);
- }
- }, readWriteService));
- }
- }
- var langFilesFuture = CompletableFuture.allOf(futureLangFiles.toArray(CompletableFuture[]::new));
- var readSqlFuture = throwIfFailed(sql.saveDefaultConfig()).thenCompose((copyResult) -> throwIfFailed(sql.readConfig()));
- var readConfFuture = throwIfFailed(config.saveDefaultConfig()).thenCompose((copyResult) -> throwIfFailed(config.readConfig()));
- var readMsgsFuture = readConfFuture.thenCompose((readConfResult) -> {
- String langFileOption = readConfResult.getReadData().getString("lang-file");
- if (langFileOption == null) {
- logger.warn("Unspecified language file, using default (en)");
- langFileOption = "en";
- }
- Path messagesPath = langFolder.resolve("messages_" + langFileOption + ".yml");
- return langFilesFuture.thenCompose((ignore) -> throwIfFailed(messages.readConfig(messagesPath)));
- });
- return CompletableFuture.allOf(readSqlFuture, readConfFuture, readMsgsFuture).handle((ignore, ex) -> {
- if (ex != null) {
- Throwable cause = ex.getCause();
- if (cause instanceof YamlSyntaxException) {
- logger.warn("One or more of your YML files has invalid syntax. "
- + "Please use a YAML validator such as https://yaml-online-parser.appspot.com/ "
- + "and paste your config files there to check them.", cause);
- } else {
- logger.warn("An unexpected issue occurred while reloading the configuration.", ex);
- }
- return false;
- }
- return true;
- });
- }
-
- private static CompletableFuture throwIfFailed(CompletableFuture futureResult) {
- return futureResult.thenApply((result) -> {
- if (!result.getResultDefinition().isSuccess()) {
- Exception ex = result.getException();
- if (ex instanceof RuntimeException) {
- throw (RuntimeException) ex;
- }
- throw new CompletionException(ex);
- }
- return result;
- });
- }
-
-}
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/config/ConfigSerialisers.java b/bans-core/src/main/java/space/arim/libertybans/core/config/ConfigSerialisers.java
new file mode 100644
index 000000000..fdbeb4fc6
--- /dev/null
+++ b/bans-core/src/main/java/space/arim/libertybans/core/config/ConfigSerialisers.java
@@ -0,0 +1,148 @@
+/*
+ * LibertyBans-core
+ * Copyright © 2020 Anand Beh
+ *
+ * LibertyBans-core is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * LibertyBans-core 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with LibertyBans-core. If not, see
+ * and navigate to version 3 of the GNU Affero General Public License.
+ */
+package space.arim.libertybans.core.config;
+
+import java.time.DateTimeException;
+import java.time.ZoneId;
+import java.time.zone.ZoneRulesException;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import space.arim.api.chat.SendableMessage;
+import space.arim.api.chat.manipulator.SendableMessageManipulator;
+import space.arim.api.chat.serialiser.JsonSkSerialiser;
+
+import space.arim.dazzleconf.ConfigurationOptions;
+import space.arim.dazzleconf.error.BadValueException;
+import space.arim.dazzleconf.serialiser.Decomposer;
+import space.arim.dazzleconf.serialiser.FlexibleType;
+import space.arim.dazzleconf.serialiser.ValueSerialiser;
+
+final class ConfigSerialisers {
+
+ private ConfigSerialisers() {}
+
+ static void addTo(ConfigurationOptions.Builder builder) {
+ List> serialisers = List.of(new MessageManipulatorSerialiser(), new MessageSerialiser(),
+ new DateTimeFormatterSerialiser(), new ZoneIdSerialiser());
+ for (ValueSerialiser> serialiser : serialisers) {
+ builder.addSerialiser(serialiser);
+ }
+ }
+
+ private static class MessageManipulatorSerialiser implements ValueSerialiser {
+
+ @Override
+ public Class getTargetClass() {
+ return SendableMessageManipulator.class;
+ }
+
+ @Override
+ public SendableMessageManipulator deserialise(FlexibleType flexibleType) throws BadValueException {
+ return SendableMessageManipulator.create(flexibleType.getObject(SendableMessage.class));
+ }
+
+ @Override
+ public Object serialise(SendableMessageManipulator value, Decomposer decomposer) {
+ return decomposer.decompose(SendableMessage.class, value.getMessage());
+ }
+
+ }
+
+ private static class MessageSerialiser implements ValueSerialiser {
+
+ @Override
+ public Class getTargetClass() {
+ return SendableMessage.class;
+ }
+
+ @Override
+ public SendableMessage deserialise(FlexibleType flexibleType) throws BadValueException {
+ String content = String.join("\n", flexibleType.getList((flexibleElement) -> flexibleElement.getString()));
+ return JsonSkSerialiser.getInstance().deserialise(content);
+ }
+
+ @Override
+ public Object serialise(SendableMessage value, Decomposer decomposer) {
+ String content = JsonSkSerialiser.getInstance().serialise(value);
+ List lines = content.lines().collect(Collectors.toList());
+ if (lines.size() == 1) {
+ return lines.get(0);
+ }
+ return lines;
+ }
+
+ }
+
+ private static class DateTimeFormatterSerialiser implements ValueSerialiser {
+
+ @Override
+ public Class getTargetClass() {
+ return DateTimeFormatterWithPattern.class;
+ }
+
+ @Override
+ public DateTimeFormatterWithPattern deserialise(FlexibleType flexibleType) throws BadValueException {
+ String format = flexibleType.getString();
+ try {
+ return new DateTimeFormatterWithPattern(format);
+ } catch (IllegalArgumentException ex) {
+ throw flexibleType.badValueExceptionBuilder().message("Bad date format " + format).cause(ex).build();
+ }
+ }
+
+ @Override
+ public String serialise(DateTimeFormatterWithPattern value, Decomposer decomposer) {
+ return value.getPattern();
+ }
+
+ }
+
+ private static class ZoneIdSerialiser implements ValueSerialiser {
+
+ @Override
+ public Class getTargetClass() {
+ return ZoneId.class;
+ }
+
+ @Override
+ public ZoneId deserialise(FlexibleType flexibleType) throws BadValueException {
+ String zone = flexibleType.getString();
+ if (zone.equalsIgnoreCase("default")) {
+ return ZoneId.systemDefault();
+ }
+ try {
+ return ZoneId.of(zone);
+ } catch (ZoneRulesException ex) {
+ throw flexibleType.badValueExceptionBuilder().message(
+ "Unknown region ID in " + zone + " (ZoneRulesException)").cause(ex).build();
+ } catch (DateTimeException ex) {
+ throw flexibleType.badValueExceptionBuilder().message(
+ "Invalid timezone in " + zone + " (DateTimeException)").cause(ex).build();
+ }
+ }
+
+ @Override
+ public Object serialise(ZoneId value, Decomposer decomposer) {
+ return value.getId();
+ }
+
+ }
+
+}
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/config/ConfigUtil.java b/bans-core/src/main/java/space/arim/libertybans/core/config/ConfigUtil.java
deleted file mode 100644
index c4cff7bb1..000000000
--- a/bans-core/src/main/java/space/arim/libertybans/core/config/ConfigUtil.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * LibertyBans-core
- * Copyright © 2020 Anand Beh
- *
- * LibertyBans-core is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * LibertyBans-core 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 Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with LibertyBans-core. If not, see
- * and navigate to version 3 of the GNU Affero General Public License.
- */
-package space.arim.libertybans.core.config;
-
-import java.time.DateTimeException;
-import java.time.ZoneId;
-import java.time.format.DateTimeFormatter;
-import java.time.zone.ZoneRulesException;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import space.arim.omnibus.util.ThisClass;
-
-import space.arim.api.chat.SendableMessage;
-import space.arim.api.chat.parser.SendableMessageParser;
-import space.arim.api.chat.parser.StandardSendableMessageParser;
-import space.arim.api.chat.parser.SendableMessageParser.ColourMode;
-import space.arim.api.chat.parser.SendableMessageParser.JsonMode;
-import space.arim.api.configure.SingleKeyValueTransformer;
-import space.arim.api.configure.ValueTransformer;
-
-import space.arim.libertybans.api.PunishmentType;
-import space.arim.libertybans.core.MiscUtil;
-import space.arim.libertybans.core.uuid.UUIDMaster;
-
-final class ConfigUtil {
-
- private static final Logger logger = LoggerFactory.getLogger(ThisClass.get());
-
- private static final SendableMessageParser parser = new StandardSendableMessageParser();
-
- private ConfigUtil() {}
-
- private static ValueTransformer dateFormatTransformer() {
- return SingleKeyValueTransformer.create("date-formatting.format", (value) -> {
- DateTimeFormatter result = null;
- if (value instanceof String) {
- String timeF = (String) value;
- try {
- result = DateTimeFormatter.ofPattern(timeF);
- } catch (IllegalArgumentException ignored) {}
- }
- if (result == null) {
- //result = DateTimeFormatter.ofPattern("dd/MM/yyyy kk:mm");
- logger.info("Config option date-formatting.format invalid: {}", value);
- }
- return result;
- });
- }
-
- private static ValueTransformer dateZoneTransformer() {
- return SingleKeyValueTransformer.create("date-formatting.timezone", (value) -> {
- ZoneId result = null;
- if (value instanceof String) {
- String timeZ = (String) value;
- if (timeZ.equalsIgnoreCase("default")) {
- return ZoneId.systemDefault();
- }
- try {
- return ZoneId.of(timeZ);
- } catch (ZoneRulesException ex) {
- logger.warn("Unknown region ID {} (ZoneRulesException)", timeZ);
- } catch (DateTimeException ex) {
- logger.warn("Invalid timezone {} (DateTimeException)", timeZ);
- }
- return null;
- }
- logger.info("Config option date-formatting.timezone invalid: {}", value);
- return result;
- });
- }
-
- private static ValueTransformer strictnessTransformer() {
- return SingleKeyValueTransformer.create("enforcement.address-strictness", (value) -> {
- if (value instanceof AddressStrictness) {
- return value;
- }
- AddressStrictness result = null;
- if (value instanceof String) {
- String addrS = (String) value;
- try {
- result = AddressStrictness.valueOf(addrS);
- } catch (IllegalArgumentException ignored) {}
- }
- if (result == null) {
- //result = AddressStrictness.NORMAL;
- logger.info("Config option enforcement.address-strictness invalid: {}", value);
- }
- return result;
- });
- }
-
- private static ValueTransformer syncEnforcementTransformer() {
- return SingleKeyValueTransformer.create("enforcement.sync-events-strategy", (value) -> {
- SyncEnforcement result = null;
- if (value instanceof String) {
- String syncE = (String) value;
- try {
- result = SyncEnforcement.valueOf(syncE);
- } catch (IllegalArgumentException ignored) {}
- }
- if (result == null) {
- //result = SyncEnforcement.ALLOW;
- logger.info("Config option enforcement.sync-events-strategy invalid: {}", value);
- }
- return result;
- });
- }
-
- private static ValueTransformer prefixTransformer() {
- return SingleKeyValueTransformer.create("all.prefix.value", (value) -> {
- if (value instanceof String) {
- return parseMessage(false, (String) value);
- }
- logger.info("Bad config value for all.prefix.value {}", value);
- return null;
- });
- }
-
- private static ValueTransformer combinedListStringTransformer(String key) {
- return SingleKeyValueTransformer.create(key, (value) -> {
- if (value instanceof List) {
- List> messages = (List>) value;
-
- StringBuilder result = new StringBuilder();
- for (int n = 0; n < messages.size(); n++) {
- if (n != 0) {
- result.append('\n');
- }
- Object element = messages.get(n);
- if (!(element instanceof String)) {
- logger.info("Bad list element for {} {}", key, element);
- return null;
- }
- result.append(element);
- }
- return result.toString();
- }
- logger.info("Bad config value for {} {}", key, value);
- return null;
- });
- }
-
- static List configTransformers() {
- List result = new ArrayList<>();
- result.add(dateFormatTransformer());
- result.add(dateZoneTransformer());
- result.add(strictnessTransformer());
- result.add(syncEnforcementTransformer());
- result.addAll(UUIDMaster.createValueTransformers());
- return result;
- }
-
- static List messagesTransformers() {
- List result = new ArrayList<>();
- result.add(prefixTransformer());
- result.add(combinedListStringTransformer("all.usage"));
- for (PunishmentType type : MiscUtil.punishmentTypes()) {
- result.add(combinedListStringTransformer("additions." + type.getLowercaseNamePlural() + ".layout"));
- }
- return result;
- }
-
- static SendableMessage parseMessage(boolean useJson, String rawMessage) {
- JsonMode jsonMode = (useJson) ? JsonMode.JSON_SK : JsonMode.NONE;
- return parser.parseMessage(rawMessage, ColourMode.ALL_COLOURS, jsonMode);
- }
-
-}
-
-enum Translation {
- EN
-}
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/config/Configs.java b/bans-core/src/main/java/space/arim/libertybans/core/config/Configs.java
index b359634f2..cd2aac7a8 100644
--- a/bans-core/src/main/java/space/arim/libertybans/core/config/Configs.java
+++ b/bans-core/src/main/java/space/arim/libertybans/core/config/Configs.java
@@ -19,134 +19,117 @@
package space.arim.libertybans.core.config;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.UncheckedIOException;
+import java.nio.channels.Channels;
+import java.nio.channels.FileChannel;
+import java.nio.channels.ReadableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.util.List;
+import java.nio.file.StandardOpenOption;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.Executor;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-import space.arim.api.configure.ConfigAccessor;
-import space.arim.api.configure.ConfigSerialiser;
-import space.arim.api.configure.Configuration;
-import space.arim.api.configure.JarResources;
-import space.arim.api.configure.ValueTransformer;
-import space.arim.api.configure.configs.ConfigurationBuilder;
-import space.arim.api.configure.configs.SingularConfig;
-import space.arim.api.configure.yaml.YamlConfigSerialiser;
import space.arim.libertybans.bootstrap.StartupException;
-import space.arim.libertybans.core.LibertyBansCore;
import space.arim.libertybans.core.Part;
-import space.arim.libertybans.core.database.DatabaseManager;
-import space.arim.libertybans.core.database.IOUtils;
public class Configs implements Part {
- private final LibertyBansCore core;
+ private final Path folder;
- private volatile ConfigPackage configPackage;
+ private final ConfigHolder mainHolder = new ConfigHolder<>(MainConfig.class);
+ private final ConfigHolder messagesHolder = new ConfigHolder<>(MessagesConfig.class);
+ private final ConfigHolder sqlHolder = new ConfigHolder<>(SqlConfig.class);
- public Configs(LibertyBansCore core) {
- this.core = core;
+ public Configs(Path folder) {
+ this.folder = folder;
}
- private static CompletableFuture extends Configuration> getFor(ConfigSerialiser serialiser, String resourceName,
- List transformers, Executor readWriteService) {
- return new ConfigurationBuilder()
- .defaultResource(JarResources.forCallerClass(resourceName))
- .executor(readWriteService).serialiser(serialiser)
- .addTransformers(transformers).buildMergingConfig();
+ public MainConfig getMainConfig() {
+ return mainHolder.getConfigData();
}
- private ConfigPackage recreateConfig(Executor existingReadWriteService) {
- Executor executor;
- if (existingReadWriteService == null) {
- executor = core.getEnvironment().getPlatformHandle().getRealExecutorFinder().findExecutor();
- if (executor instanceof ExecutorService) {
- // Danger! Don't shut down the platform's combined thread pool
- // readWriteService must NOT be an instance of ExecutorService unless shutdown is desired
- executor = new IOUtils.SafeExecutorWrapper(executor);
-
- } else if (executor == null) {
- executor = Executors.newCachedThreadPool(new IOUtils.ThreadFactoryImpl("LibertyBans-Config-"));
- }
- } else {
- executor = existingReadWriteService;
- }
-
- Path folder = core.getFolder();
+ public MessagesConfig getMessagesConfig() {
+ return messagesHolder.getConfigData();
+ }
+
+ public SqlConfig getSqlConfig() {
+ return sqlHolder.getConfigData();
+ }
+
+ public CompletableFuture reloadConfigs() {
Path langFolder = folder.resolve("lang");
try {
Files.createDirectories(langFolder);
} catch (IOException ex) {
- throw new StartupException("Unable to create plugin directories", ex);
+ throw new UncheckedIOException("Unable to create plugin directories", ex);
}
- ConfigSerialiser serialiser = new YamlConfigSerialiser();
- var futureSql = getFor(serialiser, "sql.yml", sqlValueTransformers(), executor);
- var futureConfig = getFor(serialiser, "config.yml", configValueTransformers(), executor);
- var futureMessages = getFor(serialiser, "lang/messages_en.yml", ConfigUtil.messagesTransformers(), executor);
- SingularConfig sql = new SingularConfig(futureSql.join(), folder.resolve("sql.yml"));
- SingularConfig config = new SingularConfig(futureConfig.join(), folder.resolve("config.yml"));
- Configuration messages = futureMessages.join();
+ // Save default language files
+ CompletableFuture> futureLangFiles = createLangFiles(langFolder);
- return new ConfigPackage(sql, config, messages, executor, langFolder);
+ // Reload main config
+ CompletableFuture reloadMain = mainHolder.reload(folder.resolve("config.yml"));
+ // Reload sql config
+ CompletableFuture reloadSql = sqlHolder.reload(folder.resolve("sql.yml"));
+
+ // Reload messages config from specified language file
+ CompletableFuture reloadMessages = CompletableFuture.allOf(futureLangFiles, reloadMain)
+ .thenCompose((ignore) -> {
+ String langFileOption = mainHolder.getConfigData().langFile();
+ return messagesHolder.reload(langFolder.resolve("messages_" + langFileOption + ".yml"));
+ });
+ return CompletableFuture.allOf(reloadMessages, reloadSql).thenApply((ignore) -> {
+ return reloadMain.join() && reloadMessages.join() && reloadSql.join();
+ });
}
- List sqlValueTransformers() {
- return DatabaseManager.createConfigTransformers();
+ private CompletableFuture> createLangFiles(Path langFolder) {
+ Set> futureLangFiles = new HashSet<>();
+ for (Translation translation : Translation.values()) {
+
+ final String name = "messages_" + translation.name().toLowerCase(Locale.ROOT) + ".yml";
+ Path messagesPath = langFolder.resolve(name);
+ if (!Files.exists(messagesPath)) {
+ futureLangFiles.add(CompletableFuture.runAsync(() -> {
+
+ try (InputStream inputStream = getClass().getResource("/lang/" + name).openStream();
+ ReadableByteChannel source = Channels.newChannel(inputStream);
+ FileChannel dest = FileChannel.open(messagesPath, StandardOpenOption.WRITE,
+ StandardOpenOption.CREATE_NEW)) {
+
+ dest.transferFrom(source, 0, Long.MAX_VALUE);
+ } catch (IOException ex) {
+ throw new UncheckedIOException("Unable to copy language file for language " + name, ex);
+ }
+ }));
+ }
+ }
+ return CompletableFuture.allOf(futureLangFiles.toArray(CompletableFuture[]::new));
}
- List configValueTransformers() {
- return ConfigUtil.configTransformers();
+ private enum Translation {
+
}
@Override
public void startup() {
- ConfigPackage configPackage = recreateConfig(null);
- if (!configPackage.reloadConfigs().join()) {
+ if (!reloadConfigs().join()) {
throw new StartupException("Issue while loading configuration");
}
- this.configPackage = configPackage;
}
@Override
public void restart() {
- ConfigPackage existing = this.configPackage;
- ConfigPackage configPackage = recreateConfig(existing.readWriteService);
- if (!configPackage.reloadConfigs().join()) {
+ if (!reloadConfigs().join()) {
throw new StartupException("Issue while reloading configuration");
}
- this.configPackage = configPackage;
}
@Override
public void shutdown() {
- Executor executor = configPackage.readWriteService;
- if (executor instanceof ExecutorService) {
- ((ExecutorService) executor).shutdown();
- }
- }
-
- public CompletableFuture reloadConfigs() {
- return configPackage.reloadConfigs();
- }
-
- public ConfigAccessor getSql() {
- return configPackage.sql.getAccessor();
- }
-
- public ConfigAccessor getConfig() {
- return configPackage.config.getAccessor();
- }
-
- public ConfigAccessor getMessages() {
- return configPackage.messages.getAccessor();
- }
-
- public AddressStrictness getAddressStrictness() {
- return getConfig().getObject("enforcement.address-strictness", AddressStrictness.class);
+
}
}
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/config/DateTimeFormatterWithPattern.java b/bans-core/src/main/java/space/arim/libertybans/core/config/DateTimeFormatterWithPattern.java
new file mode 100644
index 000000000..e2074f867
--- /dev/null
+++ b/bans-core/src/main/java/space/arim/libertybans/core/config/DateTimeFormatterWithPattern.java
@@ -0,0 +1,81 @@
+/*
+ * LibertyBans-core
+ * Copyright © 2020 Anand Beh
+ *
+ * LibertyBans-core is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * LibertyBans-core 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with LibertyBans-core. If not, see
+ * and navigate to version 3 of the GNU Affero General Public License.
+ */
+package space.arim.libertybans.core.config;
+
+import java.time.format.DateTimeFormatter;
+
+/**
+ * {@link DateTimeFormatter} does not retain the pattern string it was created with,
+ * therefore this wrapper is required for reserialisation.
+ *
+ * This class is public so that it may be used in dynamic proxies
+ *
+ * @author A248
+ *
+ */
+public class DateTimeFormatterWithPattern {
+
+ private final String pattern;
+ private transient final DateTimeFormatter formatter;
+
+ /**
+ * Creates from a pattern string
+ *
+ * @param pattern the pattern string
+ * @throws IllegalArgumentException if the pattern is not valid
+ */
+ DateTimeFormatterWithPattern(String pattern) {
+ this.pattern = pattern;
+ formatter = DateTimeFormatter.ofPattern(pattern);
+ }
+
+ String getPattern() {
+ return pattern;
+ }
+
+ DateTimeFormatter getFormatter() {
+ return formatter;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + pattern.hashCode();
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (this == object) {
+ return true;
+ }
+ if (!(object instanceof DateTimeFormatterWithPattern)) {
+ return false;
+ }
+ DateTimeFormatterWithPattern other = (DateTimeFormatterWithPattern) object;
+ return pattern.equals(other.pattern);
+ }
+
+ @Override
+ public String toString() {
+ return "DateTimeFormatterWithPattern [pattern=" + pattern + "]";
+ }
+
+}
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/config/Formatter.java b/bans-core/src/main/java/space/arim/libertybans/core/config/Formatter.java
index d39e9d559..ecbd8bcc5 100644
--- a/bans-core/src/main/java/space/arim/libertybans/core/config/Formatter.java
+++ b/bans-core/src/main/java/space/arim/libertybans/core/config/Formatter.java
@@ -18,39 +18,41 @@
*/
package space.arim.libertybans.core.config;
+import java.time.Duration;
import java.time.Instant;
import java.time.ZoneId;
-import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
+import java.util.EnumMap;
import java.util.List;
-import java.util.Locale;
import java.util.Map;
-import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.function.UnaryOperator;
import space.arim.omnibus.util.concurrent.CentralisedFuture;
import space.arim.uuidvault.api.UUIDUtil;
import space.arim.api.chat.SendableMessage;
-import space.arim.api.configure.ConfigAccessor;
+import space.arim.api.chat.manipulator.SendableMessageManipulator;
+import space.arim.api.chat.serialiser.JsonSkSerialiser;
import space.arim.libertybans.api.AddressVictim;
import space.arim.libertybans.api.Operator;
import space.arim.libertybans.api.PlayerOperator;
import space.arim.libertybans.api.PlayerVictim;
-import space.arim.libertybans.api.Punishment;
import space.arim.libertybans.api.PunishmentType;
-import space.arim.libertybans.api.Scope;
+import space.arim.libertybans.api.ServerScope;
import space.arim.libertybans.api.Victim;
+import space.arim.libertybans.api.manager.PunishmentFormatter;
+import space.arim.libertybans.api.punish.Punishment;
import space.arim.libertybans.core.LibertyBansCore;
-import space.arim.libertybans.core.MiscUtil;
+import space.arim.libertybans.core.punish.MiscUtil;
-public class Formatter {
+public class Formatter implements PunishmentFormatter {
private final LibertyBansCore core;
-
- private static final Entry[] timeUnits;
private static final long MARGIN_OF_INITIATION = 10; // seconds
@@ -58,115 +60,199 @@ public Formatter(LibertyBansCore core) {
this.core = core;
}
- public SendableMessage parseMessage(String rawMessage) {
- SendableMessage message = parseMessageNoPrefix(rawMessage);
- ConfigAccessor messages = core.getConfigs().getMessages();
- if (messages.getBoolean("all.prefix.enable")) {
- SendableMessage prefix = messages.getObject("all.prefix.value", SendableMessage.class);
- message = prefix.concatenate(message);
- }
- return message;
+ private MessagesConfig messages() {
+ return core.getMessagesConfig();
}
- private SendableMessage parseMessageNoPrefix(String rawMessage) {
- return ConfigUtil.parseMessage(isJsonEnabled(), rawMessage);
+ /**
+ * Parses a message to send including the prefix
+ *
+ * @param messageToParse the message to parse
+ * @return the message parsed including the prefix
+ */
+ public SendableMessage parseMessageWithPrefix(String messageToParse) {
+ return prefix(JsonSkSerialiser.getInstance().deserialise(messageToParse));
}
- public boolean isJsonEnabled() {
- return core.getConfigs().getMessages().getBoolean("formatting.enable-json");
+ /**
+ * Prefixes a message and returns the prefixed result
+ *
+ * @param message the message
+ * @return the message including the prefix
+ */
+ public SendableMessage prefix(SendableMessage message) {
+ return messages().all().prefix().concatenate(message);
}
/**
- * Gets, formats, and parses the punishment message for a punishment. Punishment messages
- * do not use the global prefix.
+ * Gets, formats, and parses the punishment message for a punishment.
*
* @param punishment the punishment
* @return a future yielding the formatted sendable message
*/
public CentralisedFuture getPunishmentMessage(Punishment punishment) {
- CentralisedFuture futureVictimFormatted = formatVictim(punishment.getVictim());
- CentralisedFuture futureOperatorFormatted = formatOperator(punishment.getOperator());
+ return formatWithPunishment(messages().additions().forType(punishment.getType()).layout(), punishment);
+ }
+
+ /**
+ * Parses and formats a message with a punishment
+ *
+ * @param manipulator the message manipulator
+ * @param punishment the punishment
+ * @return a future of the resulting formatted sendable message
+ */
+ public CentralisedFuture formatWithPunishment(SendableMessageManipulator manipulator,
+ Punishment punishment) {
+ return formatWithPunishment(manipulator, punishment, null);
+ }
+
+ /**
+ * Parses and formats a message with a punishment and an undoing operator. Used when punishments are revoked.
+ *
+ * @param manipulator the message manipulator
+ * @param punishment the punishment
+ * @param unOperator the operator undoing the punishment
+ * @return a future of the resulting formatted sendable message
+ */
+ public CentralisedFuture formatWithPunishmentAndUnoperator(SendableMessageManipulator manipulator,
+ Punishment punishment, Operator unOperator) {
+ return formatWithPunishment(manipulator, punishment, Objects.requireNonNull(unOperator, "unOperator"));
+ }
- return futureVictimFormatted.thenCombineAsync(futureOperatorFormatted, (victimFormatted, operatorFormatted) -> {
- String path = "additions." + punishment.getType().getLowercaseNamePlural() + ".layout";
- String message = core.getConfigs().getMessages().getString(path);
- return parseMessageNoPrefix(formatWithPunishment0(message, punishment, victimFormatted, operatorFormatted));
+ private CentralisedFuture formatWithPunishment(SendableMessageManipulator manipulator,
+ Punishment punishment, Operator unOperator) {
+
+ EnumMap> futureReplacements = new EnumMap<>(FutureReplaceable.class);
+ for (FutureReplaceable futureReplaceable : FutureReplaceable.values()) {
+
+ if (unOperator == null && futureReplaceable == FutureReplaceable.UNOPERATOR) {
+ continue;
+ }
+ if (manipulator.contains(futureReplaceable.getVariable())) {
+ CentralisedFuture replacement = getFutureReplacement(futureReplaceable, punishment, unOperator);
+ futureReplacements.put(futureReplaceable, replacement);
+ }
+ }
+ return core.getFuturesFactory().supplyAsync(() -> {
+ return formatWithPunishment0(manipulator, punishment, unOperator, futureReplacements);
});
}
- private String formatWithPunishment0(String message, Punishment punishment, String victimFormatted,
- String operatorFormatted) {
+ private enum SimpleReplaceable {
+ ID,
+ TYPE,
+ VICTIM_ID,
+ OPERATOR_ID,
+ UNOPERATOR_ID,
+ REASON,
+ SCOPE,
+ DURATION,
+ START_DATE,
+ TIME_PASSED,
+ END_DATE,
+ TIME_REMAINING;
+
+ String getVariable() {
+ return "%" + name() + "%";
+ }
+
+ }
+
+ private enum FutureReplaceable {
+ VICTIM,
+ OPERATOR,
+ UNOPERATOR;
+
+ String getVariable() {
+ return "%" + name() + "%";
+ }
+ }
+
+ private SendableMessage formatWithPunishment0(SendableMessageManipulator manipulator, Punishment punishment,
+ Operator unOperator, EnumMap> futureReplacements) {
final long now = MiscUtil.currentTime();
- final long start = punishment.getStart();
- final long end = punishment.getEnd();
+ final long start = punishment.getStartDateSeconds();
- long timePassed = now - start;
+ final long timePassed = now - start;
- String durationFormatted;
- String timeEndRelFormatted;
+ final String durationFormatted;
+ final String relativeEndFormatted;
- if (end == 0L) {
+ if (punishment.isPermanent()) {
// Permanent punishment
- ConfigAccessor config = core.getConfigs().getMessages();
- durationFormatted = config.getString("formatting.permanent-display.relative");
- timeEndRelFormatted = config.getString("formatting.permanent-display.absolute");
+ MessagesConfig.Formatting.PermanentDisplay display = messages().formatting().permanentDisplay();
+ durationFormatted = display.duration();
+ relativeEndFormatted = display.relative();
+
} else {
+ final long end = punishment.getEndDateSeconds();
+ assert end != 0 : end;
// Temporary punishment
long duration = end - start;
durationFormatted = formatRelative(duration);
- long timeRemaining;
// Using a margin of initiation prevents the "29 days, 23 hours, 59 minutes" issue
if (timePassed < MARGIN_OF_INITIATION) {
- timeRemaining = duration;
+ relativeEndFormatted = durationFormatted;
+
} else {
- timeRemaining = end - now;
+ long timeRemaining = end - now;
+ relativeEndFormatted = formatRelative(timeRemaining);
}
- timeEndRelFormatted = formatRelative(timeRemaining);
}
- return message
- .replace("%ID%", Integer.toString(punishment.getID()))
- .replace("%TYPE%", formatType(punishment.getType()))
- .replace("%VICTIM%", victimFormatted)
- .replace("%VICTIM_ID%", formatVictimId(punishment.getVictim()))
- .replace("%OPERATOR%", operatorFormatted)
- .replace("%REASON%", punishment.getReason())
- .replace("%SCOPE%", formatScope(punishment.getScope()))
- .replace("%DURATION%", durationFormatted)
- .replace("%TIME_START_ABS%", formatAbsolute(start))
- .replace("%TIME_START_REL%", formatRelative(timePassed))
- .replace("%TIME_END_ABS%", formatAbsolute(end))
- .replace("%TIME_END_REL%", timeEndRelFormatted);
+ EnumMap simpleReplacements = new EnumMap<>(SimpleReplaceable.class);
+ simpleReplacements.put(SimpleReplaceable.ID, Integer.toString(punishment.getID()));
+ simpleReplacements.put(SimpleReplaceable.TYPE, formatType(punishment.getType()));
+ simpleReplacements.put(SimpleReplaceable.VICTIM_ID, formatVictimId(punishment.getVictim()));
+ simpleReplacements.put(SimpleReplaceable.OPERATOR_ID, formatOperatorId(punishment.getOperator()));
+ if (unOperator != null)
+ simpleReplacements.put(SimpleReplaceable.UNOPERATOR_ID, formatOperatorId(unOperator));
+ simpleReplacements.put(SimpleReplaceable.REASON, punishment.getReason());
+ simpleReplacements.put(SimpleReplaceable.SCOPE, formatScope(punishment.getScope()));
+ simpleReplacements.put(SimpleReplaceable.DURATION, durationFormatted);
+ simpleReplacements.put(SimpleReplaceable.START_DATE, formatAbsoluteDate(punishment.getStartDate()));
+ simpleReplacements.put(SimpleReplaceable.TIME_PASSED, formatRelative(timePassed));
+ simpleReplacements.put(SimpleReplaceable.END_DATE, formatAbsoluteDate(punishment.getEndDate()));
+ simpleReplacements.put(SimpleReplaceable.TIME_REMAINING, relativeEndFormatted);
+
+ class Replacer implements UnaryOperator {
+ @Override
+ public String apply(String text) {
+ for (Map.Entry simpleReplacement : simpleReplacements.entrySet()) {
+ text = text.replace(
+ simpleReplacement.getKey().getVariable(),
+ simpleReplacement.getValue());
+ }
+ for (Map.Entry> futureReplacement : futureReplacements.entrySet()) {
+ text = text.replace(
+ futureReplacement.getKey().getVariable(),
+ futureReplacement.getValue().join());
+ }
+ return text;
+ }
+ }
+ return manipulator.replaceText(new Replacer());
}
- private String formatScope(Scope scope) {
- String scopeDisplay = core.getScopeManager().getServer(scope);
- if (scopeDisplay == null) {
- scopeDisplay = core.getConfigs().getMessages().getString("formatting.global-scope-display");
+
+ private CentralisedFuture getFutureReplacement(FutureReplaceable futureReplaceable, Punishment punishment,
+ Operator unOperator) {
+ switch (futureReplaceable) {
+ case VICTIM:
+ return formatVictim(punishment.getVictim());
+ case OPERATOR:
+ return formatOperator(punishment.getOperator());
+ case UNOPERATOR:
+ return formatOperator(unOperator);
+ default:
+ throw new IllegalArgumentException("Unknown replaceable " + futureReplaceable);
}
- return scopeDisplay;
}
- /**
- * Parses and formats a message with a punishment
- *
- * @param message the message
- * @param punishment the punishment
- * @return a future of the resulting formatted sendable message
- */
- public CentralisedFuture formatWithPunishment(String message, Punishment punishment) {
- CentralisedFuture futureVictimFormatted = formatVictim(punishment.getVictim());
- CentralisedFuture futureOperatorFormatted = formatOperator(punishment.getOperator());
-
- return futureVictimFormatted.thenCombineAsync(futureOperatorFormatted, (victimFormatted, operatorFormatted) -> {
- return parseMessage(formatWithPunishment0(message, punishment, victimFormatted, operatorFormatted));
- });
- }
-
private String formatType(PunishmentType type) {
- return type.toString();
+ return messages().formatting().punishmentTypeDisplay().forType(type);
}
private String formatVictimId(Victim victim) {
@@ -176,7 +262,7 @@ private String formatVictimId(Victim victim) {
case ADDRESS:
return formatAddressVictim((AddressVictim) victim);
default:
- throw new IllegalStateException("Unknown victim type " + victim.getType());
+ throw MiscUtil.unknownVictimType(victim.getType());
}
}
@@ -191,14 +277,25 @@ private CentralisedFuture formatVictim(Victim victim) {
case ADDRESS:
return core.getFuturesFactory().completedFuture(formatAddressVictim((AddressVictim) victim));
default:
- throw new IllegalStateException("Unknown victim type " + victim.getType());
+ throw MiscUtil.unknownVictimType(victim.getType());
}
}
- public CentralisedFuture formatOperator(Operator operator) {
+ private String formatOperatorId(Operator operator) {
switch (operator.getType()) {
case CONSOLE:
- return core.getFuturesFactory().completedFuture(core.getConfigs().getMessages().getString("formatting.console-display"));
+ return messages().formatting().consoleDisplay();
+ case PLAYER:
+ return UUIDUtil.toShortString(((PlayerOperator) operator).getUUID());
+ default:
+ throw MiscUtil.unknownOperatorType(operator.getType());
+ }
+ }
+
+ private CentralisedFuture formatOperator(Operator operator) {
+ switch (operator.getType()) {
+ case CONSOLE:
+ return core.getFuturesFactory().completedFuture(messages().formatting().consoleDisplay());
case PLAYER:
/*
* Similarly in #formatVictim, this should be a complete future every time we call this ourselves,
@@ -206,7 +303,7 @@ public CentralisedFuture formatOperator(Operator operator) {
*/
return core.getUUIDMaster().fullLookupName(((PlayerOperator) operator).getUUID());
default:
- throw new IllegalStateException("Unknown operator type " + operator.getType());
+ throw MiscUtil.unknownOperatorType(operator.getType());
}
}
@@ -214,54 +311,76 @@ private String formatAddressVictim(AddressVictim addressVictim) {
return addressVictim.getAddress().toString();
}
- private String formatAbsolute(long time) {
- ZoneId zoneId = core.getConfigs().getConfig().getObject("date-formatting.timezone", ZoneId.class);
- ZonedDateTime zonedDate = Instant.ofEpochSecond(time).atZone(zoneId);
- DateTimeFormatter formatter = core.getConfigs().getConfig().getObject("date-formatting.format", DateTimeFormatter.class);
- return formatter.format(zonedDate);
- }
-
- static {
- List> list = List.of(
- Map.entry("years", 31_536_000L), Map.entry("months", 2_592_000L),
- Map.entry("weeks", 604_800L), Map.entry("days", 86_400L),
- Map.entry("hours", 3_600L), Map.entry("minutes", 60L));
- @SuppressWarnings("unchecked")
- Entry[] asArray = (Entry[]) list.toArray(new Map.Entry, ?>[] {});
- timeUnits = asArray;
- }
-
private String formatRelative(long diff) {
if (diff < 0) {
return formatRelative(-diff);
}
- List segments = new ArrayList<>();
- for (Entry unit : timeUnits) {
- String unitName = unit.getKey();
- long unitLength = unit.getValue();
- if (diff > unitLength && core.getConfigs().getMessages().getBoolean("misc.time." + unitName + ".enable")) {
+ MessagesConfig.Misc.Time timeConfig = messages().misc().time();
+ Map fragments = timeConfig.fragments();
+ List segments = new ArrayList<>(fragments.size());
+ for (Map.Entry fragment : fragments.entrySet()) {
+ long unitLength = fragment.getKey().getDuration().toSeconds();
+ if (diff > unitLength) {
long amount = (diff / unitLength);
diff -= (amount * unitLength);
- segments.add(core.getConfigs().getMessages().getString("misc.time." + unitName + ".message")
- .replace('%' + unitName.toUpperCase(Locale.ENGLISH) + '%', Long.toString(amount)));
+ segments.add(fragment.getValue().replace("%VALUE%", Long.toString(amount)));
}
}
+ if (segments.isEmpty()) {
+ return timeConfig.fallbackSeconds().replace("%VALUE%", Long.toString(diff));
+ }
+ if (segments.size() == 1) {
+ return segments.get(0);
+ }
StringBuilder builder = new StringBuilder();
- final boolean comma = core.getConfigs().getMessages().getBoolean("misc.time.grammar.comma");
+ final boolean comma = timeConfig.useComma();
for (int n = 0; n < segments.size(); n++) {
boolean lastElement = n == segments.size() - 1;
if (lastElement) {
- builder.append(core.getConfigs().getMessages().getString("misc.time.grammar.and"));
- } else if (comma) {
- builder.append(',');
+ builder.append(timeConfig.and());
}
- if (n != 0) {
+ builder.append(segments.get(n));
+ if (!lastElement) {
+ if (comma) {
+ builder.append(',');
+ }
builder.append(" ");
}
- builder.append(segments.get(n));
}
return builder.toString();
}
+ private String formatScope(ServerScope scope) {
+ String scopeDisplay = core.getScopeManager().getServer(scope);
+ if (scopeDisplay == null) {
+ scopeDisplay = messages().formatting().globalScopeDisplay();
+ }
+ return scopeDisplay;
+ }
+
+ @Override
+ public ZoneId getTimezone() {
+ return core.getMainConfig().dateFormatting().zoneId();
+ }
+
+ @Override
+ public DateTimeFormatter getDateTimeFormatter() {
+ return core.getConfigs().getMainConfig().dateFormatting().formatAndPattern().getFormatter();
+ }
+
+ @Override
+ public String formatAbsoluteDate(Instant date) {
+ if (date.equals(Instant.MAX)) { // implicit null check
+ return messages().formatting().permanentDisplay().absolute();
+ }
+ return getDateTimeFormatter().format(date.atZone(getTimezone()));
+ }
+
+ @Override
+ public String formatDuration(Duration duration) {
+ Objects.requireNonNull(duration, "duration");
+ return formatRelative(duration.toSeconds());
+ }
+
}
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/config/ListSection.java b/bans-core/src/main/java/space/arim/libertybans/core/config/ListSection.java
new file mode 100644
index 000000000..d87a8514f
--- /dev/null
+++ b/bans-core/src/main/java/space/arim/libertybans/core/config/ListSection.java
@@ -0,0 +1,312 @@
+/*
+ * LibertyBans-core
+ * Copyright © 2020 Anand Beh
+ *
+ * LibertyBans-core is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * LibertyBans-core 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with LibertyBans-core. If not, see
+ * and navigate to version 3 of the GNU Affero General Public License.
+ */
+package space.arim.libertybans.core.config;
+
+import space.arim.api.chat.SendableMessage;
+import space.arim.api.chat.manipulator.SendableMessageManipulator;
+
+import space.arim.dazzleconf.annote.ConfDefault.DefaultInteger;
+import space.arim.dazzleconf.annote.ConfDefault.DefaultString;
+import space.arim.dazzleconf.annote.ConfDefault.DefaultStrings;
+import space.arim.dazzleconf.annote.ConfHeader;
+import space.arim.dazzleconf.annote.ConfKey;
+import space.arim.dazzleconf.annote.SubSection;
+
+@ConfHeader("Used for /banlist, /mutelist, /history, /warns, /blame")
+public interface ListSection {
+
+ interface PunishmentList {
+
+ SendableMessage usage();
+
+ int perPage();
+
+ SendableMessageManipulator noPages();
+
+ SendableMessageManipulator maxPages();
+
+ SendableMessage permissionCommand();
+
+ SendableMessageManipulator layoutHeader();
+
+ SendableMessageManipulator layoutBody();
+
+ SendableMessageManipulator layoutFooter();
+
+ }
+
+ interface BanList extends PunishmentList {
+
+ @Override
+ @DefaultString("&cUsage: /banlist &e[page]")
+ SendableMessage usage();
+
+ @Override
+ @DefaultInteger(10)
+ int perPage();
+
+ @Override
+ @DefaultString("&7There are no active bans.")
+ SendableMessageManipulator noPages();
+
+ @Override
+ @DefaultString("&7Page &e%PAGE%&7 does not exist. Max pages: &e%MAXPAGE%&7.")
+ SendableMessageManipulator maxPages();
+
+ @Override
+ @ConfKey("permission.command")
+ @DefaultString("&7You may not view the banlist.")
+ SendableMessage permissionCommand();
+
+ @Override
+ @ConfKey("layout.header")
+ @DefaultStrings({"&7[&eID&7] &e&oSubject",
+ "&7Operator &8/ &7Reason &8/ &7Time Remaining",
+ "&7"})
+ SendableMessageManipulator layoutHeader();
+
+ @Override
+ @ConfKey("layout.body")
+ @DefaultStrings({"&7[&e%ID%&7] &e&o%VICTIM%",
+ "&7%OPERATOR% &8/ &7%REASON% &8/ &7%TIME_END_REL%",
+ "&7"})
+ SendableMessageManipulator layoutBody();
+
+ @Override
+ @ConfKey("layout.footer")
+ @DefaultStrings({"&7Page &e%PAGE%&7/&e%MAXPAGE%&7.||ttp:Click for next page||cmd:/libertybans banlist %NEXTPAGE%"})
+ SendableMessageManipulator layoutFooter();
+
+ }
+
+ interface MuteList extends PunishmentList {
+
+ @Override
+ @DefaultString("&cUsage: /mutelist &e[page]")
+ SendableMessage usage();
+
+ @Override
+ @DefaultInteger(10)
+ int perPage();
+
+ @Override
+ @DefaultString("&7There are no active mutes.")
+ SendableMessageManipulator noPages();
+
+ @Override
+ @DefaultString("&7Page &e%PAGE%&7 does not exist. Max pages: &e%MAXPAGE%&7.")
+ SendableMessageManipulator maxPages();
+
+ @Override
+ @ConfKey("permission.command")
+ @DefaultString("&7You may not view the mutelist.")
+ SendableMessage permissionCommand();
+
+ @Override
+ @ConfKey("layout.header")
+ @DefaultStrings({"&7[&eID&7] &e&oSubject",
+ "&7Operator &8/ &7Reason &8/ &7Time Remaining",
+ "&7"})
+ SendableMessageManipulator layoutHeader();
+
+ @Override
+ @ConfKey("layout.body")
+ @DefaultStrings({"&7[&e%ID%&7] &e&o%VICTIM%",
+ "&7%OPERATOR% &8/ &7%REASON% &8/ &7%TIME_END_REL%",
+ "&7"})
+ SendableMessageManipulator layoutBody();
+
+ @Override
+ @ConfKey("layout.footer")
+ @DefaultStrings({"&7Page &e%PAGE%&7/&e%MAXPAGE%&7.||ttp:Click for next page||cmd:/libertybans mutelist %NEXTPAGE%"})
+ SendableMessageManipulator layoutFooter();
+
+ }
+
+ interface History extends PunishmentList {
+
+ @Override
+ @DefaultString("&cUsage: /history &e [page]")
+ SendableMessage usage();
+
+ @Override
+ @DefaultInteger(10)
+ int perPage();
+
+ @Override
+ @DefaultString("&c&o%TARGET%&r&7 has no history.")
+ SendableMessageManipulator noPages();
+
+ @Override
+ @DefaultString("&7Page &e%PAGE%&7 does not exist. Max pages: &e%MAXPAGE%&7.")
+ SendableMessageManipulator maxPages();
+
+ @Override
+ @ConfKey("permission.command")
+ @DefaultString("&7You may not view history.")
+ SendableMessage permissionCommand();
+
+ @Override
+ @ConfKey("layout.header")
+ @DefaultStrings({"&7[&eID&7] &r&8/ &7Punishment Type",
+ "&7Operator &8/ &7Reason &8/ &7Date Enacted",
+ "&7"})
+ SendableMessageManipulator layoutHeader();
+
+ @Override
+ @ConfKey("layout.body")
+ @DefaultStrings({"&7[&e%ID%&7] &r&7/ &7%TYPE%",
+ "&7%OPERATOR% &8/ &7REASON% &8/ &7%TIME_START_ABS%",
+ "&7"})
+ SendableMessageManipulator layoutBody();
+
+ @Override
+ @ConfKey("layout.footer")
+ @DefaultStrings({"&7Page &e%PAGE%&7/&e%MAXPAGE%&7.||ttp:Click for next page||cmd:/libertybans history %TARGET% %NEXTPAGE%"})
+ SendableMessageManipulator layoutFooter();
+
+ }
+
+ interface Warns extends PunishmentList {
+
+ @Override
+ @DefaultString("&cUsage: /warns &e [page]")
+ SendableMessage usage();
+
+ @Override
+ @DefaultInteger(10)
+ int perPage();
+
+ @Override
+ @DefaultString("&c&o%TARGET%&r&7 has no warns.")
+ SendableMessageManipulator noPages();
+
+ @Override
+ @DefaultString("&7Page &e%PAGE%&7 does not exist. Max pages: &e%MAXPAGE%&7.")
+ SendableMessageManipulator maxPages();
+
+ @Override
+ @ConfKey("permission.command")
+ @DefaultString("&7You may not view warns.")
+ SendableMessage permissionCommand();
+
+ @Override
+ @ConfKey("layout.header")
+ @DefaultStrings({"&7[&eID&7] Operator &8/ &7Reason &8/ &7Time Remaining",
+ "&7"})
+ SendableMessageManipulator layoutHeader();
+
+ @Override
+ @ConfKey("layout.body")
+ @DefaultStrings({"&7[&e%ID%&7] %OPERATOR% &8/ &7%REASON% &8/ &7%TIME_END_REL%",
+ "&7"})
+ SendableMessageManipulator layoutBody();
+
+ @Override
+ @ConfKey("layout.footer")
+ @DefaultStrings({"&7Page &e%PAGE%&7/&e%MAXPAGE%&7.||ttp:Click for next page||cmd:/libertybans warns %TARGET% %NEXTPAGE%"})
+ SendableMessageManipulator layoutFooter();
+
+ }
+
+ interface Blame extends PunishmentList {
+
+ @Override
+ @DefaultString("&cUsage: /blame &e [page]")
+ SendableMessage usage();
+
+ @Override
+ @DefaultInteger(10)
+ int perPage();
+
+ @Override
+ @DefaultString("&c&o%TARGET%&r&7 has not punished any players.")
+ SendableMessageManipulator noPages();
+
+ @Override
+ @DefaultString("&7Page &e%PAGE%&7 does not exist. Max pages: &e%MAXPAGE%&7.")
+ SendableMessageManipulator maxPages();
+
+ @Override
+ @ConfKey("permission.command")
+ @DefaultString("&7You may not use blame.")
+ SendableMessage permissionCommand();
+
+ @Override
+ @ConfKey("layout.header")
+ @DefaultStrings({"&7[&eID&7] &e&oSubject &r&8/ &7Punishment Type",
+ "&7Reason &8/ &7Date Enacted",
+ "&7"})
+ SendableMessageManipulator layoutHeader();
+
+ @Override
+ @ConfKey("layout.body")
+ @DefaultStrings({"&7[&e%ID%&7] &e&o%SUBJECT% &r&8 / &7%TYPE%",
+ "&7%REASON% &8/ &7%TIME_START_ABS%",
+ "&7"})
+ SendableMessageManipulator layoutBody();
+
+ @Override
+ @ConfKey("layout.footer")
+ @DefaultStrings({"&7Page &e%PAGE%&7/&e%MAXPAGE%&7.||ttp:Click for next page||cmd:/libertybans warns %TARGET% %NEXTPAGE%"})
+ SendableMessageManipulator layoutFooter();
+
+ }
+
+ enum ListType {
+ BANLIST,
+ MUTELIST,
+ HISTORY,
+ WARNS,
+ BLAME;
+ }
+
+ default PunishmentList forType(ListType type) {
+ switch (type) {
+ case BANLIST:
+ return banList();
+ case MUTELIST:
+ return muteList();
+ case HISTORY:
+ return history();
+ case WARNS:
+ return warns();
+ case BLAME:
+ return blame();
+ default:
+ throw new IllegalArgumentException("Unknown list type " + type);
+ }
+ }
+
+ @SubSection
+ BanList banList();
+
+ @SubSection
+ MuteList muteList();
+
+ @SubSection
+ History history();
+
+ @SubSection
+ Warns warns();
+
+ @SubSection
+ Blame blame();
+
+}
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/config/MainConfig.java b/bans-core/src/main/java/space/arim/libertybans/core/config/MainConfig.java
new file mode 100644
index 000000000..ceffb8d16
--- /dev/null
+++ b/bans-core/src/main/java/space/arim/libertybans/core/config/MainConfig.java
@@ -0,0 +1,110 @@
+/*
+ * LibertyBans-core
+ * Copyright © 2020 Anand Beh
+ *
+ * LibertyBans-core is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * LibertyBans-core 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with LibertyBans-core. If not, see
+ * and navigate to version 3 of the GNU Affero General Public License.
+ */
+package space.arim.libertybans.core.config;
+
+import java.time.ZoneId;
+import java.util.Set;
+
+import space.arim.libertybans.core.selector.EnforcementConfig;
+import space.arim.libertybans.core.uuid.UUIDResolutionConfig;
+
+import space.arim.dazzleconf.annote.ConfComments;
+import space.arim.dazzleconf.annote.ConfDefault.DefaultBoolean;
+import space.arim.dazzleconf.annote.ConfDefault.DefaultString;
+import space.arim.dazzleconf.annote.ConfDefault.DefaultStrings;
+import space.arim.dazzleconf.annote.ConfHeader;
+import space.arim.dazzleconf.annote.ConfKey;
+import space.arim.dazzleconf.annote.SubSection;
+
+@ConfHeader({
+ "",
+ "",
+ "The main LibertyBans configuration",
+ "All options here can be updated with /libertybans reload",
+ "",
+ ""})
+public interface MainConfig {
+
+ @ConfKey("lang-file")
+ @ConfComments({"What language file should be used for messages?",
+ "For example, 'en' means LibertyBans will look for a file called 'messages_en.yml'"})
+ @DefaultString("en")
+ String langFile();
+
+ @ConfKey("date-formatting")
+ @SubSection
+ DateFormatting dateFormatting();
+
+ @ConfHeader("Formatting of absolute dates")
+ interface DateFormatting {
+
+ @ConfKey("format")
+ @ConfComments("How should dates be formatted? Follows Java's DateTimeFormatter.ofPattern")
+ @DefaultString("dd/MM/yyyy kk:mm")
+ DateTimeFormatterWithPattern formatAndPattern();
+
+ @ConfKey("timezone")
+ @ConfComments({"Do you want to override the timezone? If 'default', the system default timezone is used",
+ "The value must be in continent/city format, such as 'America/New_York'. Uses Java's ZoneId.of"})
+ @DefaultString("default")
+ ZoneId zoneId();
+
+ }
+
+ @SubSection
+ Reasons reasons();
+
+ interface Reasons {
+
+ @ConfKey("permit-blank")
+ @ConfComments({"Are blank reasons permitted?",
+ "If blank reasons are not permitted, staff must specify the full reason",
+ "If blank reasons are permitted, the default reason is used"})
+ @DefaultBoolean(false)
+ boolean permitBlank();
+
+ @ConfKey("default-reason:")
+ @ConfComments("If the above is true, what is the default reason to use when staff do not specify a reason?")
+ @DefaultString("No reason stated.")
+ String defaultReason();
+
+ }
+
+ @SubSection
+ EnforcementConfig enforcement(); // Sensitive name used in integration testing
+
+ @ConfKey("player-uuid-resolution")
+ @SubSection
+ UUIDResolutionConfig uuidResolution();
+
+ @ConfKey("commands.aliases")
+ @ConfComments({"What commands should be registered as aliases for libertybans commands?",
+ "For each command listed here, '/' will be equal to '/libertybans '"})
+ @DefaultStrings({
+ "ban",
+ "mute",
+ "warn",
+ "kick",
+ "unban",
+ "unmute",
+ "unwarn"
+ })
+ Set commandAliases();
+
+}
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/config/MessagesConfig.java b/bans-core/src/main/java/space/arim/libertybans/core/config/MessagesConfig.java
new file mode 100644
index 000000000..6c774ac97
--- /dev/null
+++ b/bans-core/src/main/java/space/arim/libertybans/core/config/MessagesConfig.java
@@ -0,0 +1,280 @@
+/*
+ * LibertyBans-core
+ * Copyright © 2020 Anand Beh
+ *
+ * LibertyBans-core is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * LibertyBans-core 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with LibertyBans-core. If not, see
+ * and navigate to version 3 of the GNU Affero General Public License.
+ */
+package space.arim.libertybans.core.config;
+
+import java.time.temporal.ChronoUnit;
+import java.util.Map;
+import java.util.Set;
+
+import space.arim.api.chat.SendableMessage;
+import space.arim.api.chat.manipulator.SendableMessageManipulator;
+
+import space.arim.libertybans.api.PunishmentType;
+import space.arim.libertybans.core.punish.MiscUtil;
+
+import space.arim.dazzleconf.annote.ConfComments;
+import space.arim.dazzleconf.annote.ConfDefault.DefaultBoolean;
+import space.arim.dazzleconf.annote.ConfDefault.DefaultMap;
+import space.arim.dazzleconf.annote.ConfDefault.DefaultString;
+import space.arim.dazzleconf.annote.ConfDefault.DefaultStrings;
+import space.arim.dazzleconf.annote.ConfHeader;
+import space.arim.dazzleconf.annote.ConfKey;
+import space.arim.dazzleconf.annote.SubSection;
+
+@ConfHeader({
+ "",
+ "Messages configuration",
+ "",
+ "",
+ "In most cases, the variables inside the default messages are those available",
+ "in that specific message. The exception to this is messages which are related",
+ "to a certain punishment.",
+ "",
+ "When message has an associated punishment, multiple variables are available:",
+ "",
+ "%ID% - punishment ID number",
+ "%TYPE% - punishment type, e.g. 'Ban'",
+ "%VICTIM% - display name of the victim of the punishment",
+ "%VICTIM_ID% - internal identifier of victim",
+ "%OPERATOR% - display name of the staff member who made the punishment",
+ "%OPERATOR_ID% - internal identifier of the operator",
+ "%UNOPERATOR% - staff member undoing the punishment. available only when the punishment is undone",
+ "%UNOPERATOR_ID% - internal identifier of staff member undoing the punishment",
+ "%REASON% - reason for the punishment",
+ "%SCOPE% - scope of the punishment",
+ "%DURATION% - original duration (how long the punishment was made for)",
+ "%START_DATE% - the date the punishment was created",
+ "%TIME_PASSED% - the time since the punishment was created",
+ "%END_DATE% - the date the punishment will end, or formatting.permanent-display.absolute for permanent punishments",
+ "%TIME_REMAINING% - the time until the punishment ends, or formatting.permanent-display.relative for permanent punishments",
+ "",
+ ""})
+public interface MessagesConfig {
+
+ @SubSection
+ All all();
+
+ interface All {
+
+ @ConfKey("prefix.enable")
+ @ConfComments("If enabled, all messages will be prefixed")
+ @DefaultBoolean(true)
+ boolean enablePrefix();
+
+ @ConfKey("prefix.value")
+ @ConfComments("The prefix to use")
+ @DefaultString("&6&lLibertyBans &r&8»&7 ")
+ SendableMessage rawPrefix();
+
+ default SendableMessage prefix() {
+ return (enablePrefix()) ? rawPrefix() : SendableMessage.create();
+ }
+
+ @ConfKey("base-permission-message")
+ @ConfComments("If a player types /libertybans but does not have the permission 'libertybans.commands', "
+ + "this is the denial message")
+ @DefaultString("&cYou may not use this.")
+ SendableMessage basePermissionMessage();
+
+ @ConfKey("not-found")
+ @ConfComments("When issuing commands, if the specified player or IP was not found, what should the error message be?")
+ @SubSection
+ NotFound notFound();
+
+ interface NotFound {
+
+ @DefaultString("&c&o%TARGET%&r&7 was not found online or offline.")
+ SendableMessageManipulator uuid();
+
+ @DefaultString("&c&o%TARGET%&r&7 is not a valid IP address.")
+ SendableMessageManipulator ip();
+
+ @DefaultString("&c&o%TARGET%&r&7 is not a valid player or IP address.")
+ SendableMessageManipulator either();
+
+ }
+
+ @DefaultString("&cUnknown sub command. Displaying usage:")
+ SendableMessage usage();
+
+ }
+
+ @SubSection
+ Admin admin();
+
+ interface Admin {
+
+ @ConfKey("no-permission")
+ @DefaultString("&cSorry, you cannot use this.")
+ SendableMessage noPermission();
+
+ @DefaultString("&a...")
+ SendableMessage ellipses();
+
+ @DefaultString("&aReloaded")
+ SendableMessage reloaded();
+
+ @DefaultString("&aRestarted")
+ SendableMessage restarted();
+
+ }
+
+ @ConfComments("Specific formatting options")
+ @SubSection
+ Formatting formatting();
+
+ interface Formatting {
+
+ @ConfKey("permanent-arguments")
+ @ConfComments({
+ "There are 2 ways to make permanent punishments. The first is to not specify a time (/ban ).",
+ "The second is to specify a permanent amount of time (/ban perm ).",
+ "When typing commands, what time arguments will be counted as permanent?"})
+ @DefaultStrings({"perm", "permanent", "permanently"})
+ Set permanentArguments();
+
+ @ConfKey("permanent-display")
+ @ConfComments("How should 'permanent' be displayed as a length of time?")
+ @SubSection
+ PermanentDisplay permanentDisplay();
+
+ interface PermanentDisplay {
+
+ @ConfComments("What do you call a permanent duration?")
+ @DefaultString("Infinite")
+ String duration();
+
+ @ConfComments("How do you describe the time remaining in a permanent punishment?")
+ @DefaultString("Permanent")
+ String relative();
+
+ @ConfComments("When does a permanent punishment end?")
+ @DefaultString("Never")
+ String absolute();
+
+ }
+
+ @ConfKey("console-display")
+ @ConfComments("How should the console be displayed?")
+ @DefaultString("Console")
+ String consoleDisplay();
+
+ @ConfKey("global-scope-display")
+ @ConfComments("How should the global scope be displayed?")
+ @DefaultString("All servers")
+ String globalScopeDisplay();
+
+ @ConfKey("punishment-type-display")
+ @ConfComments("How should punishment types be displayed?")
+ @SubSection
+ PunishmentTypeDisplay punishmentTypeDisplay();
+
+ interface PunishmentTypeDisplay {
+
+ @DefaultString("Ban")
+ String ban();
+
+ @DefaultString("Mute")
+ String mute();
+
+ @DefaultString("Warn")
+ String warn();
+
+ @DefaultString("Kick")
+ String kick();
+
+ default String forType(PunishmentType type) {
+ switch (type) {
+ case BAN:
+ return ban();
+ case MUTE:
+ return mute();
+ case WARN:
+ return warn();
+ case KICK:
+ return kick();
+ default:
+ throw MiscUtil.unknownType(type);
+ }
+ }
+
+ }
+ }
+
+ @SubSection
+ AdditionsSection additions();
+
+ @SubSection
+ RemovalsSection removals();
+
+ @SubSection
+ ListSection lists();
+
+ @SubSection
+ Misc misc();
+
+ interface Misc {
+
+ @ConfKey("unknown-error")
+ @DefaultString("&cAn unknown error occurred.")
+ SendableMessage unknownError();
+
+ @ConfKey("sync-chat-denial-message")
+ @ConfComments("Only applicable if synchronous enforcement strategy is DENY in the main config")
+ @DefaultString("&cSynchronous chat denied. &7Please try again.")
+ SendableMessage syncDenialMessage();
+
+ @SubSection
+ @ConfComments("Concerns formatting of relative times and durations")
+ Time time();
+
+ interface Time {
+
+ @DefaultMap({
+ "YEARS", "%VALUE% years",
+ "MONTHS", "%VALUE% months",
+ "WEEKS", "%VALUE% weeks",
+ "DAYS", "%VALUE% days",
+ "HOURS", "%VALUE% hours",
+ "MINUTES", "%VALUE% minutes"})
+ Map fragments();
+
+ @ConfKey("fallback-seconds")
+ @ConfComments({
+ "Times are formatted to seconds accuracy, but you may not want to display seconds ",
+ "for most times. However, for very small durations, you need to display a value in seconds.",
+ "If you are using SECONDS in the above section, this value is meaningless."})
+ @DefaultString("%VALUE% seconds")
+ String fallbackSeconds();
+
+ @ConfKey("grammar.comma")
+ @ConfComments("If enabled, places commas after each time fragment, except the last one")
+ @DefaultBoolean(true)
+ boolean useComma();
+
+ @ConfKey("grammar.and")
+ @ConfComments("What should come before the last fragment? Set to empty text to disable")
+ @DefaultString("and ")
+ String and();
+
+ }
+
+ }
+
+}
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/config/RemovalsSection.java b/bans-core/src/main/java/space/arim/libertybans/core/config/RemovalsSection.java
new file mode 100644
index 000000000..40d0d8b08
--- /dev/null
+++ b/bans-core/src/main/java/space/arim/libertybans/core/config/RemovalsSection.java
@@ -0,0 +1,161 @@
+/*
+ * LibertyBans-core
+ * Copyright © 2020 Anand Beh
+ *
+ * LibertyBans-core is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * LibertyBans-core 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with LibertyBans-core. If not, see
+ * and navigate to version 3 of the GNU Affero General Public License.
+ */
+package space.arim.libertybans.core.config;
+
+import space.arim.api.chat.SendableMessage;
+import space.arim.api.chat.manipulator.SendableMessageManipulator;
+
+import space.arim.libertybans.api.PunishmentType;
+import space.arim.libertybans.core.punish.MiscUtil;
+
+import space.arim.dazzleconf.annote.ConfDefault.DefaultString;
+import space.arim.dazzleconf.annote.ConfHeader;
+import space.arim.dazzleconf.annote.ConfKey;
+import space.arim.dazzleconf.annote.SubSection;
+
+@ConfHeader("Regards /unban, /unmute, /unwarn")
+public interface RemovalsSection {
+
+ interface PunishmentRemoval {
+
+ SendableMessage usage();
+
+ SendableMessage permissionCommand();
+
+ SendableMessageManipulator notFound();
+
+ SendableMessageManipulator successMessage();
+
+ SendableMessageManipulator successNotification();
+
+ }
+
+ interface BanRemoval extends PunishmentRemoval {
+
+ @Override
+ @DefaultString("&cUsage: /unban &e&c.")
+ SendableMessage usage();
+
+ @Override
+ @ConfKey("permission.command")
+ @DefaultString("&cYou may not unban other players.")
+ SendableMessage permissionCommand();
+
+ @Override
+ @ConfKey("not-found")
+ @DefaultString("&c&o%TARGET%&r&7 is not banned.")
+ SendableMessageManipulator notFound();
+
+ @Override
+ @ConfKey("success.message")
+ @DefaultString("&7Unbanned &c&o%VICTIM%&r&7.")
+ SendableMessageManipulator successMessage();
+
+ @Override
+ @ConfKey("success.notification")
+ @DefaultString("&c&o%UNOPERATOR%&r&7 unbanned &c&o%TARGET%&r&7.")
+ SendableMessageManipulator successNotification();
+
+ }
+
+ interface MuteRemoval extends PunishmentRemoval {
+
+ @Override
+ @DefaultString("&cUsage: /unmute &e&c.")
+ SendableMessage usage();
+
+ @Override
+ @ConfKey("permission.command")
+ @DefaultString("&cYou may not unmute other players.")
+ SendableMessage permissionCommand();
+
+ @Override
+ @ConfKey("not-found")
+ @DefaultString("&c&o%TARGET%&r&7 is not muted.")
+ SendableMessageManipulator notFound();
+
+ @Override
+ @ConfKey("success.message")
+ @DefaultString("&7Unmuted &c&o%VICTIM%&r&7.")
+ SendableMessageManipulator successMessage();
+
+ @Override
+ @ConfKey("success.notification")
+ @DefaultString("&c&o%UNOPERATOR%&r&7 unmuted &c&o%TARGET%&r&7.")
+ SendableMessageManipulator successNotification();
+
+ }
+
+ interface WarnRemoval extends PunishmentRemoval {
+
+ @Override
+ @DefaultString("&cUsage: /unwarn &e &c.")
+ SendableMessage usage();
+
+ @Override
+ @ConfKey("permission.command")
+ @DefaultString("&cYou may not unwarn other players.")
+ SendableMessage permissionCommand();
+
+ @Override
+ @ConfKey("not-found")
+ @DefaultString("&c&o%TARGET%&r&7 does not have a warn by &c&o%ID%&r&7.")
+ SendableMessageManipulator notFound();
+
+ @ConfKey("not-a-number")
+ @DefaultString("&c&o%ID_ARG%&r&7 is not a number.")
+ SendableMessageManipulator notANumber();
+
+ @Override
+ @ConfKey("success.message")
+ @DefaultString("&7Unmuted &c&o%VICTIM%&r&7.")
+ SendableMessageManipulator successMessage();
+
+ @Override
+ @ConfKey("success.notification")
+ @DefaultString("&c&o%UNOPERATOR%&r&7 unmuted &c&o%VICTIM%&r&7.")
+ SendableMessageManipulator successNotification();
+
+ }
+
+ @SubSection
+ BanRemoval bans();
+
+ @SubSection
+ MuteRemoval mutes();
+
+ @SubSection
+ WarnRemoval warns();
+
+ default PunishmentRemoval forType(PunishmentType type) {
+ switch (type) {
+ case BAN:
+ return bans();
+ case MUTE:
+ return mutes();
+ case WARN:
+ return warns();
+ case KICK:
+ throw new IllegalArgumentException("Cannot undo kicks");
+ default:
+ throw MiscUtil.unknownType(type);
+ }
+ }
+
+}
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/config/SqlConfig.java b/bans-core/src/main/java/space/arim/libertybans/core/config/SqlConfig.java
new file mode 100644
index 000000000..12cb9dd83
--- /dev/null
+++ b/bans-core/src/main/java/space/arim/libertybans/core/config/SqlConfig.java
@@ -0,0 +1,142 @@
+/*
+ * LibertyBans-core
+ * Copyright © 2020 Anand Beh
+ *
+ * LibertyBans-core is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * LibertyBans-core 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with LibertyBans-core. If not, see
+ * and navigate to version 3 of the GNU Affero General Public License.
+ */
+package space.arim.libertybans.core.config;
+
+import java.util.Map;
+
+import space.arim.libertybans.core.database.Vendor;
+
+import space.arim.dazzleconf.annote.ConfComments;
+import space.arim.dazzleconf.annote.ConfDefault.DefaultBoolean;
+import space.arim.dazzleconf.annote.ConfDefault.DefaultInteger;
+import space.arim.dazzleconf.annote.ConfDefault.DefaultMap;
+import space.arim.dazzleconf.annote.ConfDefault.DefaultString;
+import space.arim.dazzleconf.annote.ConfHeader;
+import space.arim.dazzleconf.annote.ConfKey;
+import space.arim.dazzleconf.annote.SubSection;
+
+@ConfHeader({
+ "",
+ "SQL Database settings",
+ "",
+ "For most servers, the default options here are perfect. Most users will not need to bother here.",
+ "",
+ "However, for servers wishing to use MariaDB / MySQL, or for large servers wishing to tweak performance,",
+ "further setup is required.",
+ "",
+ "",
+ "Note well:",
+ "To apply changes made here, use '/libertybans restart' or restart the server.",
+ "Using '/libertybans reload' will NOT update your database settings!",
+ "",
+ "",
+ ""})
+public interface SqlConfig {
+
+ @ConfKey("rdms-vendor")
+ @ConfComments({
+ "What RDMS vendor will you be using?",
+ "Available options:",
+ "'HSQLDB' - Local HyperSQL database. No additional setup required.",
+ "'MARIADB' - Requires a separate MariaDB or MySQL database."})
+ @DefaultString("HSQLDB")
+ Vendor vendor(); // Sensitive name used in integration testing
+
+ @ConfKey("connection-pool-size")
+ @ConfComments({
+ "",
+ "How large should the connection pool be?",
+ "A thread pool of similar size is derived from the connection pool size.",
+ "For most servers, the default option is suitable."})
+ @DefaultInteger(6)
+ int poolSize();
+
+ @SubSection
+ @ConfComments({
+ "",
+ "Connection timeout settings",
+ "LibertyBans uses HikariCP for connection pooling. The following settings control connection timeouts."})
+ Timeouts timeouts();
+
+ interface Timeouts {
+
+ @ConfKey("connection-timeout-seconds")
+ @ConfComments({
+ "How long, at maximum, should LibertyBans wait when acquiring new connections, "
+ + "if no existing connection is available?"})
+ @DefaultInteger(14)
+ int connectionTimeoutSeconds();
+
+ @ConfKey("max-lifetime-minutes")
+ @ConfComments({
+ "How long, at maxium, should a connection in the pool last before having to be recreated?",
+ "\"This value should be set for MariaDB or MySQL. HikariCP notes:",
+ "\"It should be several seconds shorter than any database or infrastructure imposed connection time limit\""})
+ @DefaultInteger(25)
+ int maxLifetimeMinutes();
+
+ }
+
+ @ConfKey("auth-details")
+ @SubSection
+ @ConfComments({
+ "MariaDB / MySQL authentication details",
+ "These must be changed to the proper credentials in order to use MariaDB/MySQL"})
+ AuthDetails authDetails(); // Sensitive name used in integration testing
+
+ interface AuthDetails {
+
+ @DefaultString("localhost")
+ String host();
+
+ @DefaultInteger(3306)
+ int port();
+
+ @DefaultString("bans")
+ String database();
+
+ @ConfKey("user")
+ @DefaultString("username")
+ String username();
+
+ @DefaultString("defaultpass")
+ String password();
+
+ }
+
+ @ConfKey("mariadb-connection-properties")
+ @ConfComments({
+ "Connection properties to be applied to database connections to the MariaDB / MySQL database",
+ "These are specified in key-value format. Has no effect on HSQLDB"
+ })
+ @DefaultMap({
+ "useUnicode", "true",
+ "characterEncoding", "UTF-8",
+ "useServerPrepStmts", "true",
+ "cachePrepStmts", "true",
+ "prepStmtCacheSize", "25",
+ "prepStmtCacheSqlLimit", "256"})
+ Map connectionProperties();
+
+ @ConfKey("use-traditional-jdbc-url")
+ @ConfComments("Legacy option. Don't touch this unless you understand it or you're told to enable it.")
+ @DefaultBoolean(false)
+ boolean useTraditionalJdbcUrl();
+
+}
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/Database.java b/bans-core/src/main/java/space/arim/libertybans/core/database/Database.java
index 3f24e130f..6b2836bcf 100644
--- a/bans-core/src/main/java/space/arim/libertybans/core/database/Database.java
+++ b/bans-core/src/main/java/space/arim/libertybans/core/database/Database.java
@@ -43,14 +43,16 @@
import space.arim.libertybans.api.AddressVictim;
import space.arim.libertybans.api.Operator;
import space.arim.libertybans.api.PlayerVictim;
-import space.arim.libertybans.api.PunishmentDatabase;
import space.arim.libertybans.api.PunishmentType;
-import space.arim.libertybans.api.Scope;
+import space.arim.libertybans.api.ServerScope;
import space.arim.libertybans.api.Victim;
import space.arim.libertybans.api.Victim.VictimType;
+import space.arim.libertybans.api.manager.PunishmentDatabase;
import space.arim.libertybans.core.LibertyBansCore;
+import space.arim.libertybans.core.punish.MiscUtil;
import space.arim.jdbcaesar.JdbCaesar;
+import space.arim.jdbcaesar.QuerySource;
import space.arim.jdbcaesar.adapter.EnumNameAdapter;
import space.arim.jdbcaesar.builder.JdbCaesarBuilder;
import space.arim.jdbcaesar.transact.IsolationLevel;
@@ -80,7 +82,7 @@ public class Database implements PunishmentDatabase {
this.core = core;
this.vendor = vendor;
- executor = Executors.newFixedThreadPool(poolSize, new IOUtils.ThreadFactoryImpl("LibertyBans-Database-"));
+ executor = Executors.newFixedThreadPool(poolSize, core.newThreadFactory("Database"));
HikariWrapper connectionSource = new HikariWrapper(new HikariDataSource(hikariConf));
JdbCaesarBuilder jdbCaesarBuilder = new JdbCaesarBuilder()
@@ -88,6 +90,7 @@ public class Database implements PunishmentDatabase {
.exceptionHandler((ex) -> {
logger.error("Error while executing a database query", ex);
})
+ .defaultFetchSize(1000)
.defaultIsolation(IsolationLevel.REPEATABLE_READ)
.rewrapExceptions(true)
.addAdapters(
@@ -97,14 +100,14 @@ public class Database implements PunishmentDatabase {
new JdbCaesarHelper.OperatorAdapter(),
new JdbCaesarHelper.ScopeAdapter(core.getScopeManager()),
new JdbCaesarHelper.UUIDBytesAdapter(),
- new JdbCaesarHelper.InetAddressAdapter());
+ new JdbCaesarHelper.NetworkAddressAdapter());
jdbCaesar = jdbCaesarBuilder.build();
}
void startRefreshTaskIfNecessary() {
if (getVendor() != Vendor.MARIADB) {
synchronized (this) {
- hyperSqlRefreshTask = core.getResources().getEnhancedExecutor().scheduleRepeating(
+ hyperSqlRefreshTask = core.getEnhancedExecutor().scheduleRepeating(
new RefreshTaskRunnable(core.getDatabaseManager(), this),
Duration.ofHours(1L), DelayCalculators.fixedDelay());
}
@@ -194,15 +197,15 @@ public PunishmentType getTypeFromResult(ResultSet resultSet) throws SQLException
}
public Victim getVictimFromResult(ResultSet resultSet) throws SQLException {
- VictimType vType = VictimType.valueOf(resultSet.getString("victim_type"));
+ VictimType victimType = VictimType.valueOf(resultSet.getString("victim_type"));
byte[] bytes = resultSet.getBytes("victim");
- switch (vType) {
+ switch (victimType) {
case PLAYER:
return PlayerVictim.of(UUIDUtil.fromByteArray(bytes));
case ADDRESS:
return AddressVictim.of(bytes);
default:
- throw new IllegalStateException("Unknown victim type " + vType);
+ throw MiscUtil.unknownVictimType(victimType);
}
}
@@ -214,7 +217,7 @@ public String getReasonFromResult(ResultSet resultSet) throws SQLException {
return resultSet.getString("reason");
}
- public Scope getScopeFromResult(ResultSet resultSet) throws SQLException {
+ public ServerScope getScopeFromResult(ResultSet resultSet) throws SQLException {
String server = resultSet.getString("scope");
if (server != null) {
return core.getScopeManager().specificScope(server);
@@ -229,5 +232,20 @@ public long getStartFromResult(ResultSet resultSet) throws SQLException {
public long getEndFromResult(ResultSet resultSet) throws SQLException {
return resultSet.getLong("end");
}
+
+ public void clearExpiredPunishments(QuerySource> querySource, PunishmentType type, long currentTime) {
+ assert type != PunishmentType.KICK;
+ String query;
+ if (getVendor().hasDeleteFromJoin()) {
+ query = "DELETE `thetype` FROM `libertybans_" + type.getLowercaseNamePlural() + "` `thetype` "
+ + "INNER JOIN `libertybans_punishments` `puns` ON `puns`.`id` = `thetype`.`id` WHERE "
+ + "`puns`.`end` != 0 AND `puns`.`end` < ?";
+
+ } else {
+ query = "DELETE FROM `libertybans_" + type.getLowercaseNamePlural() + "` WHERE `id` IN "
+ + "(SELECT `id` FROM `libertybans_punishments` `puns` WHERE `puns`.`end` != 0 AND `puns`.`end` < ?)";
+ }
+ querySource.query(query).params(currentTime).voidResult().execute();
+ }
}
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/DatabaseManager.java b/bans-core/src/main/java/space/arim/libertybans/core/database/DatabaseManager.java
index d3ef56da9..e5aaadb96 100644
--- a/bans-core/src/main/java/space/arim/libertybans/core/database/DatabaseManager.java
+++ b/bans-core/src/main/java/space/arim/libertybans/core/database/DatabaseManager.java
@@ -18,19 +18,6 @@
*/
package space.arim.libertybans.core.database;
-import java.util.List;
-import java.util.Locale;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.TimeUnit;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import space.arim.omnibus.util.ThisClass;
-
-import space.arim.api.configure.SingleKeyValueTransformer;
-import space.arim.api.configure.ValueTransformer;
-
import space.arim.libertybans.bootstrap.StartupException;
import space.arim.libertybans.core.LibertyBansCore;
import space.arim.libertybans.core.Part;
@@ -41,8 +28,6 @@ public class DatabaseManager implements Part {
private volatile Database database;
- private static final Logger logger = LoggerFactory.getLogger(ThisClass.get());
-
public DatabaseManager(LibertyBansCore core) {
this.core = core;
}
@@ -74,9 +59,12 @@ public void restart() {
throw new StartupException("Database restart failed");
}
currentDatabase.cancelRefreshTaskIfNecessary();
- CompletableFuture.delayedExecutor(8, TimeUnit.SECONDS).execute(
- (currentDatabase.getVendor() == database.getVendor()) ?
- currentDatabase::close : currentDatabase::closeCompletely);
+
+ if (currentDatabase.getVendor() == database.getVendor()) {
+ currentDatabase.close();
+ } else {
+ currentDatabase.closeCompletely();
+ }
database.startRefreshTaskIfNecessary();
this.database = database;
@@ -86,62 +74,7 @@ public void restart() {
public void shutdown() {
Database database = this.database;
database.cancelRefreshTaskIfNecessary();
- core.addDelayedShutdownHook(database::closeCompletely);
- }
-
- /*
- *
- * Configuration
- *
- */
-
- private static boolean isAtLeast(Object value, int amount) {
- return value instanceof Integer && ((Integer) value) >= amount;
- }
-
- public static List createConfigTransformers() {
- var vendorTransformer = SingleKeyValueTransformer.create("rdms-vendor", (value) -> {
- if (value instanceof Vendor) {
- return value;
- }
- if (value instanceof String) {
- String vendorName = (String) value;
- switch (vendorName.toUpperCase(Locale.ENGLISH)) {
- case "HYPERSQL":
- case "HSQLDB":
- return Vendor.HSQLDB;
- case "MYSQL":
- case "MARIADB":
- return Vendor.MARIADB;
- default:
- break;
- }
- }
- logger.warn("Unknown RDMS vendor {}", value);
- return null;
- });
- var poolSizeTransformer = SingleKeyValueTransformer.createPredicate("connection-pool-size", (value) -> {
- if (!isAtLeast(value, 0)) {
- logger.warn("Bad connection pool size {}", value);
- return true;
- }
- return false;
- });
- var timeoutTransformer = SingleKeyValueTransformer.createPredicate("timeouts.connection-timeout-seconds", (value) -> {
- if (!isAtLeast(value, 1)) {
- logger.warn("Bad connection timeout setting {}", value);
- return true;
- }
- return false;
- });
- var lifetimeTransformer = SingleKeyValueTransformer.createPredicate("timeouts.max-lifetime-minutes", (value) -> {
- if (!isAtLeast(value, 1)) {
- logger.warn("Bad lifetime timeout setting {}", value);
- return true;
- }
- return false;
- });
- return List.of(vendorTransformer, poolSizeTransformer, timeoutTransformer, lifetimeTransformer);
+ database.closeCompletely();
}
}
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/DatabaseSettings.java b/bans-core/src/main/java/space/arim/libertybans/core/database/DatabaseSettings.java
index 3fccf317e..bc7ba4fc6 100644
--- a/bans-core/src/main/java/space/arim/libertybans/core/database/DatabaseSettings.java
+++ b/bans-core/src/main/java/space/arim/libertybans/core/database/DatabaseSettings.java
@@ -21,8 +21,10 @@
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
-import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -32,11 +34,10 @@
import space.arim.omnibus.util.ThisClass;
import space.arim.omnibus.util.concurrent.CentralisedFuture;
-import space.arim.api.configure.ConfigAccessor;
-
import space.arim.libertybans.bootstrap.StartupException;
import space.arim.libertybans.core.LibertyBansCore;
-import space.arim.libertybans.driver.DriverCreator;
+import space.arim.libertybans.core.config.SqlConfig;
+import space.arim.libertybans.core.punish.MiscUtil;
/**
* Database settings creator, NOT thread safe!
@@ -48,7 +49,7 @@ class DatabaseSettings {
private final LibertyBansCore core;
- private ConfigAccessor config;
+ private SqlConfig config;
private Vendor vendor;
private HikariConfig hikariConf;
@@ -64,7 +65,7 @@ class DatabaseSettings {
* @return a database result, which should be checked for success or failure
*/
CentralisedFuture create() {
- return create(core.getConfigs().getSql());
+ return create(core.getConfigs().getSqlConfig());
}
/**
@@ -73,9 +74,9 @@ CentralisedFuture create() {
* @param config the config accessor
* @return a database result, which should be checked for success or failure
*/
- CentralisedFuture create(ConfigAccessor config) {
+ CentralisedFuture create(SqlConfig config) {
this.config = config;
- vendor = config.getObject("rdms-vendor", Vendor.class);
+ vendor = config.vendor();
hikariConf = new HikariConfig();
setHikariConfig();
@@ -86,9 +87,32 @@ CentralisedFuture create(ConfigAccessor config) {
return tablesAndViewsCreation.thenApply((success) -> new DatabaseResult(database, success));
}
+ private void setHikariConfig() {
+ setUsernameAndPassword();
+ setConfiguredDriver();
+
+ // Timeouts
+ SqlConfig.Timeouts timeouts = config.timeouts();
+ Duration connectionTimeout = Duration.ofSeconds(timeouts.connectionTimeoutSeconds());
+ Duration maxLifetime = Duration.ofMinutes(timeouts.maxLifetimeMinutes());
+ hikariConf.setConnectionTimeout(connectionTimeout.toMillis());
+ hikariConf.setMaxLifetime(maxLifetime.toMillis());
+
+ // Pool size
+ int poolSize = config.poolSize();
+ hikariConf.setMinimumIdle(poolSize);
+ hikariConf.setMaximumPoolSize(poolSize);
+
+ // Other settings
+ hikariConf.setAutoCommit(false);
+ hikariConf.setTransactionIsolation("TRANSACTION_REPEATABLE_READ");
+ hikariConf.setPoolName("LibertyBans-HikariCP-" + vendor.displayName());
+ }
+
private void setUsernameAndPassword() {
- String username = config.getString("auth-details.user");
- String password = config.getString("auth-details.password");
+ SqlConfig.AuthDetails authDetails = config.authDetails();
+ String username = authDetails.username();
+ String password = authDetails.password();
if (vendor == Vendor.MARIADB && (username.equals("username") || password.equals("defaultpass"))) {
logger.warn("Not using MariaDB/MySQL because authentication details are still default");
vendor = Vendor.HSQLDB;
@@ -101,62 +125,70 @@ private void setUsernameAndPassword() {
hikariConf.setPassword(password);
}
- private void setHikariConfig() {
- setUsernameAndPassword();
-
- int poolSize = config.getInteger("connection-pool-size");
- hikariConf.setMinimumIdle(poolSize);
- hikariConf.setMaximumPoolSize(poolSize);
-
- int connectionTimeout = config.getInteger("timeouts.connection-timeout-seconds");
- int maxLifetime = config.getInteger("timeouts.max-lifetime-minutes");
- hikariConf.setConnectionTimeout(TimeUnit.MILLISECONDS.convert(connectionTimeout, TimeUnit.SECONDS));
- hikariConf.setMaxLifetime(TimeUnit.MILLISECONDS.convert(maxLifetime, TimeUnit.MINUTES));
-
- setConfiguredDriver();
-
- hikariConf.setAutoCommit(false);
-
- @SuppressWarnings("unchecked")
- Map connectProps = config.getObject("connection-properties", Map.class);
- for (Map.Entry property : connectProps.entrySet()) {
- String propName = property.getKey();
- Object propValue = property.getValue();
- logger.trace("Setting data source property {} to {}", propName, propValue);
- hikariConf.addDataSourceProperty(propName, propValue);
- }
-
- hikariConf.setPoolName("LibertyBans-HikariCP-" + vendor.displayName());
- }
-
private void setConfiguredDriver() {
- boolean jdbcUrl = config.getBoolean("use-traditional-jdbc-url");
- DriverCreator driverCreator = new DriverCreator(hikariConf, jdbcUrl);
-
+ final String jdbcUrl;
switch (vendor) {
case MARIADB:
- String host = config.getString("auth-details.host");
- int port = config.getInteger("auth-details.port");
- String database = config.getString("auth-details.database");
- driverCreator.createMariaDb(host, port, database);
+ SqlConfig.AuthDetails authDetails = config.authDetails();
+ String host = authDetails.host();
+ int port = authDetails.port();
+ String database = authDetails.database();
+
+ hikariConf.addDataSourceProperty("databaseName", database);
+ jdbcUrl = "jdbc:mariadb://" + host + ":" + port + "/" + database + getMariaDbUrlProperties();
break;
case HSQLDB:
Path databaseFolder = core.getFolder().resolve("hypersql");
- if (!Files.isDirectory(databaseFolder)) {
- try {
- Files.createDirectory(databaseFolder);
- } catch (IOException ex) {
- throw new StartupException("Cannot create database folder", ex);
- }
+ try {
+ Files.createDirectories(databaseFolder);
+ } catch (IOException ex) {
+ throw new StartupException("Cannot create database folder", ex);
}
- driverCreator.createHsqldb(databaseFolder + "/punishments-database");
+ Path databaseFile = databaseFolder.resolve("punishments-database").toAbsolutePath();
+ jdbcUrl = "jdbc:hsqldb:file:" + databaseFile;
break;
default:
- throw new IllegalStateException("Unknown database vendor " + vendor);
- }
+ throw MiscUtil.unknownVendor(vendor);
+ }
+ if (config.useTraditionalJdbcUrl()) {
+ setDriverClassName(vendor.driverClassName());
+ hikariConf.setJdbcUrl(jdbcUrl);
+
+ } else {
+ hikariConf.setDataSourceClassName(vendor.dataSourceClassName());
+ hikariConf.addDataSourceProperty("url", jdbcUrl);
+ }
+ }
+
+ private String getMariaDbUrlProperties() {
+ List connectProps = new ArrayList<>();
+ for (Map.Entry property : config.connectionProperties().entrySet()) {
+ String propName = property.getKey();
+ String propValue = property.getValue();
+ logger.trace("Adding connection property {} with value {}", propName, propValue);
+ connectProps.add(propName + "=" + propValue);
+ }
+ String properties = String.join("&", connectProps);
+ return (properties.isEmpty()) ? "" : "?" + properties;
+ }
+
+ /**
+ * Sets the driver class name utilising the context classloader
+ *
+ * @param driverClassName the driver class name
+ */
+ private void setDriverClassName(String driverClassName) {
+ Thread currentThread = Thread.currentThread();
+ ClassLoader initialContextLoader = currentThread.getContextClassLoader();
+ try {
+ currentThread.setContextClassLoader(getClass().getClassLoader());
+ hikariConf.setDriverClassName(driverClassName);
+ } finally {
+ currentThread.setContextClassLoader(initialContextLoader);
+ }
}
}
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/IOUtils.java b/bans-core/src/main/java/space/arim/libertybans/core/database/IOUtils.java
index bbe81afdd..1f04c6796 100644
--- a/bans-core/src/main/java/space/arim/libertybans/core/database/IOUtils.java
+++ b/bans-core/src/main/java/space/arim/libertybans/core/database/IOUtils.java
@@ -23,26 +23,18 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.io.UncheckedIOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
-import java.util.concurrent.Executor;
-import java.util.concurrent.ThreadFactory;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import space.arim.omnibus.util.ThisClass;
-
-public final class IOUtils {
-
- private static final Class> THIS_CLASS = ThisClass.get();
+final class IOUtils {
private IOUtils() {}
private static InputStream getResource(String resourceName) throws IOException {
- URL url = THIS_CLASS.getResource('/' + resourceName);
+ URL url = IOUtils.class.getResource('/' + resourceName);
if (url == null) {
throw new IllegalArgumentException("Resource " + resourceName + " not found");
}
@@ -54,7 +46,7 @@ private static InputStream getResource(String resourceName) throws IOException {
*
* @param resourceName the resource name
* @return the resource content
- * @throws IllegalStateException if an IO error occurred
+ * @throws UncheckedIOException if an IO error occurred
*/
static ByteArrayOutputStream readResource(String resourceName) {
try (InputStream is = getResource(resourceName)) {
@@ -63,7 +55,7 @@ static ByteArrayOutputStream readResource(String resourceName) {
is.transferTo(baos);
return baos;
} catch (IOException ex) {
- throw new IllegalStateException("Failed to read internal resource " + resourceName, ex);
+ throw new UncheckedIOException("Failed to read internal resource " + resourceName, ex);
}
}
@@ -76,7 +68,7 @@ static ByteArrayOutputStream readResource(String resourceName) {
*
* @param resourceName the resource name
* @return a mutable list of SQL queries
- * @throws IllegalStateException if an IO error occurred
+ * @throws UncheckedIOException if an IO error occurred
*/
static List readSqlQueries(String resourceName) {
try (InputStream inputStream = getResource(resourceName);
@@ -102,47 +94,8 @@ static List readSqlQueries(String resourceName) {
}
return result;
} catch (IOException ex) {
- throw new IllegalStateException("Failed to read internal resource " + resourceName, ex);
- }
- }
-
- public static class ThreadFactoryImpl implements ThreadFactory {
-
- private final String prefix;
- private int threadId = 1;
-
- private static final Logger logger = LoggerFactory.getLogger(ThisClass.get());
-
- public ThreadFactoryImpl(String prefix) {
- this.prefix = prefix;
- }
-
- private synchronized int nextId() {
- return threadId++;
- }
-
- @Override
- public Thread newThread(Runnable r) {
- String name = prefix + nextId();
- logger.debug("Spawning new thread {}", name);
- return new Thread(r, name);
- }
-
- }
-
- public static class SafeExecutorWrapper implements Executor {
-
- private final Executor delegate;
-
- public SafeExecutorWrapper(Executor delegate) {
- this.delegate = delegate;
- }
-
- @Override
- public void execute(Runnable command) {
- delegate.execute(command);
+ throw new UncheckedIOException("Failed to read internal resource " + resourceName, ex);
}
-
}
}
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/JdbCaesarHelper.java b/bans-core/src/main/java/space/arim/libertybans/core/database/JdbCaesarHelper.java
index 5f931b69d..3c8da3a3d 100644
--- a/bans-core/src/main/java/space/arim/libertybans/core/database/JdbCaesarHelper.java
+++ b/bans-core/src/main/java/space/arim/libertybans/core/database/JdbCaesarHelper.java
@@ -18,7 +18,6 @@
*/
package space.arim.libertybans.core.database;
-import java.net.InetAddress;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
@@ -28,13 +27,14 @@
import space.arim.libertybans.api.AddressVictim;
import space.arim.libertybans.api.ConsoleOperator;
+import space.arim.libertybans.api.NetworkAddress;
import space.arim.libertybans.api.Operator;
import space.arim.libertybans.api.PlayerOperator;
import space.arim.libertybans.api.PlayerVictim;
-import space.arim.libertybans.api.Scope;
+import space.arim.libertybans.api.ServerScope;
import space.arim.libertybans.api.Victim;
import space.arim.libertybans.api.Victim.VictimType;
-import space.arim.libertybans.core.Scoper;
+import space.arim.libertybans.core.punish.Scoper;
import space.arim.jdbcaesar.adapter.DataTypeAdapter;
@@ -116,8 +116,8 @@ static class ScopeAdapter implements DataTypeAdapter {
@Override
public Object adaptObject(Object parameter) {
- if (parameter instanceof Scope) {
- return scoper.getServer((Scope) parameter);
+ if (parameter instanceof ServerScope) {
+ return scoper.getServer((ServerScope) parameter);
}
return parameter;
}
@@ -136,12 +136,12 @@ public Object adaptObject(Object parameter) {
}
- static class InetAddressAdapter implements DataTypeAdapter {
+ static class NetworkAddressAdapter implements DataTypeAdapter {
@Override
public Object adaptObject(Object parameter) {
- if (parameter instanceof InetAddress) {
- return ((InetAddress) parameter).getAddress();
+ if (parameter instanceof NetworkAddress) {
+ return ((NetworkAddress) parameter).getRawAddress();
}
return parameter;
}
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/RefreshTaskRunnable.java b/bans-core/src/main/java/space/arim/libertybans/core/database/RefreshTaskRunnable.java
index 9f36413d2..72c12886c 100644
--- a/bans-core/src/main/java/space/arim/libertybans/core/database/RefreshTaskRunnable.java
+++ b/bans-core/src/main/java/space/arim/libertybans/core/database/RefreshTaskRunnable.java
@@ -24,7 +24,7 @@
import space.arim.omnibus.util.ThisClass;
import space.arim.libertybans.api.PunishmentType;
-import space.arim.libertybans.core.MiscUtil;
+import space.arim.libertybans.core.punish.MiscUtil;
class RefreshTaskRunnable implements Runnable {
@@ -49,11 +49,7 @@ public void run() {
database.jdbCaesar().transaction().body((querySource, controller) -> {
for (PunishmentType type : MiscUtil.punishmentTypesExcludingKick()) {
- querySource.query(
- "DELETE FROM `libertybans_" + type.getLowercaseNamePlural() + "` WHERE `id` IN "
- + "(SELECT `id` FROM `libertybans_punishments` `puns` WHERE (`puns`.`end` != 0 AND `puns`.`end` < ?))")
- .params(currentTime)
- .voidResult().execute();
+ database.clearExpiredPunishments(querySource, type, currentTime);
}
return (Void) null;
}).onError(() -> null).execute();
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/TableDefinitions.java b/bans-core/src/main/java/space/arim/libertybans/core/database/TableDefinitions.java
index 6372d3432..5a05da9f8 100644
--- a/bans-core/src/main/java/space/arim/libertybans/core/database/TableDefinitions.java
+++ b/bans-core/src/main/java/space/arim/libertybans/core/database/TableDefinitions.java
@@ -37,7 +37,7 @@
import space.arim.libertybans.api.PunishmentType;
import space.arim.libertybans.api.Victim;
import space.arim.libertybans.core.LibertyBansCore;
-import space.arim.libertybans.core.MiscUtil;
+import space.arim.libertybans.core.punish.MiscUtil;
import space.arim.jdbcaesar.mapper.UpdateCountMapper;
import space.arim.jdbcaesar.transact.TransactionQuerySource;
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/Vendor.java b/bans-core/src/main/java/space/arim/libertybans/core/database/Vendor.java
index e8b9de4cf..60f8483dd 100644
--- a/bans-core/src/main/java/space/arim/libertybans/core/database/Vendor.java
+++ b/bans-core/src/main/java/space/arim/libertybans/core/database/Vendor.java
@@ -20,19 +20,31 @@
public enum Vendor {
- MARIADB("MariaDB"),
- HSQLDB("HyperSQL");
+ MARIADB("MariaDB", "org.mariadb.jdbc.Driver", "org.mariadb.jdbc.MariaDbDataSource"),
+ HSQLDB("HyperSQL", "org.hsqldb.jdbc.JDBCDriver", "org.hsqldb.jdbc.JDBCDataSource");
private final String displayName;
+ private final String driverClassName;
+ private final String dataSourceClassName;
- private Vendor(String displayName) {
+ private Vendor(String displayName, String driverClassName, String dataSourceClassName) {
this.displayName = displayName;
+ this.driverClassName = driverClassName;
+ this.dataSourceClassName = dataSourceClassName;
}
String displayName() {
return displayName;
}
+ String driverClassName() {
+ return driverClassName;
+ }
+
+ String dataSourceClassName() {
+ return dataSourceClassName;
+ }
+
public boolean useStoredRoutines() {
return this == MARIADB;
}
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/env/AbstractCmdSender.java b/bans-core/src/main/java/space/arim/libertybans/core/env/AbstractCmdSender.java
index 654a6c339..d62b8e6ae 100644
--- a/bans-core/src/main/java/space/arim/libertybans/core/env/AbstractCmdSender.java
+++ b/bans-core/src/main/java/space/arim/libertybans/core/env/AbstractCmdSender.java
@@ -36,6 +36,10 @@ protected AbstractCmdSender(LibertyBansCore core, Object rawSender, Operator ope
this.operator = operator;
}
+ protected LibertyBansCore core() {
+ return core;
+ }
+
@Override
public Operator getOperator() {
return operator;
@@ -43,12 +47,14 @@ public Operator getOperator() {
@Override
public void sendMessage(SendableMessage message) {
+ SendableMessage prefix = core.getMessagesConfig().all().prefix();
+ message = prefix.concatenate(message);
core.getEnvironment().getPlatformHandle().sendMessage(rawSender, message);
}
@Override
- public void parseThenSend(String message) {
- sendMessage(core.getFormatter().parseMessage(message));
+ public void sendLiteralMessage(String message) {
+ sendMessage(core.getFormatter().parseMessageWithPrefix(message));
}
@Override
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/env/AbstractEnvEnforcer.java b/bans-core/src/main/java/space/arim/libertybans/core/env/AbstractEnvEnforcer.java
index 1b779ad39..b316ffb49 100644
--- a/bans-core/src/main/java/space/arim/libertybans/core/env/AbstractEnvEnforcer.java
+++ b/bans-core/src/main/java/space/arim/libertybans/core/env/AbstractEnvEnforcer.java
@@ -21,17 +21,28 @@
import java.util.UUID;
import space.arim.api.chat.SendableMessage;
+import space.arim.libertybans.core.LibertyBansCore;
+
public abstract class AbstractEnvEnforcer implements EnvEnforcer {
+ private final LibertyBansCore core;
private final Environment env;
- protected AbstractEnvEnforcer(Environment env) {
+ protected AbstractEnvEnforcer(LibertyBansCore core, Environment env) {
+ this.core = core;
this.env = env;
}
protected Environment env() {
return env;
}
+
+ @Override
+ public final void sendToThoseWithPermission(String permission, SendableMessage message) {
+ sendToThoseWithPermission0(permission, core.getFormatter().prefix(message));
+ }
+
+ protected abstract void sendToThoseWithPermission0(String permission, SendableMessage message);
@Override
public void kickByUUID(UUID uuid, SendableMessage message) {
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/env/CmdSender.java b/bans-core/src/main/java/space/arim/libertybans/core/env/CmdSender.java
index aadff7627..90b0537ea 100644
--- a/bans-core/src/main/java/space/arim/libertybans/core/env/CmdSender.java
+++ b/bans-core/src/main/java/space/arim/libertybans/core/env/CmdSender.java
@@ -30,7 +30,7 @@ public interface CmdSender {
void sendMessage(SendableMessage message);
- void parseThenSend(String message);
+ void sendLiteralMessage(String messageToParse);
Object getRawSender();
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/env/EnvEnforcer.java b/bans-core/src/main/java/space/arim/libertybans/core/env/EnvEnforcer.java
index 3dc7aba28..9648d0c86 100644
--- a/bans-core/src/main/java/space/arim/libertybans/core/env/EnvEnforcer.java
+++ b/bans-core/src/main/java/space/arim/libertybans/core/env/EnvEnforcer.java
@@ -18,6 +18,7 @@
*/
package space.arim.libertybans.core.env;
+import java.net.InetAddress;
import java.util.UUID;
import java.util.function.Consumer;
@@ -68,6 +69,6 @@ public interface EnvEnforcer {
UUID getUniqueIdFor(@PlatformPlayer Object player);
- byte[] getAddressFor(@PlatformPlayer Object player);
+ InetAddress getAddressFor(@PlatformPlayer Object player);
}
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/env/EnvironmentManager.java b/bans-core/src/main/java/space/arim/libertybans/core/env/EnvironmentManager.java
index f1a22b070..b65fc05b7 100644
--- a/bans-core/src/main/java/space/arim/libertybans/core/env/EnvironmentManager.java
+++ b/bans-core/src/main/java/space/arim/libertybans/core/env/EnvironmentManager.java
@@ -37,7 +37,7 @@ public EnvironmentManager(LibertyBansCore core) {
@Override
public void startup() {
listeners.addAll(core.getEnvironment().createListeners());
- for (String alias : core.getConfigs().getConfig().getStringList("commands.aliases")) {
+ for (String alias : core.getMainConfig().commandAliases()) {
listeners.add(core.getEnvironment().createAliasCommand(alias));
}
listeners.forEach(PlatformListener::register);
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/env/TargetMatcher.java b/bans-core/src/main/java/space/arim/libertybans/core/env/TargetMatcher.java
index 02d7dd598..cb917e4b5 100644
--- a/bans-core/src/main/java/space/arim/libertybans/core/env/TargetMatcher.java
+++ b/bans-core/src/main/java/space/arim/libertybans/core/env/TargetMatcher.java
@@ -32,17 +32,13 @@ public class TargetMatcher {
private final Consumer<@PlatformPlayer Object> callback;
public TargetMatcher(Set uuids, Set addresses, Consumer<@PlatformPlayer Object> callback) {
- this.uuids = uuids;
- this.addresses = addresses;
+ this.uuids = Set.copyOf(uuids);
+ this.addresses = Set.copyOf(addresses);
this.callback = callback;
}
- public Set uuids() {
- return uuids;
- }
-
- public Set addresses() {
- return addresses;
+ public boolean matches(UUID uuid, InetAddress address) {
+ return uuids.contains(uuid) || addresses.contains(address);
}
public Consumer<@PlatformPlayer Object> callback() {
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/punish/AbstractPunishmentBase.java b/bans-core/src/main/java/space/arim/libertybans/core/punish/AbstractPunishmentBase.java
new file mode 100644
index 000000000..ea1d86fba
--- /dev/null
+++ b/bans-core/src/main/java/space/arim/libertybans/core/punish/AbstractPunishmentBase.java
@@ -0,0 +1,103 @@
+/*
+ * LibertyBans-core
+ * Copyright © 2020 Anand Beh
+ *
+ * LibertyBans-core is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * LibertyBans-core 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with LibertyBans-core. If not, see
+ * and navigate to version 3 of the GNU Affero General Public License.
+ */
+package space.arim.libertybans.core.punish;
+
+import java.util.Objects;
+
+import space.arim.libertybans.api.Operator;
+import space.arim.libertybans.api.PunishmentType;
+import space.arim.libertybans.api.ServerScope;
+import space.arim.libertybans.api.Victim;
+import space.arim.libertybans.api.punish.PunishmentBase;
+
+abstract class AbstractPunishmentBase implements PunishmentBase {
+
+ transient final EnforcementCenter center;
+
+ private final PunishmentType type;
+ private final Victim victim;
+ private final Operator operator;
+ private final String reason;
+ private final ServerScope scope;
+
+ AbstractPunishmentBase(EnforcementCenter center,
+ PunishmentType type, Victim victim, Operator operator, String reason, ServerScope scope) {
+ this.center = center;
+
+ this.type = Objects.requireNonNull(type, "type");
+ this.victim = Objects.requireNonNull(victim, "victim");
+ this.operator = Objects.requireNonNull(operator, "operator");
+ this.reason = Objects.requireNonNull(reason, "reason");
+ this.scope = Objects.requireNonNull(scope, "scope");
+ }
+
+ @Override
+ public PunishmentType getType() {
+ return type;
+ }
+
+ @Override
+ public Victim getVictim() {
+ return victim;
+ }
+
+ @Override
+ public Operator getOperator() {
+ return operator;
+ }
+
+ @Override
+ public String getReason() {
+ return reason;
+ }
+
+ @Override
+ public ServerScope getScope() {
+ return scope;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + type.hashCode();
+ result = prime * result + victim.hashCode();
+ result = prime * result + operator.hashCode();
+ result = prime * result + reason.hashCode();
+ result = prime * result + scope.hashCode();
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (this == object) {
+ return true;
+ }
+ if (!(object instanceof AbstractPunishmentBase)) {
+ return false;
+ }
+ AbstractPunishmentBase other = (AbstractPunishmentBase) object;
+ return type == other.type
+ && victim.equals(other.victim)
+ && operator.equals(other.operator)
+ && reason.equals(other.reason)
+ && scope.equals(other.scope);
+ }
+
+}
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/punish/DraftPunishmentBuilderImpl.java b/bans-core/src/main/java/space/arim/libertybans/core/punish/DraftPunishmentBuilderImpl.java
new file mode 100644
index 000000000..12a2209f5
--- /dev/null
+++ b/bans-core/src/main/java/space/arim/libertybans/core/punish/DraftPunishmentBuilderImpl.java
@@ -0,0 +1,99 @@
+/*
+ * LibertyBans-core
+ * Copyright © 2020 Anand Beh
+ *
+ * LibertyBans-core is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * LibertyBans-core 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with LibertyBans-core. If not, see
+ * and navigate to version 3 of the GNU Affero General Public License.
+ */
+package space.arim.libertybans.core.punish;
+
+import java.time.Duration;
+import java.util.Objects;
+
+import space.arim.libertybans.api.ConsoleOperator;
+import space.arim.libertybans.api.Operator;
+import space.arim.libertybans.api.PunishmentType;
+import space.arim.libertybans.api.ServerScope;
+import space.arim.libertybans.api.Victim;
+import space.arim.libertybans.api.punish.DraftPunishment;
+import space.arim.libertybans.api.punish.DraftPunishmentBuilder;
+
+class DraftPunishmentBuilderImpl implements DraftPunishmentBuilder {
+
+ private final EnforcementCenter center;
+
+ PunishmentType type;
+ Victim victim;
+ Operator operator = ConsoleOperator.INSTANCE;
+ String reason;
+ ServerScope scope;
+ Duration duration = Duration.ZERO;
+
+ DraftPunishmentBuilderImpl(EnforcementCenter center) {
+ this.center = center;
+ scope = center.core().getScopeManager().globalScope();
+ }
+
+ @Override
+ public DraftPunishmentBuilder type(PunishmentType type) {
+ this.type = Objects.requireNonNull(type, "type");
+ return this;
+ }
+
+ @Override
+ public DraftPunishmentBuilder victim(Victim victim) {
+ this.victim = Objects.requireNonNull(victim, "victim");
+ return this;
+ }
+
+ @Override
+ public DraftPunishmentBuilder operator(Operator operator) {
+ this.operator = Objects.requireNonNull(operator, "operator");
+ return this;
+ }
+
+ @Override
+ public DraftPunishmentBuilder reason(String reason) {
+ this.reason = Objects.requireNonNull(reason, "reason");
+ return this;
+ }
+
+ @Override
+ public DraftPunishmentBuilder scope(ServerScope scope) {
+ this.scope = MiscUtil.checkScope(scope);
+ return this;
+ }
+
+ @Override
+ public DraftPunishmentBuilder duration(Duration duration) {
+ Objects.requireNonNull(duration, "duration");
+ if (duration.isNegative()) {
+ throw new IllegalArgumentException("duration cannot be negative");
+ }
+ this.duration = duration;
+ return this;
+ }
+
+ @Override
+ public DraftPunishment build() {
+ if (type == null || victim == null || reason == null) {
+ throw new IllegalStateException("Builder details have not been set");
+ }
+ if (type == PunishmentType.KICK && !duration.isZero()) {
+ throw new IllegalArgumentException("Kicks cannot be temporary");
+ }
+ return new DraftPunishmentImpl(center, this);
+ }
+
+}
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/punish/DraftPunishmentImpl.java b/bans-core/src/main/java/space/arim/libertybans/core/punish/DraftPunishmentImpl.java
new file mode 100644
index 000000000..877a415eb
--- /dev/null
+++ b/bans-core/src/main/java/space/arim/libertybans/core/punish/DraftPunishmentImpl.java
@@ -0,0 +1,85 @@
+/*
+ * LibertyBans-core
+ * Copyright © 2020 Anand Beh
+ *
+ * LibertyBans-core is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * LibertyBans-core 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with LibertyBans-core. If not, see
+ * and navigate to version 3 of the GNU Affero General Public License.
+ */
+package space.arim.libertybans.core.punish;
+
+import java.time.Duration;
+import java.util.concurrent.CompletableFuture;
+
+import space.arim.omnibus.util.concurrent.CentralisedFuture;
+
+import space.arim.libertybans.api.punish.DraftPunishment;
+import space.arim.libertybans.api.punish.Punishment;
+
+class DraftPunishmentImpl extends AbstractPunishmentBase implements DraftPunishment {
+
+ private final Duration duration;
+
+ DraftPunishmentImpl(EnforcementCenter center, DraftPunishmentBuilderImpl builder) {
+ super(center, builder.type, builder.victim, builder.operator, builder.reason, builder.scope);
+ duration = builder.duration;
+ }
+
+ @Override
+ public Duration getDuration() {
+ return duration;
+ }
+
+ @Override
+ public CentralisedFuture enactPunishment() {
+ return enactPunishmentWithoutEnforcement().thenCompose((punishment) -> {
+ return (punishment == null) ?
+ CompletableFuture.completedStage(null) :
+ punishment.enforcePunishment().thenApply((ignore) -> punishment);
+ });
+ }
+
+ @Override
+ public CentralisedFuture enactPunishmentWithoutEnforcement() {
+ return center.getEnactor().enactPunishment(this);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + duration.hashCode();
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (this == object) {
+ return true;
+ }
+ if (!super.equals(object)) {
+ return false;
+ }
+ if (!(object instanceof DraftPunishmentImpl)) {
+ return false;
+ }
+ DraftPunishmentImpl other = (DraftPunishmentImpl) object;
+ return duration.equals(other.duration);
+ }
+
+ @Override
+ public String toString() {
+ return "DraftPunishmentImpl [duration=" + duration + ", toString()=" + super.toString() + "]";
+ }
+
+}
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/punish/Enactor.java b/bans-core/src/main/java/space/arim/libertybans/core/punish/Enactor.java
new file mode 100644
index 000000000..9efc82b68
--- /dev/null
+++ b/bans-core/src/main/java/space/arim/libertybans/core/punish/Enactor.java
@@ -0,0 +1,116 @@
+/*
+ * LibertyBans-core
+ * Copyright © 2020 Anand Beh
+ *
+ * LibertyBans-core is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * LibertyBans-core 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with LibertyBans-core. If not, see
+ * and navigate to version 3 of the GNU Affero General Public License.
+ */
+package space.arim.libertybans.core.punish;
+
+import java.time.Duration;
+
+import space.arim.omnibus.util.concurrent.CentralisedFuture;
+
+import space.arim.libertybans.api.Operator;
+import space.arim.libertybans.api.PunishmentType;
+import space.arim.libertybans.api.ServerScope;
+import space.arim.libertybans.api.Victim;
+import space.arim.libertybans.api.punish.DraftPunishment;
+import space.arim.libertybans.api.punish.Punishment;
+import space.arim.libertybans.core.database.Database;
+
+import space.arim.jdbcaesar.mapper.UpdateCountMapper;
+
+class Enactor extends EnforcementCenterMember {
+
+ Enactor(EnforcementCenter center) {
+ super(center);
+ }
+
+ CentralisedFuture enactPunishment(DraftPunishment draftPunishment) {
+ Database database = core().getDatabase();
+ return database.selectAsync(() -> {
+
+ final PunishmentType type = draftPunishment.getType();
+ final Victim victim = draftPunishment.getVictim();
+ final Operator operator = draftPunishment.getOperator();
+ final String reason = draftPunishment.getReason();
+ final ServerScope scope = draftPunishment.getScope();
+ final Duration duration = draftPunishment.getDuration();
+ final long start = MiscUtil.currentTime();
+ final long end = (duration.isZero()) ? 0L : start + duration.toSeconds();
+ MiscUtil.checkRange(start, end);
+
+ return database.jdbCaesar().transaction().body((querySource, controller) -> {
+
+ if (type != PunishmentType.KICK) {
+ database.clearExpiredPunishments(querySource, type, MiscUtil.currentTime());
+ }
+
+ if (database.getVendor().useStoredRoutines()) {
+ String enactmentProcedure = MiscUtil.getEnactmentProcedure(type);
+
+ return querySource.query(
+ "{CALL `libertybans_" + enactmentProcedure + "` (?, ?, ?, ?, ?, ?, ?)}")
+ .params(victim, victim.getType().ordinal() + 1, operator, reason,
+ scope, start, end)
+
+ .singleResult((resultSet) -> {
+ int id = resultSet.getInt("id");
+ return center.createPunishment(id, type, victim, operator, reason, scope, start, end);
+
+ }).execute();
+ } else {
+ int id = querySource.query(
+ "INSERT INTO `libertybans_punishments` (`type`, `operator`, `reason`, `scope`, `start`, `end`) "
+ + "VALUES (?, ?, ?, ?, ?, ?)")
+ .params(type, operator, draftPunishment.getReason(), scope, start, end)
+ .updateGenKeys((updateCount, genKeys) -> {
+ if (!genKeys.next()) {
+ throw new IllegalStateException("No punishment ID generated for insertion query");
+ }
+ return genKeys.getInt("id");
+ }).execute();
+
+ if (type != PunishmentType.KICK) { // Kicks are pure history, so they jump straight to the end
+
+ String enactStatement = "%INSERT% INTO `libertybans_" + type.getLowercaseNamePlural() + "` "
+ + "(`id`, `victim`, `victim_type`) VALUES (?, ?, ?)";
+ Object[] enactArgs = new Object[] {id, victim, victim.getType()};
+
+ if (type.isSingular()) {
+ enactStatement = enactStatement.replace("%INSERT%", "INSERT IGNORE");
+ int updateCount = querySource.query(enactStatement).params(enactArgs)
+ .updateCount(UpdateCountMapper.identity()).execute();
+ if (updateCount == 0) {
+ controller.rollback();
+ return null;
+ }
+ } else {
+ enactStatement = enactStatement.replace("%INSERT%", "INSERT");
+ querySource.query(enactStatement).params(enactArgs).voidResult().execute();
+ }
+ }
+
+ querySource.query(
+ "INSERT INTO `libertybans_history` (`id`, `victim`, `victim_type`) VALUES (?, ?, ?)")
+ .params(id, victim, victim.getType())
+ .voidResult().execute();
+ return center.createPunishment(id, type, victim, operator, reason,scope, start, end);
+ }
+ }).onError(() -> null).execute();
+ });
+ }
+
+}
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/punish/EnforcementCenter.java b/bans-core/src/main/java/space/arim/libertybans/core/punish/EnforcementCenter.java
new file mode 100644
index 000000000..db488942f
--- /dev/null
+++ b/bans-core/src/main/java/space/arim/libertybans/core/punish/EnforcementCenter.java
@@ -0,0 +1,112 @@
+/*
+ * LibertyBans-core
+ * Copyright © 2020 Anand Beh
+ *
+ * LibertyBans-core is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * LibertyBans-core 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with LibertyBans-core. If not, see
+ * and navigate to version 3 of the GNU Affero General Public License.
+ */
+package space.arim.libertybans.core.punish;
+
+import java.net.InetAddress;
+import java.time.Instant;
+import java.util.UUID;
+
+import space.arim.omnibus.util.concurrent.CentralisedFuture;
+
+import space.arim.api.chat.SendableMessage;
+
+import space.arim.libertybans.api.NetworkAddress;
+import space.arim.libertybans.api.Operator;
+import space.arim.libertybans.api.PunishmentType;
+import space.arim.libertybans.api.ServerScope;
+import space.arim.libertybans.api.Victim;
+import space.arim.libertybans.api.punish.DraftPunishmentBuilder;
+import space.arim.libertybans.api.punish.Punishment;
+import space.arim.libertybans.api.punish.PunishmentDrafter;
+import space.arim.libertybans.core.LibertyBansCore;
+
+public class EnforcementCenter implements PunishmentDrafter {
+
+ private final LibertyBansCore core;
+
+ private final Enactor enactor;
+ private final Enforcer enforcer;
+ private final Revoker revoker;
+
+ public EnforcementCenter(LibertyBansCore core) {
+ this.core = core;
+ enactor = new Enactor(this);
+ enforcer = new Enforcer(this);
+ revoker = new Revoker(this);
+ }
+
+ public Punishment createPunishment(int id, PunishmentType type, Victim victim, Operator operator, String reason,
+ ServerScope scope, long start, long end) {
+ Instant startDate = Instant.ofEpochSecond(start);
+ Instant endDate = (end == 0L) ? Instant.MAX : Instant.ofEpochSecond(end);
+ return new SecurePunishment(this, id, type, victim, operator, reason, scope, startDate, endDate);
+ }
+
+ LibertyBansCore core() {
+ return core;
+ }
+
+ Enactor getEnactor() {
+ return enactor;
+ }
+
+ Enforcer getEnforcer() {
+ return enforcer;
+ }
+
+ public Revoker getRevoker() {
+ return revoker;
+ }
+
+ /**
+ * Enforces an incoming connection, returning a punishment message if denied, null if allowed.
+ *
+ * Adds the UUID and name to the local fast cache, queries for an applicable ban, and formats the
+ * ban reason as the punishment message.
+ *
+ * @param uuid the player's UUID
+ * @param name the player's name
+ * @param address the player's network address
+ * @return a future which yields the punishment message if denied, else null if allowed
+ */
+ public CentralisedFuture executeAndCheckConnection(UUID uuid, String name, InetAddress address) {
+ return getEnforcer().executeAndCheckConnection(uuid, name, NetworkAddress.of(address));
+ }
+
+ /**
+ * Enforces a chat message or executed command, returning a punishment message if denied, null if allowed.
+ *
+ * If this corresponds to an executed command, the configured commands whose access to muted players to block
+ * are taken into account.
+ *
+ * @param uuid the player's UUID
+ * @param address the player's network address
+ * @param command the command executed, or {@code null} if this is a chat message
+ * @return a future which yields the punishment message if denied, else null if allowed
+ */
+ public CentralisedFuture checkChat(UUID uuid, InetAddress address, String command) {
+ return getEnforcer().checkChat(uuid, NetworkAddress.of(address), command);
+ }
+
+ @Override
+ public DraftPunishmentBuilder draftBuilder() {
+ return new DraftPunishmentBuilderImpl(this);
+ }
+
+}
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/punish/EnforcementCenterMember.java b/bans-core/src/main/java/space/arim/libertybans/core/punish/EnforcementCenterMember.java
new file mode 100644
index 000000000..30b568737
--- /dev/null
+++ b/bans-core/src/main/java/space/arim/libertybans/core/punish/EnforcementCenterMember.java
@@ -0,0 +1,35 @@
+/*
+ * LibertyBans-core
+ * Copyright © 2020 Anand Beh
+ *
+ * LibertyBans-core is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * LibertyBans-core 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with LibertyBans-core. If not, see
+ * and navigate to version 3 of the GNU Affero General Public License.
+ */
+package space.arim.libertybans.core.punish;
+
+import space.arim.libertybans.core.LibertyBansCore;
+
+class EnforcementCenterMember {
+
+ final EnforcementCenter center;
+
+ EnforcementCenterMember(EnforcementCenter center) {
+ this.center = center;
+ }
+
+ LibertyBansCore core() {
+ return center.core();
+ }
+
+}
diff --git a/bans-core/src/main/java/space/arim/libertybans/core/Enforcer.java b/bans-core/src/main/java/space/arim/libertybans/core/punish/Enforcer.java
similarity index 57%
rename from bans-core/src/main/java/space/arim/libertybans/core/Enforcer.java
rename to bans-core/src/main/java/space/arim/libertybans/core/punish/Enforcer.java
index c56182f6a..ee28df6e1 100644
--- a/bans-core/src/main/java/space/arim/libertybans/core/Enforcer.java
+++ b/bans-core/src/main/java/space/arim/libertybans/core/punish/Enforcer.java
@@ -16,7 +16,7 @@
* along with LibertyBans-core. If not, see
* and navigate to version 3 of the GNU Affero General Public License.
*/
-package space.arim.libertybans.core;
+package space.arim.libertybans.core.punish;
import java.util.Set;
import java.util.UUID;
@@ -28,52 +28,40 @@
import space.arim.api.chat.SendableMessage;
import space.arim.api.env.PlatformHandle;
+import space.arim.api.env.annote.PlatformPlayer;
import space.arim.libertybans.api.AddressVictim;
import space.arim.libertybans.api.NetworkAddress;
import space.arim.libertybans.api.PlayerVictim;
-import space.arim.libertybans.api.Punishment;
-import space.arim.libertybans.api.PunishmentEnforcer;
import space.arim.libertybans.api.PunishmentType;
-import space.arim.libertybans.core.config.AddressStrictness;
+import space.arim.libertybans.api.punish.Punishment;
import space.arim.libertybans.core.database.Database;
import space.arim.libertybans.core.env.EnvEnforcer;
import space.arim.libertybans.core.env.TargetMatcher;
+import space.arim.libertybans.core.selector.AddressStrictness;
-public class Enforcer implements PunishmentEnforcer {
+class Enforcer extends EnforcementCenterMember {
- private final LibertyBansCore core;
-
- Enforcer(LibertyBansCore core) {
- this.core = core;
+ Enforcer(EnforcementCenter center) {
+ super(center);
}
-
- @Override
- public CentralisedFuture> enforce(Punishment punishment) {
- MiscUtil.validate(punishment);
- CentralisedFuture futureMessage = core.getFormatter().getPunishmentMessage(punishment);
+
+ CentralisedFuture> enforce(Punishment punishment) {
+ CentralisedFuture futureMessage = core().getFormatter().getPunishmentMessage(punishment);
switch (punishment.getVictim().getType()) {
case PLAYER:
UUID uuid = ((PlayerVictim) punishment.getVictim()).getUUID();
CentralisedFuture> enforceFuture = futureMessage.thenAccept((message) -> {
- core.getEnvironment().getEnforcer().doForPlayerIfOnline(uuid, enforcementCallback(punishment, message));
+ core().getEnvironment().getEnforcer().doForPlayerIfOnline(uuid, enforcementCallback(punishment, message));
});
return enforceFuture;
-
case ADDRESS:
return futureMessage.thenCompose((message) -> enforceAddressPunishment(punishment, message));
default:
- throw new IllegalStateException("Unknown victim type " + punishment.getVictim().getType());
+ throw MiscUtil.unknownVictimType(punishment.getVictim().getType());
}
}
- @Override
- public CentralisedFuture> unenforce(Punishment punishment) {
- MiscUtil.validate(punishment);
- core.getMuteCacher().clearCachedMute(punishment);
- return core.getFuturesFactory().completedFuture(null);
- }
-
private static boolean shouldKick(PunishmentType type) {
switch (type) {
case BAN:
@@ -83,16 +71,16 @@ private static boolean shouldKick(PunishmentType type) {
case WARN:
return false;
default:
- throw new IllegalStateException("Unknown punishment type " + type);
+ throw MiscUtil.unknownType(type);
}
}
- private Consumer