diff --git a/build.gradle b/build.gradle
index f1c8306..d5f000a 100644
--- a/build.gradle
+++ b/build.gradle
@@ -81,6 +81,8 @@ dependencies {
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.hamcrest:hamcrest:2.2'
+
+ compileOnly 'org.xerial:sqlite-jdbc:3.36.0'
}
task buildResources {
diff --git a/src/de/steamwar/ImplementationProvider.java b/src/de/steamwar/ImplementationProvider.java
new file mode 100644
index 0000000..e5b795e
--- /dev/null
+++ b/src/de/steamwar/ImplementationProvider.java
@@ -0,0 +1,34 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2022 SteamWar.de-Serverteam
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.steamwar;
+
+import java.lang.reflect.InvocationTargetException;
+
+public class ImplementationProvider {
+ private ImplementationProvider() {}
+
+ public static T getImpl(String className) {
+ try {
+ return (T) Class.forName(className).getDeclaredConstructor().newInstance();
+ } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException | ClassNotFoundException e) {
+ throw new SecurityException("Could not load implementation", e);
+ }
+ }
+}
diff --git a/src/de/steamwar/sql/BauweltMember.java b/src/de/steamwar/sql/BauweltMember.java
new file mode 100644
index 0000000..aa3ed93
--- /dev/null
+++ b/src/de/steamwar/sql/BauweltMember.java
@@ -0,0 +1,79 @@
+/*
+ This file is a part of the SteamWar software.
+
+ Copyright (C) 2020 SteamWar.de-Serverteam
+
+ This program 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.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+
+package de.steamwar.sql;
+
+import de.steamwar.sql.internal.Field;
+import de.steamwar.sql.internal.SelectStatement;
+import de.steamwar.sql.internal.Table;
+import lombok.Getter;
+
+import java.util.*;
+
+public class BauweltMember {
+ private static final Map memberCache = new HashMap<>();
+
+ public static void clear() {
+ memberCache.clear();
+ }
+
+ private static final Table table = new Table<>(BauweltMember.class);
+ private static final SelectStatement getMember = table.select(Table.PRIMARY);
+ private static final SelectStatement getMembers = table.selectFields("BauweltID");
+
+ public static BauweltMember getBauMember(UUID ownerID, UUID memberID){
+ return getBauMember(SteamwarUser.get(ownerID).getId(), SteamwarUser.get(memberID).getId());
+ }
+
+ public static BauweltMember getBauMember(int ownerID, int memberID){
+ BauweltMember member = memberCache.get(memberID);
+ if(member != null)
+ return member;
+ return getMember.select(ownerID, memberID);
+ }
+
+ public static List getMembers(UUID bauweltID){
+ return getMembers(SteamwarUser.get(bauweltID).getId());
+ }
+
+ public static List getMembers(int bauweltID){
+ return getMembers.listSelect(bauweltID);
+ }
+
+ @Getter
+ @Field(keys = {Table.PRIMARY})
+ private final int bauweltID;
+ @Getter
+ @Field(keys = {Table.PRIMARY})
+ private final int memberID;
+ @Getter
+ @Field
+ private final boolean worldEdit;
+ @Getter
+ @Field
+ private final boolean world;
+
+ public BauweltMember(int bauweltID, int memberID, boolean worldEdit, boolean world) {
+ this.bauweltID = bauweltID;
+ this.memberID = memberID;
+ this.worldEdit = worldEdit;
+ this.world = world;
+ memberCache.put(memberID, this);
+ }
+}
diff --git a/src/de/steamwar/sql/CheckedSchematic.java b/src/de/steamwar/sql/CheckedSchematic.java
new file mode 100644
index 0000000..71fe24a
--- /dev/null
+++ b/src/de/steamwar/sql/CheckedSchematic.java
@@ -0,0 +1,71 @@
+/*
+ This file is a part of the SteamWar software.
+
+ Copyright (C) 2020 SteamWar.de-Serverteam
+
+ This program 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.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+
+package de.steamwar.sql;
+
+import de.steamwar.sql.internal.Field;
+import de.steamwar.sql.internal.SelectStatement;
+import de.steamwar.sql.internal.Table;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.sql.Timestamp;
+import java.util.List;
+
+@AllArgsConstructor
+public class CheckedSchematic {
+
+ private static final Table table = new Table<>(CheckedSchematic.class);
+ private static final SelectStatement statusOfNode = new SelectStatement<>(table, "SELECT * FROM CheckedSchematic WHERE NodeId = ? AND DeclineReason != 'Prüfvorgang abgebrochen' ORDER BY EndTime DESC");
+
+ public static List getLastDeclinedOfNode(int node){
+ return statusOfNode.listSelect(node);
+ }
+
+ @Field(nullable = true)
+ private final Integer nodeId;
+ @Field
+ private final int nodeOwner;
+ @Field
+ private final String nodeName;
+ @Getter
+ @Field
+ private final int validator;
+ @Getter
+ @Field
+ private final Timestamp startTime;
+ @Getter
+ @Field
+ private final Timestamp endTime;
+ @Getter
+ @Field
+ private final String declineReason;
+
+ public int getNode() {
+ return nodeId;
+ }
+
+ public String getSchemName() {
+ return nodeName;
+ }
+
+ public int getSchemOwner() {
+ return nodeOwner;
+ }
+}
diff --git a/src/de/steamwar/sql/Event.java b/src/de/steamwar/sql/Event.java
new file mode 100644
index 0000000..b3f8f8c
--- /dev/null
+++ b/src/de/steamwar/sql/Event.java
@@ -0,0 +1,72 @@
+/*
+ This file is a part of the SteamWar software.
+
+ Copyright (C) 2020 SteamWar.de-Serverteam
+
+ This program 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.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+
+package de.steamwar.sql;
+
+import de.steamwar.sql.internal.Field;
+import de.steamwar.sql.internal.SelectStatement;
+import de.steamwar.sql.internal.Table;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.sql.Timestamp;
+
+@AllArgsConstructor
+public class Event {
+
+ private static final Table table = new Table<>(Event.class);
+ private static final SelectStatement byId = table.select(Table.PRIMARY);
+
+ public static Event get(int eventID){
+ return byId.select(eventID);
+ }
+
+ @Getter
+ @Field(keys = {Table.PRIMARY}, autoincrement = true)
+ private final int eventID;
+ @Getter
+ @Field(keys = {"eventName"})
+ private final String eventName;
+ @Getter
+ @Field
+ private final Timestamp deadline;
+ @Getter
+ @Field
+ private final Timestamp start;
+ @Getter
+ @Field
+ private final Timestamp end;
+ @Getter
+ @Field
+ private final int maximumTeamMembers;
+ @Getter
+ @Field(nullable = true)
+ private final SchematicType schematicType;
+ @Field
+ private final boolean publicSchemsOnly;
+ @Field
+ private final boolean spectateSystem;
+
+ public boolean publicSchemsOnly() {
+ return publicSchemsOnly;
+ }
+ public boolean spectateSystem(){
+ return spectateSystem;
+ }
+}
diff --git a/src/de/steamwar/sql/EventFight.java b/src/de/steamwar/sql/EventFight.java
new file mode 100644
index 0000000..60e4831
--- /dev/null
+++ b/src/de/steamwar/sql/EventFight.java
@@ -0,0 +1,72 @@
+/*
+ This file is a part of the SteamWar software.
+
+ Copyright (C) 2020 SteamWar.de-Serverteam
+
+ This program 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.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+
+package de.steamwar.sql;
+
+import de.steamwar.sql.internal.Field;
+import de.steamwar.sql.internal.SelectStatement;
+import de.steamwar.sql.internal.Statement;
+import de.steamwar.sql.internal.Table;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@AllArgsConstructor
+public class EventFight {
+
+ private static final Table table = new Table<>(EventFight.class);
+ private static final SelectStatement byId = table.select(Table.PRIMARY);
+ private static final Statement setResult = table.update(Table.PRIMARY, "Ergebnis");
+ private static final Statement setFight = table.update(Table.PRIMARY, "Fight");
+
+ public static EventFight get(int fightID) {
+ return byId.select(fightID);
+ }
+
+ @Getter
+ @Field
+ private final int eventID;
+ @Getter
+ @Field(keys = {Table.PRIMARY}, autoincrement = true)
+ private final int fightID;
+ @Getter
+ @Field
+ private final int teamBlue;
+ @Getter
+ @Field
+ private final int teamRed;
+ @Getter
+ @Field
+ private final int kampfleiter;
+ @Getter
+ @Field(def = "0")
+ private int ergebnis;
+ @Field(nullable = true)
+ private int fight;
+
+ public void setErgebnis(int winner) {
+ this.ergebnis = winner;
+ setResult.update(winner, fightID);
+ }
+
+ public void setFight(int fight) {
+ //Fight.FightID, not EventFight.FightID
+ this.fight = fight;
+ setFight.update(fight, fightID);
+ }
+}
diff --git a/src/de/steamwar/sql/Fight.java b/src/de/steamwar/sql/Fight.java
new file mode 100644
index 0000000..0f9eae6
--- /dev/null
+++ b/src/de/steamwar/sql/Fight.java
@@ -0,0 +1,61 @@
+/*
+ This file is a part of the SteamWar software.
+
+ Copyright (C) 2020 SteamWar.de-Serverteam
+
+ This program 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.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+
+package de.steamwar.sql;
+
+import de.steamwar.sql.internal.Field;
+import de.steamwar.sql.internal.Statement;
+import de.steamwar.sql.internal.Table;
+import lombok.AllArgsConstructor;
+
+import java.sql.Timestamp;
+
+@AllArgsConstructor
+public class Fight {
+
+ private static final Table table = new Table<>(Fight.class);
+ private static final Statement insert = table.insertFields(true, "GameMode", "Server", "StartTime", "Duration", "BlueLeader", "RedLeader", "BlueSchem", "RedSchem", "Win", "WinCondition");
+
+ @Field(keys = {Table.PRIMARY}, autoincrement = true)
+ private final int fightID;
+ @Field
+ private final String gameMode;
+ @Field
+ private final String server;
+ @Field
+ private final Timestamp startTime;
+ @Field
+ private final int duration;
+ @Field
+ private final int blueLeader;
+ @Field
+ private final int redLeader;
+ @Field(nullable = true)
+ private final Integer blueSchem;
+ @Field(nullable = true)
+ private final Integer redSchem;
+ @Field
+ private final int win;
+ @Field
+ private final String wincondition;
+
+ public static int create(String gamemode, String server, Timestamp starttime, int duration, int blueleader, int redleader, Integer blueschem, Integer redschem, int win, String wincondition){
+ return insert.insertGetKey(gamemode, server, starttime, duration, blueleader, redleader, blueschem, redschem, win, wincondition);
+ }
+}
diff --git a/src/de/steamwar/sql/FightPlayer.java b/src/de/steamwar/sql/FightPlayer.java
new file mode 100644
index 0000000..cc529b8
--- /dev/null
+++ b/src/de/steamwar/sql/FightPlayer.java
@@ -0,0 +1,49 @@
+/*
+ This file is a part of the SteamWar software.
+
+ Copyright (C) 2020 SteamWar.de-Serverteam
+
+ This program 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.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+
+package de.steamwar.sql;
+
+import de.steamwar.sql.internal.Field;
+import de.steamwar.sql.internal.Statement;
+import de.steamwar.sql.internal.Table;
+import lombok.AllArgsConstructor;
+
+@AllArgsConstructor
+public class FightPlayer {
+
+ private static final Table table = new Table<>(FightPlayer.class);
+ private static final Statement create = table.insertAll();
+
+ @Field(keys = {Table.PRIMARY})
+ private final int fightID;
+ @Field(keys = {Table.PRIMARY})
+ private final int userID;
+ @Field
+ private final int team;
+ @Field
+ private final String kit;
+ @Field
+ private final int kills;
+ @Field
+ private final boolean isOut;
+
+ public static void create(int fightID, int userID, boolean blue, String kit, int kills, boolean isOut) {
+ create.update(fightID, userID, blue ? 1 : 2, kit, kills, isOut);
+ }
+}
diff --git a/src/de/steamwar/sql/NoClipboardException.java b/src/de/steamwar/sql/NoClipboardException.java
new file mode 100644
index 0000000..9743348
--- /dev/null
+++ b/src/de/steamwar/sql/NoClipboardException.java
@@ -0,0 +1,23 @@
+/*
+ This file is a part of the SteamWar software.
+
+ Copyright (C) 2020 SteamWar.de-Serverteam
+
+ This program 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.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+
+package de.steamwar.sql;
+
+public class NoClipboardException extends RuntimeException {
+}
diff --git a/src/de/steamwar/sql/NodeDownload.java b/src/de/steamwar/sql/NodeDownload.java
new file mode 100644
index 0000000..0984096
--- /dev/null
+++ b/src/de/steamwar/sql/NodeDownload.java
@@ -0,0 +1,69 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2021 SteamWar.de-Serverteam
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.steamwar.sql;
+
+import de.steamwar.sql.internal.Field;
+import de.steamwar.sql.internal.Statement;
+import de.steamwar.sql.internal.Table;
+import lombok.AllArgsConstructor;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.Timestamp;
+import java.time.Instant;
+
+@AllArgsConstructor
+public class NodeDownload {
+
+ private static final char[] HEX = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
+ private static final String LINK_BASE = "https://steamwar.de/download.php?schem=";
+
+ private static final Table table = new Table<>(NodeDownload.class);
+ private static final Statement insert = table.insertFields("NodeId", "Link");
+
+ @Field(keys = {Table.PRIMARY})
+ private final int nodeId;
+ @Field
+ private final String link;
+ @Field(def = "CURRENT_TIMESTAMP")
+ private final Timestamp timestamp;
+
+ public static String getLink(SchematicNode schem){
+ if(schem.isDir())
+ throw new SecurityException("Can not Download Directorys");
+ MessageDigest digest;
+ try {
+ digest = MessageDigest.getInstance("SHA-1");
+ } catch (NoSuchAlgorithmException e) {
+ throw new SecurityException(e);
+ }
+ digest.reset();
+ digest.update((Instant.now().toString() + schem.getOwner() + schem.getId()).getBytes());
+ String hash = base16encode(digest.digest());
+ insert.update(schem.getId(), hash);
+ return LINK_BASE + hash;
+ }
+ public static String base16encode(byte[] byteArray) {
+ StringBuilder hexBuffer = new StringBuilder(byteArray.length * 2);
+ for (byte b : byteArray)
+ hexBuffer.append(HEX[b >> 4]).append(HEX[b & 0xF]);
+ return hexBuffer.toString();
+ }
+}
diff --git a/src/de/steamwar/sql/NodeMember.java b/src/de/steamwar/sql/NodeMember.java
new file mode 100644
index 0000000..e704ad0
--- /dev/null
+++ b/src/de/steamwar/sql/NodeMember.java
@@ -0,0 +1,78 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2020 SteamWar.de-Serverteam
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.steamwar.sql;
+
+import de.steamwar.sql.internal.Field;
+import de.steamwar.sql.internal.SelectStatement;
+import de.steamwar.sql.internal.Statement;
+import de.steamwar.sql.internal.Table;
+import lombok.AllArgsConstructor;
+
+import java.util.HashSet;
+import java.util.Set;
+
+@AllArgsConstructor
+public class NodeMember {
+
+ public static void init() {
+ // enforce class initialization
+ }
+
+ private static final Table table = new Table<>(NodeMember.class);
+ private static final SelectStatement getNodeMember = table.select(Table.PRIMARY);
+ private static final SelectStatement getNodeMembers = table.selectFields("NodeId");
+ private static final SelectStatement getSchematics = table.selectFields("UserId");
+ private static final Statement create = table.insertAll();
+ private static final Statement delete = table.delete(Table.PRIMARY);
+
+ @Field(keys = {Table.PRIMARY})
+ private final int nodeId;
+ @Field(keys = {Table.PRIMARY})
+ private final int userId;
+
+ public int getNode() {
+ return nodeId;
+ }
+
+ public int getMember() {
+ return userId;
+ }
+
+ public void delete() {
+ delete.update(nodeId, userId);
+ }
+
+ public static NodeMember createNodeMember(int node, int member) {
+ create.update(node, member);
+ return new NodeMember(node, member);
+ }
+
+ public static NodeMember getNodeMember(int node, int member) {
+ return getNodeMember.select(node, member);
+ }
+
+ public static Set getNodeMembers(int node) {
+ return new HashSet<>(getNodeMembers.listSelect(node));
+ }
+
+ public static Set getSchematics(int member) {
+ return new HashSet<>(getSchematics.listSelect(member));
+ }
+}
diff --git a/src/de/steamwar/sql/Punishment.java b/src/de/steamwar/sql/Punishment.java
new file mode 100644
index 0000000..22f54ce
--- /dev/null
+++ b/src/de/steamwar/sql/Punishment.java
@@ -0,0 +1,119 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2022 SteamWar.de-Serverteam
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.steamwar.sql;
+
+import de.steamwar.sql.internal.Field;
+import de.steamwar.sql.internal.SelectStatement;
+import de.steamwar.sql.internal.SqlTypeMapper;
+import de.steamwar.sql.internal.Table;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.sql.Timestamp;
+import java.time.format.DateTimeFormatter;
+import java.util.Date;
+import java.util.Map;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+@AllArgsConstructor
+public class Punishment {
+
+ static {
+ SqlTypeMapper.nameEnumMapper(PunishmentType.class);
+ }
+
+ private static final Table table = new Table<>(Punishment.class, "Punishments");
+ private static final SelectStatement getPunishments = new SelectStatement<>(table, "SELECT * FROM Punishments WHERE PunishmentId IN (SELECT MAX(PunishmentId) FROM Punishments WHERE UserId = ? GROUP BY Type)");
+ private static final SelectStatement getPunishment = new SelectStatement<>(table, "SELECT * FROM Punishments WHERE UserId = ? AND Type = ? ORDER BY PunishmentId DESC LIMIT 1");
+
+ @Field(keys = {Table.PRIMARY}, autoincrement = true)
+ private final int punishmentId;
+ @Field
+ @Getter
+ private final int user;
+ @Field
+ @Getter
+ private final int punisher;
+ @Field
+ @Getter
+ private final PunishmentType type;
+ @Field
+ @Getter
+ private final Timestamp startTime;
+ @Field
+ @Getter
+ private final Timestamp endTime;
+ @Field
+ @Getter
+ private final boolean perma;
+ @Field
+ @Getter
+ private final String reason;
+
+ public static Punishment getPunishmentOfPlayer(int user, PunishmentType type) {
+ return getPunishment.select(user, type);
+ }
+
+ public static Map getPunishmentsOfPlayer(int user) {
+ return getPunishments.listSelect(user).stream().collect(Collectors.toMap(Punishment::getType, punishment -> punishment));
+ }
+
+ public static boolean isPunished(SteamwarUser user, Punishment.PunishmentType type, Consumer callback) {
+ Punishment punishment = Punishment.getPunishmentOfPlayer(user.getId(), type);
+ if(punishment == null || !punishment.isCurrent()) {
+ return false;
+ } else {
+ callback.accept(punishment);
+ return true;
+ }
+ }
+
+ @Deprecated // Not multiling, misleading title
+ public String getBantime(Timestamp endTime, boolean perma) {
+ if (perma) {
+ return "permanent";
+ } else {
+ return endTime.toLocalDateTime().format(DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm"));
+ }
+ }
+
+ public boolean isCurrent() {
+ return isPerma() || getEndTime().after(new Date());
+ }
+
+ @AllArgsConstructor
+ @Getter
+ public enum PunishmentType {
+ Ban(false, "BAN_TEAM", "BAN_PERMA", "BAN_UNTIL", "UNBAN_ERROR", "UNBAN"),
+ Mute( false, "MUTE_TEAM", "MUTE_PERMA", "MUTE_UNTIL", "UNMUTE_ERROR", "UNMUTE"),
+ NoSchemReceiving(false, "NOSCHEMRECEIVING_TEAM", "NOSCHEMRECEIVING_PERMA", "NOSCHEMRECEIVING_UNTIL", "UNNOSCHEMRECEIVING_ERROR", "UNNOSCHEMRECEIVING"),
+ NoSchemSharing(false, "NOSCHEMSHARING_TEAM", "NOSCHEMSHARING_PERMA", "NOSCHEMSHARING_UNTIL", "UNNOSCHEMSHARING_ERROR", "UNNOSCHEMSHARING"),
+ NoSchemSubmitting(true, "NOSCHEMSUBMITTING_TEAM", "NOSCHEMSUBMITTING_PERMA", "NOSCHEMSUBMITTING_UNTIL", "UNNOSCHEMSUBMITTING_ERROR", "UNNOSCHEMSUBMITTING"),
+ NoDevServer(true, "NODEVSERVER_TEAM", "NODEVSERVER_PERMA", "NODEVSERVER_UNTIL", "UNNODEVSERVER_ERROR", "UNNODEVSERVER");
+
+ private final boolean needsAdmin;
+ private final String teamMessage;
+ private final String playerMessagePerma;
+ private final String playerMessageUntil;
+ private final String usageNotPunished;
+ private final String unpunishmentMessage;
+ }
+}
diff --git a/src/de/steamwar/sql/Replay.java b/src/de/steamwar/sql/Replay.java
new file mode 100644
index 0000000..a90e239
--- /dev/null
+++ b/src/de/steamwar/sql/Replay.java
@@ -0,0 +1,74 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2022 SteamWar.de-Serverteam
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.steamwar.sql;
+
+import de.steamwar.sql.internal.*;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
+import java.sql.SQLException;
+
+@AllArgsConstructor
+public class Replay {
+
+ static {
+ new SqlTypeMapper<>(File.class, "BLOB", (rs, identifier) -> {
+ try {
+ File file = File.createTempFile("replay", ".replay");
+ file.deleteOnExit();
+ Files.copy(rs.getBinaryStream(identifier), file.toPath(), StandardCopyOption.REPLACE_EXISTING);
+ return file;
+ } catch (IOException e) {
+ throw new SQLException(e);
+ }
+ }, (st, index, value) -> {
+ try {
+ st.setBinaryStream(index, new FileInputStream(value));
+ } catch (FileNotFoundException e) {
+ throw new SQLException(e);
+ }
+ });
+ }
+
+ private static final Table table = new Table<>(Replay.class);
+ private static final SelectStatement get = table.select(Table.PRIMARY);
+
+ public static final Statement insert = table.insertAll();
+
+ public static Replay get(int fightID) {
+ return get.select(fightID);
+ }
+
+ public static void save(int fightID, File file) {
+ insert.update(fightID, file);
+ }
+
+ @Field(keys = {Table.PRIMARY})
+ private final int fightID;
+ @Getter
+ @Field
+ private final File replay;
+}
diff --git a/src/de/steamwar/sql/SQLWrapper.java b/src/de/steamwar/sql/SQLWrapper.java
new file mode 100644
index 0000000..cff7dec
--- /dev/null
+++ b/src/de/steamwar/sql/SQLWrapper.java
@@ -0,0 +1,33 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2022 SteamWar.de-Serverteam
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.steamwar.sql;
+
+import de.steamwar.ImplementationProvider;
+
+import java.util.List;
+import java.util.Map;
+
+public interface SQLWrapper {
+ SQLWrapper impl = ImplementationProvider.getImpl("de.steamwar.sql.SQLWrapperImpl");
+
+ void loadSchemTypes(List tmpTypes, Map tmpFromDB);
+
+ void additionalExceptionMetadata(StringBuilder builder);
+}
diff --git a/src/de/steamwar/sql/SWException.java b/src/de/steamwar/sql/SWException.java
new file mode 100644
index 0000000..0a8ece3
--- /dev/null
+++ b/src/de/steamwar/sql/SWException.java
@@ -0,0 +1,61 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2022 SteamWar.de-Serverteam
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.steamwar.sql;
+
+import de.steamwar.sql.internal.Field;
+import de.steamwar.sql.internal.Statement;
+import de.steamwar.sql.internal.Table;
+import lombok.AllArgsConstructor;
+
+import java.io.File;
+import java.sql.Timestamp;
+
+@AllArgsConstructor
+public class SWException {
+
+ public static void init() {
+ // force class initialialisation
+ }
+
+ private static final String CWD = System.getProperty("user.dir");
+ private static final String SERVER_NAME = new File(CWD).getName();
+
+ private static final Table table = new Table<>(SWException.class, "Exception");
+ private static final Statement insert = table.insertFields("server", "message", "stacktrace");
+
+ @Field(keys = {Table.PRIMARY}, autoincrement = true)
+ private final int id;
+ @Field(def = "CURRENT_TIMESTAMP")
+ private final Timestamp time;
+ @Field
+ private final String server;
+ @Field
+ private final String message;
+ @Field
+ private final String stacktrace;
+
+ public static void log(String message, String stacktrace){
+ StringBuilder msgBuilder = new StringBuilder(message);
+ SQLWrapper.impl.additionalExceptionMetadata(msgBuilder);
+ msgBuilder.append("\nCWD: ").append(CWD);
+
+ insert.update(SERVER_NAME, msgBuilder.toString(), stacktrace);
+ }
+}
diff --git a/src/de/steamwar/sql/SchemElo.java b/src/de/steamwar/sql/SchemElo.java
new file mode 100644
index 0000000..3aedaba
--- /dev/null
+++ b/src/de/steamwar/sql/SchemElo.java
@@ -0,0 +1,43 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2022 SteamWar.de-Serverteam
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.steamwar.sql;
+
+import de.steamwar.sql.internal.Field;
+import de.steamwar.sql.internal.SelectStatement;
+import de.steamwar.sql.internal.Table;
+import lombok.AllArgsConstructor;
+
+@AllArgsConstructor
+public class SchemElo {
+
+ private static final Table table = new Table<>(SchemElo.class);
+ private static final SelectStatement select = table.select(Table.PRIMARY);
+
+ @Field(keys = {Table.PRIMARY})
+ private final int schemId;
+ @Field
+ private final int elo;
+ @Field(keys = {Table.PRIMARY})
+ private final int season;
+
+ public static int getElo(SchematicNode node, int season) {
+ return select.select(node, season).elo;
+ }
+}
diff --git a/src/de/steamwar/sql/SchematicNode.java b/src/de/steamwar/sql/SchematicNode.java
new file mode 100644
index 0000000..78fa24b
--- /dev/null
+++ b/src/de/steamwar/sql/SchematicNode.java
@@ -0,0 +1,547 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2020 SteamWar.de-Serverteam
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.steamwar.sql;
+
+import de.steamwar.sql.internal.*;
+import lombok.AccessLevel;
+import lombok.Setter;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.sql.Timestamp;
+import java.time.Instant;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+public class SchematicNode {
+
+ static {
+ new SqlTypeMapper<>(SchematicNode.class, null, (rs, identifier) -> { throw new SecurityException("SchematicNode cannot be used as type (recursive select)"); }, (st, index, value) -> st.setInt(index, value.nodeId));
+ }
+
+ private static final Map>> TAB_CACHE = new HashMap<>();
+ public static void clear() {
+ TAB_CACHE.clear();
+ }
+
+ private static final String[] fields = {"NodeId", "NodeOwner", "NodeName", "ParentNode", "LastUpdate", "NodeItem", "NodeType", "NodeRank", "ReplaceColor", "AllowReplay", "NodeFormat"};
+ private static String nodeSelectCreator(String itemPrefix) {
+ return "SELECT " + Arrays.stream(fields).map(s -> itemPrefix + s).collect(Collectors.joining(", ")) + " FROM SchematicNode ";
+ }
+
+ private static final Table table = new Table<>(SchematicNode.class);
+ private static final Statement create = table.insertFields(true, "NodeOwner", "NodeName", "ParentNode", "NodeItem", "NodeType");
+ private static final Statement update = table.insertAll();
+ private static final Statement delete = table.delete(Table.PRIMARY);
+
+ private static final SelectStatement byId = table.select(Table.PRIMARY);
+ private static final SelectStatement byOwnerNameParent = table.select("OwnerNameParent");
+ private static final SelectStatement byOwnerNameParent_null = new SelectStatement<>(table, nodeSelectCreator("") + "WHERE NodeOwner = ? AND NodeName = ? AND ParentNode is NULL");
+ private static final SelectStatement byParent = new SelectStatement<>(table, nodeSelectCreator("") + "WHERE ParentNode = ? ORDER BY NodeName");
+ private static final SelectStatement byParent_null = new SelectStatement<>(table, nodeSelectCreator("") + "WHERE ParentNode is NULL ORDER BY NodeName");
+ private static final SelectStatement dirsByParent = new SelectStatement<>(table, nodeSelectCreator("") + "WHERE ParentNode = ? AND NodeType is NULL ORDER BY NodeName");
+ private static final SelectStatement dirsByParent_null = new SelectStatement<>(table, nodeSelectCreator("") + "WHERE ParentNode is NULL AND NodeType is NULL ORDER BY NodeName");
+ private static final SelectStatement byParentName = table.selectFields("NodeName", "ParentNode");
+ private static final SelectStatement byParentName_null = new SelectStatement<>(table, nodeSelectCreator("") + "WHERE NodeName = ? AND ParentNode is NULL");
+ private static final SelectStatement accessibleByUserTypeParent = new SelectStatement<>(table, "WITH RECURSIVE RSNB AS (WITH RECURSIVE RSN as (" + nodeSelectCreator("s.") + "s LEFT JOIN NodeMember n ON s.NodeId = n.NodeId WHERE (s.NodeOwner = ? OR n.UserId = ?) GROUP BY s.NodeId UNION " + nodeSelectCreator("SN.") + "AS SN, RSN WHERE SN.ParentNode = RSN.NodeId) SELECT * FROM RSN WHERE NodeType = ? UNION " + nodeSelectCreator("SN.") + "AS SN, RSNB WHERE SN.NodeId = RSNB.ParentNode)SELECT * FROM RSNB WHERE ParentNode = ? ORDER BY NodeName");
+ private static final SelectStatement accessibleByUserTypeParent_Null = new SelectStatement<>(table, "WITH RECURSIVE RSNB AS (WITH RECURSIVE RSN as (" + nodeSelectCreator("s.") + "s LEFT JOIN NodeMember n ON s.NodeId = n.NodeId WHERE (s.NodeOwner = ? OR n.UserId = ?) GROUP BY s.NodeId UNION " + nodeSelectCreator("SN.") + "AS SN, RSN WHERE SN.ParentNode = RSN.NodeId) SELECT * FROM RSN WHERE NodeType = ? UNION " + nodeSelectCreator("SN.") + "AS SN, RSNB WHERE SN.NodeId = RSNB.ParentNode)SELECT * FROM RSNB WHERE ParentNode is null ORDER BY NodeName");
+ private static final SelectStatement accessibleByUserType = new SelectStatement<>(table, "WITH RECURSIVE RSN as (" + nodeSelectCreator("s.") + "s LEFT JOIN NodeMember n ON s.NodeId = n.NodeId WHERE (s.NodeOwner = ? OR n.UserId = ?) GROUP BY s.NodeId UNION " + nodeSelectCreator("SN.") + "AS SN, RSN WHERE SN.ParentNode = RSN.NodeId) SELECT * FROM RSN WHERE NodeType = ? ORDER BY NodeName");
+ private static final SelectStatement byOwnerType = new SelectStatement<>(table, nodeSelectCreator("") + "WHERE NodeOwner = ? AND NodeType = ? ORDER BY NodeName");
+ private static final SelectStatement byType = new SelectStatement<>(table, nodeSelectCreator("") + "WHERE NodeType = ? ORDER BY NodeName");
+ private static final SelectStatement accessibleByUser = new SelectStatement<>(table, nodeSelectCreator("s.") + "s LEFT JOIN NodeMember n ON s.NodeId = n.NodeId WHERE (s.NodeOwner = ? OR n.UserId = ?) AND ((s.NodeOwner = ? AND s.ParentNode IS NULL) OR NOT s.NodeOwner = ?) GROUP BY s.NodeId ORDER BY s.NodeName");
+ private static final Statement schematicAccessibleForUser = new Statement("WITH RECURSIVE RSN AS (" + nodeSelectCreator("") + "WHERE NodeId = ? UNION " + nodeSelectCreator("SN.") + "SN, RSN WHERE RSN.ParentNode = SN.NodeId) SELECT COUNT(RSN.NodeId) AS `Accessible` FROM RSN LEFT Join NodeMember NM On NM.NodeId = RSN.NodeId WHERE NodeOwner = ? OR UserId = ? LIMIT 1");
+ private static final SelectStatement allAccessibleByUser = new SelectStatement<>(table, "WITH RECURSIVE RSN as (" + nodeSelectCreator("s.") + "s LEFT JOIN NodeMember n ON s.NodeId = n.NodeId WHERE (s.NodeOwner = ? OR n.UserId = ?) GROUP BY s.NodeId UNION " + nodeSelectCreator("SN.") + "AS SN, RSN WHERE SN.ParentNode = RSN.NodeId) SELECT * FROM RSN ORDER BY NodeName");
+ private static final SelectStatement allParentsOfNode = new SelectStatement<>(table, "WITH RECURSIVE RSN AS (" + nodeSelectCreator("") + "WHERE NodeId = ? UNION " + nodeSelectCreator("SN.") + "SN, RSN WHERE RSN.ParentNode = SN.NodeId) SELECT * FROM RSN ORDER BY NodeName");
+
+ static {
+ NodeMember.init();
+ }
+
+ @Field(keys = {Table.PRIMARY}, autoincrement = true)
+ private final int nodeId;
+ @Field(keys = {"OwnerNameParent"})
+ private final int nodeOwner;
+ @Field(keys = {"OwnerNameParent"})
+ private String nodeName;
+ @Field(keys = {"OwnerNameParent"}, nullable = true)
+ private Integer parentNode;
+ @Field(def = "CURRENT_TIMESTAMP")
+ private Timestamp lastUpdate;
+ @Field(def = "''")
+ private String nodeItem;
+ @Field(def = "'normal'", nullable = true)
+ private SchematicType nodeType;
+ @Field(def = "0")
+ private int nodeRank;
+ @Field(def = "1")
+ private boolean replaceColor;
+ @Field(def = "1")
+ private boolean allowReplay;
+ @Setter(AccessLevel.PACKAGE)
+ @Field(def = "1")
+ private boolean nodeFormat;
+
+ private final Map brCache = new HashMap<>();
+
+ public SchematicNode(
+ int nodeId,
+ int nodeOwner,
+ String nodeName,
+ Integer parentNode,
+ Timestamp lastUpdate,
+ String nodeItem,
+ SchematicType nodeType,
+ int nodeRank,
+ boolean replaceColor,
+ boolean allowReplay,
+ boolean nodeFormat
+ ) {
+ this.nodeId = nodeId;
+ this.nodeOwner = nodeOwner;
+ this.nodeName = nodeName;
+ this.parentNode = parentNode;
+ this.nodeItem = nodeItem;
+ this.nodeType = nodeType;
+ this.lastUpdate = lastUpdate;
+ this.nodeRank = nodeRank;
+ this.replaceColor = replaceColor;
+ this.allowReplay = allowReplay;
+ this.nodeFormat = nodeFormat;
+ }
+
+ public static SchematicNode createSchematic(int owner, String name, Integer parent) {
+ return createSchematicNode(owner, name, parent, SchematicType.Normal.toDB(), "");
+ }
+
+ public static SchematicNode createSchematicDirectory(int owner, String name, Integer parent) {
+ return createSchematicNode(owner, name, parent, null, "");
+ }
+
+ public static SchematicNode createSchematicNode(int owner, String name, Integer parent, String type, String item) {
+ if (parent != null && parent == 0)
+ parent = null;
+ int nodeId = create.insertGetKey(owner, name, parent, item, type);
+ return getSchematicNode(nodeId);
+ }
+
+ public static SchematicNode getSchematicNode(int owner, String name, SchematicNode parent) {
+ return getSchematicNode(owner, name, parent.getId());
+ }
+
+ public static SchematicNode getSchematicNode(int owner, String name, Integer parent) {
+ if (parent == null || parent == 0)
+ return byOwnerNameParent_null.select(owner, name);
+ return byOwnerNameParent.select(owner, name, parent);
+ }
+
+ public static List getSchematicNodeInNode(SchematicNode parent) {
+ return getSchematicNodeInNode(parent.getId());
+ }
+
+ public static List getSchematicNodeInNode(Integer parent) {
+ if(parent == null || parent == 0) {
+ rootWarning();
+ return byParent_null.listSelect();
+ }
+
+ return byParent.listSelect(parent);
+ }
+
+ public static List getSchematicDirectoryInNode(Integer parent) {
+ if(parent == null || parent == 0) {
+ rootWarning();
+ return dirsByParent_null.listSelect();
+ }
+ return dirsByParent.listSelect(parent);
+ }
+
+ @Deprecated
+ public static SchematicNode getSchematicDirectory(String name, SchematicNode parent) {
+ return getSchematicNode(name, parent.getId());
+ }
+
+ @Deprecated
+ public static SchematicNode getSchematicDirectory(String name, Integer parent) {
+ return getSchematicNode(name, parent);
+ }
+
+ public static SchematicNode getSchematicNode(String name, Integer parent) {
+ if(parent == null || parent == 0) {
+ rootWarning();
+ return byParentName_null.select(name);
+ }
+ return byParentName.select(name, parent);
+ }
+
+ public static SchematicNode getSchematicNode(int id) {
+ return byId.select(id);
+ }
+
+ public static List getAccessibleSchematicsOfTypeInParent(int owner, String schemType, Integer parent) {
+ if(parent == null || parent == 0)
+ return accessibleByUserTypeParent_Null.listSelect(owner, owner, schemType);
+ return accessibleByUserTypeParent.listSelect(owner, owner, schemType, parent);
+ }
+
+ public static List getAllAccessibleSchematicsOfType(int user, String schemType) {
+ return accessibleByUserType.listSelect(user, user, schemType);
+ }
+
+ public static List getAllSchematicsOfType(int owner, String schemType) {
+ return byOwnerType.listSelect(owner, schemType);
+ }
+
+ @Deprecated
+ public static List getAllSchematicsOfType(String schemType) {
+ return byType.listSelect(schemType);
+ }
+
+ public static List getAllSchematicsOfType(SchematicType schemType) {
+ return byType.listSelect(schemType);
+ }
+
+ public static List deepGet(Integer parent, Predicate filter) {
+ List finalList = new ArrayList<>();
+ List nodes = SchematicNode.getSchematicNodeInNode(parent);
+ nodes.forEach(node -> {
+ if (node.isDir()) {
+ finalList.addAll(deepGet(node.getId(), filter));
+ } else {
+ if (filter.test(node))
+ finalList.add(node);
+ }
+ });
+ return finalList;
+ }
+
+ public static List getSchematicsAccessibleByUser(int user, Integer parent) {
+ if (parent == null || parent == 0)
+ return accessibleByUser.listSelect(user, user, user, user);
+
+ if(schematicAccessibleForUser.select(rs -> {
+ rs.next();
+ return rs.getInt("Accessible") > 0;
+ }, parent, user, user))
+ return getSchematicNodeInNode(parent);
+
+ return Collections.emptyList();
+ }
+
+ public static List getAllSchematicsAccessibleByUser(int user) {
+ return allAccessibleByUser.listSelect(user, user);
+ }
+
+ public static List getAllParentsOfNode(SchematicNode node) {
+ return getAllParentsOfNode(node.getId());
+ }
+
+ public static List getAllParentsOfNode(int node) {
+ return allParentsOfNode.listSelect(node);
+ }
+
+ public static SchematicNode getNodeFromPath(SteamwarUser user, String s) {
+ if (s.startsWith("/")) {
+ s = s.substring(1);
+ }
+ if (s.isEmpty()) {
+ return null;
+ }
+ if (s.contains("/")) {
+ String[] layers = s.split("/");
+ SchematicNode currentNode = null;
+ for (int i = 0; i < layers.length; i++) {
+ int finalI = i;
+ Optional node;
+ if (currentNode == null) {
+ node = SchematicNode.getSchematicsAccessibleByUser(user.getId(), 0).stream().filter(node1 -> node1.getName().equals(layers[finalI])).findAny();
+ } else {
+ node = Optional.ofNullable(SchematicNode.getSchematicNode(layers[i], currentNode.getId()));
+ }
+ if (!node.isPresent()) {
+ return null;
+ } else {
+ currentNode = node.get();
+ if (!currentNode.isDir() && i != layers.length - 1) {
+ return null;
+ }
+ }
+ }
+ return currentNode;
+ } else {
+ String finalS = s;
+ return SchematicNode.getSchematicsAccessibleByUser(user.getId(), 0).stream().filter(node1 -> node1.getName().equals(finalS)).findAny().orElse(null);
+ }
+ }
+
+ public static List filterSchems(int user, Predicate filter) {
+ List finalList = new ArrayList<>();
+ List nodes = getSchematicsAccessibleByUser(user, null);
+ nodes.forEach(node -> {
+ if (node.isDir()) {
+ finalList.addAll(deepGet(node.getId(), filter));
+ } else {
+ if (filter.test(node))
+ finalList.add(node);
+ }
+ });
+ return finalList;
+ }
+
+ @Deprecated
+ public static Integer countNodes() {
+ return -1;
+ }
+
+ public int getId() {
+ return nodeId;
+ }
+
+ public int getOwner() {
+ return nodeOwner;
+ }
+
+ public String getName() {
+ return nodeName;
+ }
+
+ public void setName(String name) {
+ this.nodeName = name;
+ updateDB();
+ }
+
+ public Integer getParent() {
+ return parentNode;
+ }
+
+ public void setParent(Integer parent) {
+ this.parentNode = parent;
+ updateDB();
+ }
+
+ public String getItem() {
+ if (nodeItem.isEmpty()) {
+ return isDir() ? "CHEST" : "CAULDRON_ITEM";
+ }
+ return nodeItem;
+ }
+
+ public void setItem(String item) {
+ this.nodeItem = item;
+ updateDB();
+ }
+
+ @Deprecated
+ public String getType() {
+ return nodeType.name();
+ }
+
+ @Deprecated
+ public void setType(String type) {
+ if(isDir())
+ throw new SecurityException("Node is Directory");
+ this.nodeType = SchematicType.fromDB(type);
+ updateDB();
+ }
+
+ public boolean isDir() {
+ return nodeType == null;
+ }
+
+ public boolean getSchemFormat() {
+ if(isDir())
+ throw new SecurityException("Node is Directory");
+ return nodeFormat;
+ }
+
+ public int getRank() {
+ if(isDir())
+ throw new SecurityException("Node is Directory");
+ return nodeRank;
+ }
+
+ @Deprecated
+ public int getRankUnsafe() {
+ return nodeRank;
+ }
+
+ public void setRank(int rank) {
+ if(isDir())
+ throw new SecurityException("Node is Directory");
+ this.nodeRank = rank;
+ }
+
+ public SchematicType getSchemtype() {
+ if(isDir())
+ throw new SecurityException("Is Directory");
+ return nodeType;
+ }
+
+ public void setSchemtype(SchematicType type) {
+ if(isDir())
+ throw new SecurityException("Is Directory");
+ this.nodeType = type;
+ updateDB();
+ }
+
+ public boolean replaceColor() {
+ return replaceColor;
+ }
+
+ public void setReplaceColor(boolean replaceColor) {
+ if(isDir())
+ throw new SecurityException("Is Directory");
+ this.replaceColor = replaceColor;
+ updateDB();
+ }
+
+ public boolean allowReplay() {
+ return allowReplay;
+ }
+
+ public void setAllowReplay(boolean allowReplay) {
+ if(isDir())
+ throw new SecurityException("Is Directory");
+ this.allowReplay = allowReplay;
+ updateDB();
+ }
+
+ public SchematicNode getParentNode() {
+ if(parentNode == null) return null;
+ return SchematicNode.getSchematicNode(parentNode);
+ }
+
+ public int getElo(int season) {
+ return SchemElo.getElo(this, season);
+ }
+
+ public boolean accessibleByUser(int user) {
+ return NodeMember.getNodeMember(nodeId, user) != null;
+ }
+
+ public Set getMembers() {
+ return NodeMember.getNodeMembers(nodeId);
+ }
+
+ public Timestamp getLastUpdate() {
+ return lastUpdate;
+ }
+
+ private void updateDB() {
+ this.lastUpdate = Timestamp.from(Instant.now());
+ update.update(nodeId, nodeOwner, nodeName, parentNode, lastUpdate, nodeItem, nodeType, nodeRank, replaceColor, allowReplay, nodeFormat);
+ this.brCache.clear();
+ TAB_CACHE.clear();
+ }
+
+ public void delete() {
+ delete.update(nodeId);
+ }
+
+ @Override
+ public int hashCode() {
+ return nodeId;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof SchematicNode))
+ return false;
+
+ return ((SchematicNode) obj).getId() == nodeId;
+ }
+
+ public String generateBreadcrumbs(SteamwarUser user) {
+ return brCache.computeIfAbsent(user.getId(), integer -> generateBreadcrumbs("/", user));
+ }
+
+ public String generateBreadcrumbs(String split, SteamwarUser user) {
+ StringBuilder builder = new StringBuilder(getName());
+ SchematicNode currentNode = this;
+ if (currentNode.isDir()) builder.append("/");
+ final Set nodeMembers = NodeMember.getSchematics(user.getId());
+ AtomicInteger i = new AtomicInteger();
+ i.set(currentNode.getId());
+ while (currentNode.getParentNode() != null && nodeMembers.stream().noneMatch(nodeMember -> nodeMember.getNode() == i.get())) {
+ currentNode = currentNode.getParentNode();
+ i.set(currentNode.getId());
+ builder.insert(0, split)
+ .insert(0, currentNode.getName());
+ }
+ return builder.toString();
+ }
+
+ private static final List FORBIDDEN_NAMES = Collections.unmodifiableList(Arrays.asList("public"));
+ public static boolean invalidSchemName(String[] layers) {
+ for (String layer : layers) {
+ if (layer.isEmpty()) {
+ return true;
+ }
+ if (layer.contains("/") ||
+ layer.contains("\\") ||
+ layer.contains("<") ||
+ layer.contains(">") ||
+ layer.contains("^") ||
+ layer.contains("°") ||
+ layer.contains("'") ||
+ layer.contains("\"") ||
+ layer.contains(" ")) {
+ return true;
+ }
+ if(FORBIDDEN_NAMES.contains(layer.toLowerCase())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static List getNodeTabcomplete(SteamwarUser user, String s) {
+ boolean sws = s.startsWith("/");
+ if (sws) {
+ s = s.substring(1);
+ }
+ int index = s.lastIndexOf("/");
+ String cacheKey = index == -1 ? "" : s.substring(0, index);
+ if(TAB_CACHE.containsKey(user.getId()) && TAB_CACHE.get(user.getId()).containsKey(cacheKey)) {
+ return new ArrayList<>(TAB_CACHE.get(user.getId()).get(cacheKey));
+ }
+ List list = new ArrayList<>();
+ if (s.contains("/")) {
+ String preTab = s.substring(0, s.lastIndexOf("/") + 1);
+ SchematicNode pa = SchematicNode.getNodeFromPath(user, preTab);
+ if (pa == null) return Collections.emptyList();
+ List nodes = SchematicNode.getSchematicNodeInNode(pa);
+ nodes.forEach(node -> list.add((sws ? "/" : "") + node.generateBreadcrumbs(user)));
+ } else {
+ List nodes = SchematicNode.getSchematicsAccessibleByUser(user.getId(), 0);
+ nodes.forEach(node -> list.add((sws ? "/" : "") + node.getName() + (node.isDir() ? "/" : "")));
+ }
+ list.remove("//copy");
+ TAB_CACHE.computeIfAbsent(user.getId(), integer -> new HashMap<>()).putIfAbsent(cacheKey, list);
+ return list;
+ }
+
+ private static void rootWarning() {
+ ByteArrayOutputStream stacktraceOutput = new ByteArrayOutputStream();
+ new Throwable().printStackTrace(new PrintStream(stacktraceOutput));
+ SWException.log("PERFORMANCE!!! Getting all/weird subset of schematic nodes with parent NULL", stacktraceOutput.toString());
+ }
+}
diff --git a/src/de/steamwar/sql/SchematicType.java b/src/de/steamwar/sql/SchematicType.java
new file mode 100644
index 0000000..d87065c
--- /dev/null
+++ b/src/de/steamwar/sql/SchematicType.java
@@ -0,0 +1,116 @@
+/*
+ This file is a part of the SteamWar software.
+
+ Copyright (C) 2020 SteamWar.de-Serverteam
+
+ This program 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.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+
+package de.steamwar.sql;
+
+import de.steamwar.sql.internal.SqlTypeMapper;
+
+import java.util.*;
+
+public class SchematicType {
+
+ public static final SchematicType Normal = new SchematicType("Normal", "", Type.NORMAL, null, "STONE_BUTTON");
+
+ private static final Map fromDB;
+ private static final List types;
+
+ static {
+ List tmpTypes = new LinkedList<>();
+ Map tmpFromDB = new HashMap<>();
+
+ tmpTypes.add(Normal);
+ tmpFromDB.put(Normal.name().toLowerCase(), Normal);
+
+ SQLWrapper.impl.loadSchemTypes(tmpTypes, tmpFromDB);
+
+ fromDB = Collections.unmodifiableMap(tmpFromDB);
+ types = Collections.unmodifiableList(tmpTypes);
+ }
+
+ static {
+ new SqlTypeMapper<>(SchematicType.class, "VARCHAR(16)", (rs, identifier) -> {
+ String t = rs.getString(identifier);
+ return t != null ? fromDB.get(t) : null;
+ }, (st, index, value) -> st.setString(index, value.toDB()));
+ }
+
+ private final String name;
+ private final String kuerzel;
+ private final Type type;
+ private final SchematicType checkType;
+ private final String material;
+
+ SchematicType(String name, String kuerzel, Type type, SchematicType checkType, String material){
+ this.name = name;
+ this.kuerzel = kuerzel;
+ this.type = type;
+ this.checkType = checkType;
+ this.material = material;
+ }
+
+ public boolean isAssignable(){
+ return type == Type.NORMAL || (type == Type.FIGHT_TYPE && checkType != null);
+ }
+
+ public SchematicType checkType(){
+ return checkType;
+ }
+
+ public boolean check(){
+ return type == Type.CHECK_TYPE;
+ }
+
+ public boolean fightType(){
+ return type == Type.FIGHT_TYPE;
+ }
+
+ public boolean writeable(){
+ return type == Type.NORMAL;
+ }
+
+ public String name(){
+ return name;
+ }
+
+ public String getKuerzel() {
+ return kuerzel;
+ }
+
+ public String getMaterial() {
+ return material;
+ }
+
+ public String toDB(){
+ return name.toLowerCase();
+ }
+
+ public static SchematicType fromDB(String input){
+ return fromDB.get(input.toLowerCase());
+ }
+
+ public static List values(){
+ return types;
+ }
+
+ enum Type{
+ NORMAL,
+ CHECK_TYPE,
+ FIGHT_TYPE
+ }
+}
diff --git a/src/de/steamwar/sql/Season.java b/src/de/steamwar/sql/Season.java
new file mode 100644
index 0000000..8768ad8
--- /dev/null
+++ b/src/de/steamwar/sql/Season.java
@@ -0,0 +1,54 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2020 SteamWar.de-Serverteam
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.steamwar.sql;
+
+import java.util.Calendar;
+
+public class Season {
+ private Season() {}
+
+ public static int getSeason() {
+ Calendar calendar = Calendar.getInstance();
+ int yearIndex = calendar.get(Calendar.MONTH) / 4;
+ return (calendar.get(Calendar.YEAR) * 3 + yearIndex);
+ }
+
+ public static String getSeasonStart() {
+ Calendar calendar = Calendar.getInstance();
+ return calendar.get(Calendar.YEAR) + "-" + (calendar.get(Calendar.MONTH) / 4 * 3 + 1) + "-1";
+ }
+
+ public static String convertSeasonToString(int season){
+ if (season == -1) return "";
+ int yearSeason = season % 3;
+ int year = (season - yearSeason) / 3;
+ return String.format("%d-%d", year, yearSeason);
+ }
+
+ public static int convertSeasonToNumber(String season){
+ if (season.isEmpty()) return -1;
+ String[] split = season.split("-");
+ try {
+ return Integer.parseInt(split[0]) * 3 + Integer.parseInt(split[1]);
+ } catch (NumberFormatException e) {
+ return -1;
+ }
+ }
+}
diff --git a/src/de/steamwar/sql/SteamwarUser.java b/src/de/steamwar/sql/SteamwarUser.java
new file mode 100644
index 0000000..874643f
--- /dev/null
+++ b/src/de/steamwar/sql/SteamwarUser.java
@@ -0,0 +1,171 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2022 SteamWar.de-Serverteam
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.steamwar.sql;
+
+import de.steamwar.sql.internal.*;
+import lombok.Getter;
+
+import java.util.*;
+
+public class SteamwarUser {
+
+ static {
+ new SqlTypeMapper<>(UUID.class, "CHAR(36)", (rs, identifier) -> UUID.fromString(rs.getString(identifier)), (st, index, value) -> st.setString(index, value.toString()));
+ new SqlTypeMapper<>(Locale.class, "VARCHAR(32)", (rs, identifier) -> {
+ String l = rs.getString(identifier);
+ return l != null ? Locale.forLanguageTag(l) : null;
+ }, (st, index, value) -> st.setString(index, value.toLanguageTag()));
+ SqlTypeMapper.nameEnumMapper(UserGroup.class);
+ new SqlTypeMapper<>(SteamwarUser.class, null, (rs, identifier) -> { throw new SecurityException("SteamwarUser cannot be used as type (recursive select)"); }, (st, index, value) -> st.setInt(index, value.id));
+ }
+
+ private static final Table table = new Table<>(SteamwarUser.class, "UserData");
+ private static final Statement insert = table.insertFields("UUID", "UserName");
+ private static final SelectStatement byID = table.selectFields("id");
+ private static final SelectStatement byUUID = table.selectFields("UUID");
+ private static final SelectStatement byName = table.selectFields("UserName");
+ private static final SelectStatement byDiscord = table.selectFields("DiscordId");
+ private static final SelectStatement byTeam = table.selectFields("Team");
+ private static final SelectStatement getServerTeam = new SelectStatement<>(table, "SELECT * FROM UserData WHERE UserGroup != 'Member' AND UserGroup != 'YouTuber'");
+ private static final Statement updateName = table.update(Table.PRIMARY, "UserName");
+ private static final Statement updateLocale = table.update(Table.PRIMARY, "Locale", "ManualLocale");
+ private static final Statement updateTeam = table.update(Table.PRIMARY, "Team");
+ private static final Statement updateLeader = table.update(Table.PRIMARY, "Leader");
+ private static final Statement updateDiscord = table.update(Table.PRIMARY, "DiscordId");
+
+ private static final Map usersById = new HashMap<>();
+ private static final Map usersByUUID = new HashMap<>();
+ private static final Map usersByName = new HashMap<>();
+ private static final Map usersByDiscord = new HashMap<>();
+ public static void clear() {
+ usersById.clear();
+ usersByName.clear();
+ usersByUUID.clear();
+ usersByDiscord.clear();
+ }
+
+ public static void invalidate(int userId) {
+ SteamwarUser user = usersById.remove(userId);
+ if (user == null)
+ return;
+ usersByName.remove(user.getUserName());
+ usersByUUID.remove(user.getUUID());
+ }
+
+ @Getter
+ @Field(keys = {Table.PRIMARY}, autoincrement = true)
+ private final int id;
+ @Field(keys = {"uuid"})
+ private final UUID uuid;
+ @Getter
+ @Field
+ private String userName;
+ @Getter
+ @Field(def = "'Member'")
+ private final UserGroup userGroup;
+ @Getter
+ @Field(def = "0")
+ private int team;
+ @Field(def = "0")
+ private boolean leader;
+ @Field(nullable = true)
+ private Locale locale;
+ @Field(def = "0")
+ private boolean manualLocale;
+ @Field(keys = {"discordId"}, nullable = true)
+ private Long discordId;
+
+ public SteamwarUser(int id, UUID uuid, String userName, UserGroup userGroup, int team, boolean leader, Locale locale, boolean manualLocale, Long discordId) {
+ this.id = id;
+ this.uuid = uuid;
+ this.userName = userName;
+ this.userGroup = userGroup;
+ this.team = team;
+ this.leader = leader;
+ this.locale = locale;
+ this.manualLocale = manualLocale;
+ this.discordId = discordId != null && discordId != 0 ? discordId : null;
+
+ usersById.put(id, this);
+ usersByName.put(userName.toLowerCase(), this);
+ usersByUUID.put(uuid, this);
+ if (this.discordId != null) {
+ usersByDiscord.put(discordId, this);
+ }
+ }
+
+ public UUID getUUID() {
+ return uuid;
+ }
+
+ public Locale getLocale() {
+ if(locale != null)
+ return locale;
+ return Locale.getDefault();
+ }
+
+ public void setLocale(Locale locale, boolean manualLocale) {
+ if (locale == null || (this.manualLocale && !manualLocale))
+ return;
+
+ this.locale = locale;
+ this.manualLocale = manualLocale;
+ updateLocale.update(locale.toLanguageTag(), manualLocale, id);
+ }
+
+ public static SteamwarUser get(String userName){
+ SteamwarUser user = usersByName.get(userName.toLowerCase());
+ if(user != null)
+ return user;
+ return byName.select(userName);
+ }
+
+ public static SteamwarUser get(UUID uuid){
+ SteamwarUser user = usersByUUID.get(uuid);
+ if(user != null)
+ return user;
+ return byUUID.select(uuid);
+ }
+
+ public static SteamwarUser get(int id) {
+ SteamwarUser user = usersById.get(id);
+ if(user != null)
+ return user;
+ return byID.select(id);
+ }
+
+ public static SteamwarUser get(Long discordId) {
+ if(usersByDiscord.containsKey(discordId))
+ return usersByDiscord.get(discordId);
+ return byDiscord.select(discordId);
+ }
+
+ public static void createOrUpdateUsername(UUID uuid, String userName) {
+ insert.update(uuid, userName);
+ }
+
+ public static List getServerTeam() {
+ return getServerTeam.listSelect();
+ }
+
+ public static List getTeam(int teamId) {
+ return byTeam.listSelect(teamId);
+ }
+}
diff --git a/src/de/steamwar/sql/Team.java b/src/de/steamwar/sql/Team.java
new file mode 100644
index 0000000..10e6892
--- /dev/null
+++ b/src/de/steamwar/sql/Team.java
@@ -0,0 +1,61 @@
+/*
+ This file is a part of the SteamWar software.
+
+ Copyright (C) 2020 SteamWar.de-Serverteam
+
+ This program 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.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+
+package de.steamwar.sql;
+
+import de.steamwar.sql.internal.Field;
+import de.steamwar.sql.internal.SelectStatement;
+import de.steamwar.sql.internal.Table;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+@AllArgsConstructor
+public class Team {
+
+ private static final Table table = new Table<>(Team.class);
+ private static final SelectStatement select = table.select(Table.PRIMARY);
+
+ @Field(keys = {Table.PRIMARY})
+ @Getter
+ private final int teamId;
+ @Field
+ @Getter
+ private final String teamKuerzel;
+ @Field
+ @Getter
+ private final String teamName;
+ @Field(def = "'8'")
+ @Getter
+ private final String teamColor;
+
+ private static final Team pub = new Team(0, "PUB", "Öffentlich", "8");
+
+ public static Team get(int id) {
+ if(id == 0)
+ return pub;
+ return select.select(id);
+ }
+
+ public List getMembers(){
+ return SteamwarUser.getTeam(teamId).stream().map(SteamwarUser::getId).collect(Collectors.toList());
+ }
+}
diff --git a/src/de/steamwar/sql/TeamTeilnahme.java b/src/de/steamwar/sql/TeamTeilnahme.java
new file mode 100644
index 0000000..533b830
--- /dev/null
+++ b/src/de/steamwar/sql/TeamTeilnahme.java
@@ -0,0 +1,54 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2020 SteamWar.de-Serverteam
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.steamwar.sql;
+
+import de.steamwar.sql.internal.Field;
+import de.steamwar.sql.internal.SelectStatement;
+import de.steamwar.sql.internal.Table;
+import lombok.AllArgsConstructor;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@AllArgsConstructor
+public class TeamTeilnahme {
+
+ private static final Table table = new Table<>(TeamTeilnahme.class);
+ private static final SelectStatement select = table.select(Table.PRIMARY);
+ private static final SelectStatement selectTeams = table.selectFields("EventID");
+ private static final SelectStatement selectEvents = table.selectFields("TeamID");
+
+ @Field(keys = {Table.PRIMARY})
+ private final int teamId;
+ @Field(keys = {Table.PRIMARY})
+ private final int eventId;
+
+ public static boolean nimmtTeil(int teamID, int eventID){
+ return select.select(teamID, eventID) != null;
+ }
+
+ public static Set getTeams(int eventID){
+ return selectTeams.listSelect(eventID).stream().map(tt -> Team.get(tt.teamId)).collect(Collectors.toSet()); // suboptimal performance (O(n) database queries)
+ }
+
+ public static Set getEvents(int teamID){
+ return selectEvents.listSelect(teamID).stream().map(tt -> Event.get(tt.eventId)).collect(Collectors.toSet()); // suboptimal performance (O(n) database queries)
+ }
+}
diff --git a/src/de/steamwar/sql/UserConfig.java b/src/de/steamwar/sql/UserConfig.java
new file mode 100644
index 0000000..f705dfd
--- /dev/null
+++ b/src/de/steamwar/sql/UserConfig.java
@@ -0,0 +1,73 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2020 SteamWar.de-Serverteam
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.steamwar.sql;
+
+import de.steamwar.sql.internal.Field;
+import de.steamwar.sql.internal.SelectStatement;
+import de.steamwar.sql.internal.Statement;
+import de.steamwar.sql.internal.Table;
+import lombok.AllArgsConstructor;
+
+import java.util.UUID;
+
+@AllArgsConstructor
+public class UserConfig {
+
+ private static final Table table = new Table<>(UserConfig.class);
+ private static final SelectStatement select = table.select(Table.PRIMARY);
+ private static final Statement insert = table.insertAll();
+ private static final Statement delete = table.delete(Table.PRIMARY);
+
+ @Field(keys = {Table.PRIMARY})
+ private final int user;
+ @Field(keys = {Table.PRIMARY})
+ private final String config;
+ @Field
+ private final String value;
+
+ public static String getConfig(UUID player, String config) {
+ return getConfig(SteamwarUser.get(player).getId(), config);
+ }
+
+ public static String getConfig(int player, String config) {
+ UserConfig value = select.select(player, config);
+ return value != null ? value.value : null;
+ }
+
+ public static void updatePlayerConfig(UUID uuid, String config, String value) {
+ updatePlayerConfig(SteamwarUser.get(uuid).getId(), config, value);
+ }
+
+ public static void updatePlayerConfig(int id, String config, String value) {
+ if (value == null) {
+ removePlayerConfig(id, config);
+ return;
+ }
+ insert.update(id, config, value);
+ }
+
+ public static void removePlayerConfig(UUID uuid, String config) {
+ removePlayerConfig(SteamwarUser.get(uuid).getId(), config);
+ }
+
+ public static void removePlayerConfig(int id, String config) {
+ delete.update(id, config);
+ }
+}
diff --git a/src/de/steamwar/sql/UserGroup.java b/src/de/steamwar/sql/UserGroup.java
new file mode 100644
index 0000000..cef4fee
--- /dev/null
+++ b/src/de/steamwar/sql/UserGroup.java
@@ -0,0 +1,45 @@
+/*
+ This file is a part of the SteamWar software.
+
+ Copyright (C) 2020 SteamWar.de-Serverteam
+
+ This program 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.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+
+package de.steamwar.sql;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@AllArgsConstructor
+public enum UserGroup {
+ Admin("§4", "§e", true, true, true),
+ Developer("§3", "§f", true, true, true),
+ Moderator("§c", "§f", true, true, true),
+ Supporter("§9", "§f", false, true, true),
+ Builder("§2", "§f", false, true, false),
+ YouTuber("§5", "§f", false, false, false),
+ Member("§7", "§7", false, false, false);
+
+ @Getter
+ private final String colorCode;
+ @Getter
+ private final String chatColorCode;
+ @Getter
+ private final boolean adminGroup;
+ @Getter
+ private final boolean teamGroup;
+ @Getter
+ private final boolean checkSchematics;
+}
\ No newline at end of file
diff --git a/src/de/steamwar/sql/internal/Field.java b/src/de/steamwar/sql/internal/Field.java
new file mode 100644
index 0000000..90bfa1d
--- /dev/null
+++ b/src/de/steamwar/sql/internal/Field.java
@@ -0,0 +1,34 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2022 SteamWar.de-Serverteam
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.steamwar.sql.internal;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Field {
+ String[] keys() default {};
+ String def() default "";
+ boolean nullable() default false;
+ boolean autoincrement() default false;
+}
diff --git a/src/de/steamwar/sql/internal/SQLConfig.java b/src/de/steamwar/sql/internal/SQLConfig.java
new file mode 100644
index 0000000..3153ecb
--- /dev/null
+++ b/src/de/steamwar/sql/internal/SQLConfig.java
@@ -0,0 +1,34 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2022 SteamWar.de-Serverteam
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.steamwar.sql.internal;
+
+import de.steamwar.ImplementationProvider;
+
+import java.util.logging.Logger;
+
+public interface SQLConfig {
+ SQLConfig impl = ImplementationProvider.getImpl("de.steamwar.sql.SQLConfigImpl");
+
+ Logger getLogger();
+
+ int maxConnections();
+
+
+}
diff --git a/src/de/steamwar/sql/internal/SelectStatement.java b/src/de/steamwar/sql/internal/SelectStatement.java
new file mode 100644
index 0000000..e17dcb7
--- /dev/null
+++ b/src/de/steamwar/sql/internal/SelectStatement.java
@@ -0,0 +1,72 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2022 SteamWar.de-Serverteam
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.steamwar.sql.internal;
+
+import java.lang.reflect.InvocationTargetException;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class SelectStatement extends Statement {
+ private final Table table;
+
+ SelectStatement(Table table, String... kfields) {
+ this(table, "SELECT " + Arrays.stream(table.fields).map(f -> f.identifier).collect(Collectors.joining(", ")) + " FROM " + table.name + " WHERE " + Arrays.stream(kfields).map(f -> f + " = ?").collect(Collectors.joining(" AND ")));
+ }
+
+ public SelectStatement(Table table, String sql) {
+ super(sql);
+ this.table = table;
+ }
+
+ public T select(Object... values) {
+ return select(rs -> {
+ if (rs.next())
+ return read(rs);
+ return null;
+ }, values);
+ }
+
+ public List listSelect(Object... values) {
+ return select(rs -> {
+ List result = new ArrayList<>();
+ while (rs.next())
+ result.add(read(rs));
+
+ return result;
+ }, values);
+ }
+
+ private T read(ResultSet rs) throws SQLException {
+ Object[] params = new Object[table.fields.length];
+ for(int i = 0; i < params.length; i++) {
+ params[i] = table.fields[i].read(rs);
+ }
+
+ try {
+ return table.constructor.newInstance(params);
+ } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
+ throw new SecurityException(e);
+ }
+ }
+}
diff --git a/src/de/steamwar/sql/internal/SqlTypeMapper.java b/src/de/steamwar/sql/internal/SqlTypeMapper.java
new file mode 100644
index 0000000..34c6173
--- /dev/null
+++ b/src/de/steamwar/sql/internal/SqlTypeMapper.java
@@ -0,0 +1,110 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2022 SteamWar.de-Serverteam
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.steamwar.sql.internal;
+
+import java.io.InputStream;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.util.Arrays;
+import java.util.IdentityHashMap;
+import java.util.Map;
+
+public final class SqlTypeMapper {
+ private static final Map, SqlTypeMapper>> mappers = new IdentityHashMap<>();
+
+ public static SqlTypeMapper getMapper(Class> clazz) {
+ return (SqlTypeMapper) mappers.get(clazz);
+ }
+
+ public static > void ordinalEnumMapper(Class type) {
+ T[] enumConstants = type.getEnumConstants();
+ new SqlTypeMapper<>(
+ type,
+ "INTEGER(" + (int)Math.ceil(enumConstants.length/256.0) + ")",
+ (rs, identifier) -> enumConstants[rs.getInt(identifier)],
+ (st, index, value) -> st.setInt(index, value.ordinal())
+ );
+ }
+
+ public static > void nameEnumMapper(Class type) {
+ new SqlTypeMapper<>(
+ type,
+ "VARCHAR(" + Arrays.stream(type.getEnumConstants()).map(e -> e.name().length()).max(Integer::compareTo).orElse(0) + ")",
+ (rs, identifier) -> Enum.valueOf(type, rs.getString(identifier)),
+ (st, index, value) -> st.setString(index, value.name())
+ );
+ }
+
+ static {
+ primitiveMapper(boolean.class, Boolean.class, "BOOLEAN", ResultSet::getBoolean, PreparedStatement::setBoolean);
+ primitiveMapper(byte.class, Byte.class, "INTEGER(1)", ResultSet::getByte, PreparedStatement::setByte);
+ primitiveMapper(short.class, Short.class, "INTEGER(2)", ResultSet::getShort, PreparedStatement::setShort);
+ primitiveMapper(int.class, Integer.class, "INTEGER", ResultSet::getInt, PreparedStatement::setInt);
+ primitiveMapper(long.class, Long.class, "INTEGER(8)", ResultSet::getLong, PreparedStatement::setLong);
+ primitiveMapper(float.class, Float.class, "REAL", ResultSet::getFloat, PreparedStatement::setFloat);
+ primitiveMapper(double.class, Double.class, "REAL", ResultSet::getDouble, PreparedStatement::setDouble);
+ new SqlTypeMapper<>(String.class, "TEXT", ResultSet::getString, PreparedStatement::setString);
+ new SqlTypeMapper<>(Timestamp.class, "TIMESTAMP", ResultSet::getTimestamp, PreparedStatement::setTimestamp);
+ new SqlTypeMapper<>(InputStream.class, "BLOB", ResultSet::getBinaryStream, PreparedStatement::setBinaryStream);
+ }
+
+ private static void primitiveMapper(Class primitive, Class wrapped, String sqlType, SQLReader reader, SQLWriter writer) {
+ new SqlTypeMapper<>(primitive, sqlType, reader, writer);
+ new SqlTypeMapper<>(wrapped, sqlType, (rs, identifier) -> {
+ T value = reader.read(rs, identifier);
+ return rs.wasNull() ? null : value;
+ }, writer);
+ }
+
+ private final String sqlType;
+ private final SQLReader reader;
+ private final SQLWriter writer;
+
+ public SqlTypeMapper(Class clazz, String sqlType, SQLReader reader, SQLWriter writer) {
+ this.sqlType = sqlType;
+ this.reader = reader;
+ this.writer = writer;
+ mappers.put(clazz, this);
+ }
+
+ public T read(ResultSet rs, String identifier) throws SQLException {
+ return reader.read(rs, identifier);
+ }
+
+ public void write(PreparedStatement st, int index, Object value) throws SQLException {
+ writer.write(st, index, (T) value);
+ }
+
+ public String sqlType() {
+ return sqlType;
+ }
+
+ @FunctionalInterface
+ public interface SQLReader {
+ T read(ResultSet rs, String identifier) throws SQLException;
+ }
+
+ @FunctionalInterface
+ public interface SQLWriter {
+ void write(PreparedStatement st, int index, T value) throws SQLException;
+ }
+}
diff --git a/src/de/steamwar/sql/internal/Statement.java b/src/de/steamwar/sql/internal/Statement.java
new file mode 100644
index 0000000..6c44332
--- /dev/null
+++ b/src/de/steamwar/sql/internal/Statement.java
@@ -0,0 +1,287 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2022 SteamWar.de-Serverteam
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.steamwar.sql.internal;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.sql.*;
+import java.util.*;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import java.util.function.UnaryOperator;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class Statement implements AutoCloseable {
+
+ private static final Logger logger = SQLConfig.impl.getLogger();
+
+ private static final List statements = new ArrayList<>();
+ private static final Deque connections = new ArrayDeque<>();
+ private static final int MAX_CONNECTIONS;
+ private static final Supplier conProvider;
+ static final Consumer> schemaCreator;
+ static final String ON_DUPLICATE_KEY;
+ static final UnaryOperator upsertWrapper;
+
+ private static final boolean MYSQL_MODE;
+ private static final boolean PRODUCTION_DATABASE;
+
+ static {
+ File file = new File(System.getProperty("user.home"), "mysql.properties");
+ MYSQL_MODE = file.exists();
+
+ if(MYSQL_MODE) {
+ Properties properties = new Properties();
+ try {
+ properties.load(new FileReader(file));
+ } catch (IOException e) {
+ throw new SecurityException("Could not load SQL connection", e);
+ }
+
+ String url = "jdbc:mysql://" + properties.getProperty("host") + ":" + properties.getProperty("port") + "/" + properties.getProperty("database") + "?useServerPrepStmts=true";
+ String user = properties.getProperty("user");
+ String password = properties.getProperty("password");
+
+ PRODUCTION_DATABASE = "core".equals(properties.getProperty("database"));
+ MAX_CONNECTIONS = SQLConfig.impl.maxConnections();
+ conProvider = () -> {
+ try {
+ return DriverManager.getConnection(url, user, password);
+ } catch (SQLException e) {
+ throw new SecurityException("Could not create MySQL connection", e);
+ }
+ };
+ schemaCreator = table -> {};
+ ON_DUPLICATE_KEY = " ON DUPLICATE KEY UPDATE ";
+ upsertWrapper = f -> f + " = VALUES(" + f + ")";
+ } else {
+ Connection connection;
+
+ try {
+ Class.forName("org.sqlite.JDBC");
+ connection = DriverManager.getConnection("jdbc:sqlite:" + System.getProperty("user.home") + "/standalone.db");
+ } catch (SQLException | ClassNotFoundException e) {
+ throw new SecurityException("Could not create sqlite connection", e);
+ }
+
+ PRODUCTION_DATABASE = false;
+ MAX_CONNECTIONS = 1;
+ conProvider = () -> connection;
+ schemaCreator = Table::ensureExistanceInSqlite;
+ ON_DUPLICATE_KEY = " ON CONFLICT DO UPDATE SET ";
+ upsertWrapper = f -> f + " = " + f;
+ }
+ }
+
+ private static int connectionBudget = MAX_CONNECTIONS;
+
+ public static void closeAll() {
+ synchronized (connections) {
+ while(connectionBudget < MAX_CONNECTIONS) {
+ if(connections.isEmpty())
+ waitOnConnections();
+ else
+ closeConnection(aquireConnection());
+ }
+ }
+ }
+
+ public static boolean mysqlMode() {
+ return MYSQL_MODE;
+ }
+
+ public static boolean productionDatabase() {
+ return PRODUCTION_DATABASE;
+ }
+
+ private final boolean returnGeneratedKeys;
+ private final String sql;
+ private final Map cachedStatements = new HashMap<>();
+
+ public Statement(String sql) {
+ this(sql, false);
+ }
+
+ public Statement(String sql, boolean returnGeneratedKeys) {
+ this.sql = sql;
+ this.returnGeneratedKeys = returnGeneratedKeys;
+ synchronized (statements) {
+ statements.add(this);
+ }
+ }
+
+ public T select(ResultSetUser user, Object... objects) {
+ return withConnection(st -> {
+ ResultSet rs = st.executeQuery();
+ T result = user.use(rs);
+ rs.close();
+ return result;
+ }, objects);
+ }
+
+ public void update(Object... objects) {
+ withConnection(PreparedStatement::executeUpdate, objects);
+ }
+
+ public int insertGetKey(Object... objects) {
+ return withConnection(st -> {
+ st.executeUpdate();
+ ResultSet rs = st.getGeneratedKeys();
+ rs.next();
+ return rs.getInt(1);
+ }, objects);
+ }
+
+ public String getSql() {
+ return sql;
+ }
+
+ private T withConnection(SQLRunnable runnable, Object... objects) {
+ Connection connection = aquireConnection();
+
+ try {
+ try {
+ return tryWithConnection(connection, runnable, objects);
+ } finally {
+ if(connectionInvalid(connection)) {
+ closeConnection(connection);
+ } else {
+ synchronized (connections) {
+ connections.push(connection);
+ connections.notify();
+ }
+ }
+ }
+ } catch (SQLException e) {
+ if(connectionInvalid(connection)) {
+ return withConnection(runnable, objects);
+ } else {
+ throw new SecurityException("Failing sql statement", e);
+ }
+ }
+ }
+
+ private boolean connectionInvalid(Connection connection) {
+ try {
+ return connection.isClosed();
+ } catch (SQLException e) {
+ logger.log(Level.INFO, "Could not check SQL connection status", e); // No database logging possible at this state
+ return true;
+ }
+ }
+
+ private T tryWithConnection(Connection connection, SQLRunnable runnable, Object... objects) throws SQLException {
+ PreparedStatement st = cachedStatements.get(connection);
+ if(st == null) {
+ if(returnGeneratedKeys)
+ st = connection.prepareStatement(sql, java.sql.Statement.RETURN_GENERATED_KEYS);
+ else
+ st = connection.prepareStatement(sql);
+ cachedStatements.put(connection, st);
+ }
+
+ for (int i = 0; i < objects.length; i++) {
+ Object o = objects[i];
+ if(o != null)
+ SqlTypeMapper.getMapper(o.getClass()).write(st, i+1, o);
+ else
+ st.setNull(i+1, Types.NULL);
+ }
+
+ return runnable.run(st);
+ }
+
+ @Override
+ public void close() {
+ cachedStatements.values().forEach(st -> closeStatement(st, false));
+ cachedStatements.clear();
+ synchronized (statements) {
+ statements.remove(this);
+ }
+ }
+
+ private void close(Connection connection) {
+ PreparedStatement st = cachedStatements.remove(connection);
+ if(st != null)
+ closeStatement(st, true);
+ }
+
+ private static Connection aquireConnection() {
+ synchronized (connections) {
+ if(connections.isEmpty() && connectionBudget == 0)
+ waitOnConnections();
+
+ if(!connections.isEmpty()) {
+ return connections.pop();
+ } else {
+ Connection connection = conProvider.get();
+ connectionBudget--;
+ return connection;
+ }
+ }
+ }
+
+ private static void closeConnection(Connection connection) {
+ synchronized (statements) {
+ for (Statement statement : statements) {
+ statement.close(connection);
+ }
+ }
+ try {
+ connection.close();
+ } catch (SQLException e) {
+ logger.log(Level.INFO, "Could not close connection", e);
+ }
+
+ synchronized (connections) {
+ connectionBudget++;
+ connections.notify();
+ }
+ }
+
+ private static void waitOnConnections() {
+ synchronized (connections) {
+ try {
+ connections.wait();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+
+ private static void closeStatement(PreparedStatement st, boolean silent) {
+ try {
+ st.close();
+ } catch (SQLException e) {
+ if(!silent)
+ logger.log(Level.INFO, "Could not close statement", e);
+ }
+ }
+
+ public interface ResultSetUser {
+ T use(ResultSet rs) throws SQLException;
+ }
+
+ private interface SQLRunnable {
+ T run(PreparedStatement st) throws SQLException;
+ }
+}
diff --git a/src/de/steamwar/sql/internal/Table.java b/src/de/steamwar/sql/internal/Table.java
new file mode 100644
index 0000000..cbad10a
--- /dev/null
+++ b/src/de/steamwar/sql/internal/Table.java
@@ -0,0 +1,137 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2022 SteamWar.de-Serverteam
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.steamwar.sql.internal;
+
+import java.lang.reflect.Constructor;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+public class Table {
+ public static final String PRIMARY = "primary";
+
+ final String name;
+ final TableField>[] fields;
+ private final Map> fieldsByIdentifier = new HashMap<>();
+ final Constructor constructor;
+
+ private final Map[]> keys;
+
+
+ public Table(Class clazz) {
+ this(clazz, clazz.getSimpleName());
+ }
+
+ public Table(Class clazz, String name) {
+ this.name = name;
+ this.fields = Arrays.stream(clazz.getDeclaredFields()).filter(field -> field.isAnnotationPresent(Field.class)).map(TableField::new).toArray(TableField[]::new);
+ try {
+ this.constructor = clazz.getDeclaredConstructor(Arrays.stream(clazz.getDeclaredFields()).filter(field -> field.isAnnotationPresent(Field.class)).map(java.lang.reflect.Field::getType).toArray(Class[]::new));
+ } catch (NoSuchMethodException e) {
+ throw new SecurityException(e);
+ }
+
+ keys = Arrays.stream(fields).flatMap(field -> Arrays.stream(field.field.keys())).distinct().collect(Collectors.toMap(Function.identity(), key -> Arrays.stream(fields).filter(field -> Arrays.asList(field.field.keys()).contains(key)).toArray(TableField[]::new)));
+
+ for (TableField> field : fields) {
+ fieldsByIdentifier.put(field.identifier.toLowerCase(), field);
+ }
+
+ Statement.schemaCreator.accept(this);
+ }
+
+ public SelectStatement select(String name) {
+ return selectFields(keyFields(name));
+ }
+ public SelectStatement selectFields(String... kfields) {
+ return new SelectStatement<>(this, kfields);
+ }
+
+ public Statement update(String name, String... fields) {
+ return updateFields(fields, keyFields(name));
+ }
+
+ public Statement updateField(String field, String... kfields) {
+ return updateFields(new String[]{field}, kfields);
+ }
+
+ public Statement updateFields(String[] fields, String... kfields) {
+ return new Statement("UPDATE " + name + " SET " + Arrays.stream(fields).map(f -> f + " = ?").collect(Collectors.joining(", ")) + " WHERE " + Arrays.stream(kfields).map(f -> f + " = ?").collect(Collectors.joining(" AND ")));
+ }
+
+ public Statement insert(String name) {
+ return insertFields(keyFields(name));
+ }
+
+ public Statement insertAll() {
+ return insertFields(false, Arrays.stream(fields).map(f -> f.identifier).toArray(String[]::new));
+ }
+
+ public Statement insertFields(String... fields) {
+ return insertFields(false, fields);
+ }
+
+ public Statement insertFields(boolean returnGeneratedKeys, String... fields) {
+ List nonKeyFields = Arrays.stream(fields).filter(f -> fieldsByIdentifier.get(f.toLowerCase()).field.keys().length == 0).collect(Collectors.toList());
+ return new Statement("INSERT INTO " + name + " (" + String.join(", ", fields) + ") VALUES (" + Arrays.stream(fields).map(f -> "?").collect(Collectors.joining(", ")) + ")" + (nonKeyFields.isEmpty() ? "" : Statement.ON_DUPLICATE_KEY + nonKeyFields.stream().map(Statement.upsertWrapper).collect(Collectors.joining(", "))), returnGeneratedKeys);
+ }
+
+ public Statement delete(String name) {
+ return deleteFields(keyFields(name));
+ }
+
+ public Statement deleteFields(String... kfields) {
+ return new Statement("DELETE FROM " + name + " WHERE " + Arrays.stream(kfields).map(f -> f + " = ?").collect(Collectors.joining(" AND ")));
+ }
+
+ void ensureExistanceInSqlite() {
+ try (Statement statement = new Statement(
+ "CREATE TABLE IF NOT EXISTS " + name + "(" +
+ Arrays.stream(fields).map(field -> field.identifier + " " + field.mapper.sqlType() + (field.field.nullable() ? " DEFAULT NULL" : " NOT NULL") + (field.field.nullable() || field.field.def().equals("") ? "" : " DEFAULT " + field.field.def())).collect(Collectors.joining(", ")) +
+ keys.entrySet().stream().map(key -> (key.getKey().equals(PRIMARY) ? ", PRIMARY KEY(" : ", UNIQUE (") + Arrays.stream(key.getValue()).map(field -> field.identifier).collect(Collectors.joining(", ")) + ")").collect(Collectors.joining(" ")) +
+ ")")) {
+ statement.update();
+ }
+ }
+
+ private String[] keyFields(String name) {
+ return Arrays.stream(keys.get(name)).map(f -> f.identifier).toArray(String[]::new);
+ }
+
+ static class TableField {
+
+ final String identifier;
+
+ final SqlTypeMapper mapper;
+ private final Field field;
+
+ private TableField(java.lang.reflect.Field field) {
+ this.identifier = field.getName();
+ this.mapper = SqlTypeMapper.getMapper(field.getType());
+ this.field = field.getAnnotation(Field.class);
+ }
+
+ T read(ResultSet rs) throws SQLException {
+ return mapper.read(rs, identifier);
+ }
+ }
+}