From 6d2950797310a244ce5bd23d6bb55c2fa52b6913 Mon Sep 17 00:00:00 2001 From: Lixfel Date: Tue, 24 May 2022 09:16:44 +0200 Subject: [PATCH 01/85] WIP commonDB Signed-off-by: Lixfel --- build.gradle | 2 + src/de/steamwar/ImplementationProvider.java | 34 +++ src/de/steamwar/sql/SQLConfig.java | 34 +++ src/de/steamwar/sql/Statement.java | 226 ++++++++++++++++++++ 4 files changed, 296 insertions(+) create mode 100644 src/de/steamwar/ImplementationProvider.java create mode 100644 src/de/steamwar/sql/SQLConfig.java create mode 100644 src/de/steamwar/sql/Statement.java diff --git a/build.gradle b/build.gradle index 41ba86c..1edd6eb 100644 --- a/build.gradle +++ b/build.gradle @@ -81,6 +81,8 @@ dependencies { testImplementation 'junit:junit:4.13.2' testImplementation 'org.hamcrest:hamcrest:2.2' + + implementation 'org.xerial:sqlite-jdbc:3.36.0' } task buildProject { diff --git a/src/de/steamwar/ImplementationProvider.java b/src/de/steamwar/ImplementationProvider.java new file mode 100644 index 0000000..1e8baec --- /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 SQLConfigProviderImpl", e); + } + } +} diff --git a/src/de/steamwar/sql/SQLConfig.java b/src/de/steamwar/sql/SQLConfig.java new file mode 100644 index 0000000..2420a0e --- /dev/null +++ b/src/de/steamwar/sql/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; + +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/Statement.java b/src/de/steamwar/sql/Statement.java new file mode 100644 index 0000000..4ea6142 --- /dev/null +++ b/src/de/steamwar/sql/Statement.java @@ -0,0 +1,226 @@ +/* + 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 java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.sql.*; +import java.util.*; +import java.util.function.Supplier; +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 { + File file = new File(new File("plugins", "SpigotCore"), "mysql.properties"); + + if(file.exists()) { + 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") + "?autoReconnect=true&useServerPrepStmts=true"; + String user = properties.getProperty("user"); + String password = properties.getProperty("password"); + + 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); + } + }; + } else { + MAX_CONNECTIONS = 1; + Connection connection; + + try { + connection = DriverManager.getConnection("jdbc:sqlite:standalone.db"); + } catch (SQLException e) { + throw new SecurityException("Could not create sqlite connection", e); + } + //TODO ensure schema + + conProvider = () -> connection; + } + } + + private static int connectionBudget = MAX_CONNECTIONS; + + public static void closeAll() { + synchronized (connections) { + while(connectionBudget < MAX_CONNECTIONS) { + if(connections.isEmpty()) + waitOnConnections(); + else + closeConnection(aquireConnection()); + } + } + } + + private final String sql; + private final Map cachedStatements = new HashMap<>(); + + public Statement(String sql) { + this.sql = sql; + 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); + } + + private T withConnection(SQLRunnable runnable, Object... objects) { + Connection connection = aquireConnection(); + + T result; + try { + result = tryWithConnection(connection, runnable, objects); + } catch (SQLException e) { + closeConnection(connection); + connection = aquireConnection(); + try { + result = tryWithConnection(connection, runnable, objects); + } catch (SQLException ex) { + closeConnection(connection); + throw new SecurityException("Could not execute statement", ex); + } + } + + synchronized (connections) { + connections.push(connection); + connections.notify(); + } + + return result; + } + + private T tryWithConnection(Connection connection, SQLRunnable runnable, Object... objects) throws SQLException { + PreparedStatement st = cachedStatements.get(connection); + if(st == null) { + st = connection.prepareStatement(sql); + cachedStatements.put(connection, st); + } + + for (int i = 0; i < objects.length; i++) { + st.setObject(i + 1, objects[i]); + } + + 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; + } +} From f6197978243d82cc078ba3a33b726f2b2dfc3f56 Mon Sep 17 00:00:00 2001 From: yoyosource Date: Mon, 30 May 2022 14:32:38 +0200 Subject: [PATCH 02/85] Fix enum mapper --- src/de/steamwar/command/SWCommandUtils.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/de/steamwar/command/SWCommandUtils.java b/src/de/steamwar/command/SWCommandUtils.java index e038e23..73468fa 100644 --- a/src/de/steamwar/command/SWCommandUtils.java +++ b/src/de/steamwar/command/SWCommandUtils.java @@ -218,12 +218,12 @@ public class SWCommandUtils { public static >, K> T createEnumMapper(Class> enumClass) { Map> enumMap = new HashMap<>(); for (Enum e : enumClass.getEnumConstants()) { - enumMap.put(e.name(), e); + enumMap.put(e.name().toLowerCase(), e); } return (T) new AbstractTypeMapper>() { @Override public Enum map(Object commandSender, String[] previousArguments, String s) { - return enumMap.get(s); + return enumMap.get(s.toLowerCase()); } @Override From 800e5073304ce7f353f497830389755b2bd250ee Mon Sep 17 00:00:00 2001 From: yoyosource Date: Mon, 30 May 2022 14:52:21 +0200 Subject: [PATCH 03/85] Fix SWCommandUtils Add SWTypeMapperCreator --- src/de/steamwar/command/SWCommandUtils.java | 36 ++++++------------- .../steamwar/command/SWTypeMapperCreator.java | 28 +++++++++++++++ 2 files changed, 38 insertions(+), 26 deletions(-) create mode 100644 src/de/steamwar/command/SWTypeMapperCreator.java diff --git a/src/de/steamwar/command/SWCommandUtils.java b/src/de/steamwar/command/SWCommandUtils.java index 73468fa..331b9f9 100644 --- a/src/de/steamwar/command/SWCommandUtils.java +++ b/src/de/steamwar/command/SWCommandUtils.java @@ -38,7 +38,11 @@ public class SWCommandUtils { @Getter private final Map> GUARD_FUNCTIONS = new HashMap<>(); - static { + private SWTypeMapperCreator swTypeMapperCreator; + + public static , K, V> void init(SWTypeMapperCreator swTypeMapperCreator) { + SWCommandUtils.swTypeMapperCreator = swTypeMapperCreator; + addMapper(boolean.class, Boolean.class, createMapper(s -> { if (s.equalsIgnoreCase("true")) return true; if (s.equalsIgnoreCase("false")) return false; @@ -197,22 +201,12 @@ public class SWCommandUtils { return createMapper((s) -> strings.contains(s) ? s : null, s -> strings); } - public static , K, V> T createMapper(Function mapper, Function> tabCompleter) { + public static , K, V> T createMapper(Function mapper, Function> tabCompleter) { return createMapper(mapper, (commandSender, s) -> tabCompleter.apply(s)); } - public static , K, V> T createMapper(Function mapper, BiFunction> tabCompleter) { - return (T) new AbstractTypeMapper() { - @Override - public V map(K commandSender, String[] previousArguments, String s) { - return mapper.apply(s); - } - - @Override - public List tabCompletes(K commandSender, String[] previous, String s) { - return tabCompleter.apply(commandSender, s); - } - }; + public static , K, V> T createMapper(Function mapper, BiFunction> tabCompleter) { + return (T) swTypeMapperCreator.createTypeMapper(mapper, tabCompleter); } public static >, K> T createEnumMapper(Class> enumClass) { @@ -220,17 +214,7 @@ public class SWCommandUtils { for (Enum e : enumClass.getEnumConstants()) { enumMap.put(e.name().toLowerCase(), e); } - return (T) new AbstractTypeMapper>() { - @Override - public Enum map(Object commandSender, String[] previousArguments, String s) { - return enumMap.get(s.toLowerCase()); - } - - @Override - public Collection tabCompletes(Object commandSender, String[] previousArguments, String s) { - return enumMap.keySet(); - } - }; + return createMapper(s -> enumMap.get(s.toLowerCase()), (k, s) -> enumMap.keySet()); } private static Function numberMapper(Function mapper) { @@ -249,7 +233,7 @@ public class SWCommandUtils { }; } - private static Function> numberCompleter(Function mapper) { + private static Function> numberCompleter(Function mapper) { return s -> numberMapper(mapper).apply(s) != null ? Collections.singletonList(s) : Collections.emptyList(); diff --git a/src/de/steamwar/command/SWTypeMapperCreator.java b/src/de/steamwar/command/SWTypeMapperCreator.java new file mode 100644 index 0000000..33c5dcb --- /dev/null +++ b/src/de/steamwar/command/SWTypeMapperCreator.java @@ -0,0 +1,28 @@ +/* + * 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.command; + +import java.util.List; +import java.util.function.BiFunction; +import java.util.function.Function; + +public interface SWTypeMapperCreator, K, V> { + T createTypeMapper(Function mapper, BiFunction> tabCompleter); +} From 57ab89e058b2feda1445981c3b48dbe356c9294f Mon Sep 17 00:00:00 2001 From: yoyosource Date: Mon, 30 May 2022 14:59:00 +0200 Subject: [PATCH 04/85] Fix SWCommandUtils --- src/de/steamwar/command/SWCommandUtils.java | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/de/steamwar/command/SWCommandUtils.java b/src/de/steamwar/command/SWCommandUtils.java index 331b9f9..6e486b1 100644 --- a/src/de/steamwar/command/SWCommandUtils.java +++ b/src/de/steamwar/command/SWCommandUtils.java @@ -38,11 +38,19 @@ public class SWCommandUtils { @Getter private final Map> GUARD_FUNCTIONS = new HashMap<>(); - private SWTypeMapperCreator swTypeMapperCreator; + private SWTypeMapperCreator swTypeMapperCreator = (mapper, tabCompleter) -> new AbstractTypeMapper() { + @Override + public Object map(Object sender, String[] previousArguments, String s) { + return mapper.apply(s); + } - public static , K, V> void init(SWTypeMapperCreator swTypeMapperCreator) { - SWCommandUtils.swTypeMapperCreator = swTypeMapperCreator; + @Override + public Collection tabCompletes(Object sender, String[] previousArguments, String s) { + return tabCompleter.apply(sender, s); + } + }; + static { addMapper(boolean.class, Boolean.class, createMapper(s -> { if (s.equalsIgnoreCase("true")) return true; if (s.equalsIgnoreCase("false")) return false; @@ -55,6 +63,10 @@ public class SWCommandUtils { MAPPER_FUNCTIONS.put(String.class.getTypeName(), createMapper(s -> s, Collections::singletonList)); } + public static , K, V> void init(SWTypeMapperCreator swTypeMapperCreator) { + SWCommandUtils.swTypeMapperCreator = swTypeMapperCreator; + } + private static void addMapper(Class clazz, Class alternativeClazz, AbstractTypeMapper mapper) { MAPPER_FUNCTIONS.put(clazz.getTypeName(), mapper); MAPPER_FUNCTIONS.put(alternativeClazz.getTypeName(), mapper); From b25bf2a79cbf2ff32478123dda728733bb840018 Mon Sep 17 00:00:00 2001 From: yoyosource Date: Mon, 30 May 2022 15:04:51 +0200 Subject: [PATCH 05/85] Fix SWCommandUtils --- src/de/steamwar/command/SWCommandUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/de/steamwar/command/SWCommandUtils.java b/src/de/steamwar/command/SWCommandUtils.java index 6e486b1..aaf0c7a 100644 --- a/src/de/steamwar/command/SWCommandUtils.java +++ b/src/de/steamwar/command/SWCommandUtils.java @@ -46,7 +46,7 @@ public class SWCommandUtils { @Override public Collection tabCompletes(Object sender, String[] previousArguments, String s) { - return tabCompleter.apply(sender, s); + return ((BiFunction>) tabCompleter).apply(sender, s); } }; From d6c29a25b917c85e68521421ee8e588c8195977a Mon Sep 17 00:00:00 2001 From: Lixfel Date: Wed, 8 Jun 2022 08:00:31 +0200 Subject: [PATCH 06/85] More commonDB Signed-off-by: Lixfel --- src/de/steamwar/ImplementationProvider.java | 2 +- src/de/steamwar/sql/Statement.java | 16 ++++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/de/steamwar/ImplementationProvider.java b/src/de/steamwar/ImplementationProvider.java index 1e8baec..e5b795e 100644 --- a/src/de/steamwar/ImplementationProvider.java +++ b/src/de/steamwar/ImplementationProvider.java @@ -28,7 +28,7 @@ public class ImplementationProvider { try { return (T) Class.forName(className).getDeclaredConstructor().newInstance(); } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException | ClassNotFoundException e) { - throw new SecurityException("Could not load SQLConfigProviderImpl", e); + throw new SecurityException("Could not load implementation", e); } } } diff --git a/src/de/steamwar/sql/Statement.java b/src/de/steamwar/sql/Statement.java index 4ea6142..a1d55f0 100644 --- a/src/de/steamwar/sql/Statement.java +++ b/src/de/steamwar/sql/Statement.java @@ -64,11 +64,12 @@ public class Statement implements AutoCloseable { Connection connection; try { + Class.forName("org.sqlite.JDBC"); connection = DriverManager.getConnection("jdbc:sqlite:standalone.db"); - } catch (SQLException e) { + //TODO schema + } catch (SQLException | ClassNotFoundException e) { throw new SecurityException("Could not create sqlite connection", e); } - //TODO ensure schema conProvider = () -> connection; } @@ -117,14 +118,17 @@ public class Statement implements AutoCloseable { try { result = tryWithConnection(connection, runnable, objects); } catch (SQLException e) { - closeConnection(connection); - connection = aquireConnection(); try { - result = tryWithConnection(connection, runnable, objects); + if(connection.isClosed() || !connection.isValid(1)) { + closeConnection(connection); + return withConnection(runnable, objects); + } } catch (SQLException ex) { closeConnection(connection); - throw new SecurityException("Could not execute statement", ex); + throw new SecurityException("Could not test connection validity", ex); } + + throw new SecurityException("Failing sql statement", e); } synchronized (connections) { From 29bc98889a38e181e007519ae11245ef8b447895 Mon Sep 17 00:00:00 2001 From: Lixfel Date: Sat, 11 Jun 2022 14:20:14 +0200 Subject: [PATCH 07/85] Prototyping new db interface Signed-off-by: Lixfel --- src/de/steamwar/sql/Field.java | 39 ++++++++++++++ src/de/steamwar/sql/Row.java | 35 ++++++++++++ src/de/steamwar/sql/Statement.java | 4 ++ src/de/steamwar/sql/Table.java | 87 ++++++++++++++++++++++++++++++ 4 files changed, 165 insertions(+) create mode 100644 src/de/steamwar/sql/Field.java create mode 100644 src/de/steamwar/sql/Row.java create mode 100644 src/de/steamwar/sql/Table.java diff --git a/src/de/steamwar/sql/Field.java b/src/de/steamwar/sql/Field.java new file mode 100644 index 0000000..8424665 --- /dev/null +++ b/src/de/steamwar/sql/Field.java @@ -0,0 +1,39 @@ +/* + * 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; + +public class Field { + + private final String identifier; + private final Class type; + + public Field(String identifier, Class type) { + this.identifier = identifier; + this.type = type; + } + + public String identifier() { + return identifier; + } + + public Class type() { + return type; + } +} diff --git a/src/de/steamwar/sql/Row.java b/src/de/steamwar/sql/Row.java new file mode 100644 index 0000000..c9ae439 --- /dev/null +++ b/src/de/steamwar/sql/Row.java @@ -0,0 +1,35 @@ +/* + * 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; + +public class Row { + + private final Table table; + private Object[] values; + + public Row(Table table, Object[] values) { + this.table = table; + this.values = values; + } + + void update(Field field, Object value) { + + } +} diff --git a/src/de/steamwar/sql/Statement.java b/src/de/steamwar/sql/Statement.java index a1d55f0..a54a4ee 100644 --- a/src/de/steamwar/sql/Statement.java +++ b/src/de/steamwar/sql/Statement.java @@ -111,6 +111,10 @@ public class Statement implements AutoCloseable { withConnection(PreparedStatement::executeUpdate, objects); } + public String getSql() { + return sql; + } + private T withConnection(SQLRunnable runnable, Object... objects) { Connection connection = aquireConnection(); diff --git a/src/de/steamwar/sql/Table.java b/src/de/steamwar/sql/Table.java new file mode 100644 index 0000000..239b3f8 --- /dev/null +++ b/src/de/steamwar/sql/Table.java @@ -0,0 +1,87 @@ +/* + * 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 java.util.*; +import java.util.logging.Level; +import java.util.stream.Collectors; + +public class Table { + + private final String name; + private final Field key; + private final Field[] fields; + + private final Map selectBy = new HashMap<>(); + + public Table(String name, Field key, Field... fields) { + this.name = name; + this.key = key; + this.fields = fields; + } + + public Row selectSingle(Field field, Object value) { + return select(rs -> { + if(!rs.next()) + return null; + + Object[] values = new Object[fields.length]; + for(int i = 0; i < fields.length; i++) { + values[i] = fields[i].type().cast(rs.getObject(i)); + } + + if(rs.next()) + SQLConfig.impl.getLogger().log(Level.WARNING, "Statement returned more than one result " + selectBy.get(field).getSql() + " for value " + value); + + return new Row(this, values); + }, field, value); + } + + public List selectMulti(Field field, Object value) { + return select(rs -> { + List result = new ArrayList<>(); + while(rs.next()) { + Object[] values = new Object[fields.length]; + for(int i = 0; i < fields.length; i++) { + values[i] = fields[i].type().cast(rs.getObject(i)); + } + + result.add(new Row(this, values)); + } + return result; + }, field, value); + } + + public void create() { + //TODO and alter table + } + + public Field getKey() { + return key; + } + + private T select(Statement.ResultSetUser u, Field field, Object value) { + Statement statement; + synchronized (selectBy) { + statement = selectBy.computeIfAbsent(field, f -> new Statement("SELECT " + Arrays.stream(fields).map(Field::identifier).collect(Collectors.joining(", ")) + " FROM " + name + " WHERE " + field.identifier() + " = ?")); + } + return statement.select(u, value); + } +} From 4f719e30ac67aceb153a206ef3b7202305a3700b Mon Sep 17 00:00:00 2001 From: Lixfel Date: Thu, 23 Jun 2022 14:58:33 +0200 Subject: [PATCH 08/85] First basic untested implementation Signed-off-by: Lixfel --- src/de/steamwar/sql/Field.java | 45 ++++++++++-- src/de/steamwar/sql/Row.java | 8 ++- src/de/steamwar/sql/SqlTypeParser.java | 27 +++++++ src/de/steamwar/sql/Statement.java | 2 +- src/de/steamwar/sql/Table.java | 99 ++++++++++++++++---------- 5 files changed, 138 insertions(+), 43 deletions(-) create mode 100644 src/de/steamwar/sql/SqlTypeParser.java diff --git a/src/de/steamwar/sql/Field.java b/src/de/steamwar/sql/Field.java index 8424665..2b40c8f 100644 --- a/src/de/steamwar/sql/Field.java +++ b/src/de/steamwar/sql/Field.java @@ -19,21 +19,58 @@ package de.steamwar.sql; -public class Field { +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; + +public class Field { + + private static final Map, String> sqlTypeMapping = new HashMap<>(); + private static final Map, SqlTypeParser> sqlTypeParser = new HashMap<>(); + + public static void addTypeMapping(Class clazz, String sqlType, SqlTypeParser parser) { + sqlTypeMapping.put(clazz, sqlType); + sqlTypeParser.put(clazz, parser); + } + + static { + addTypeMapping(String.class, "TEXT", (rs, field) -> rs.getString(field.identifier())); + addTypeMapping(boolean.class, "INTEGER(1)", (rs, field) -> rs.getBoolean(field.identifier())); + addTypeMapping(byte.class, "INTEGER(1)", (rs, field) -> rs.getByte(field.identifier())); + addTypeMapping(short.class, "INTEGER(2)", (rs, field) -> rs.getShort(field.identifier())); + addTypeMapping(int.class, "INTEGER(4)", (rs, field) -> rs.getInt(field.identifier())); + addTypeMapping(long.class, "INTEGER(8)", (rs, field) -> rs.getLong(field.identifier())); + addTypeMapping(float.class, "REAL", (rs, field) -> rs.getFloat(field.identifier())); + addTypeMapping(double.class, "REAL", (rs, field) -> rs.getDouble(field.identifier())); + } private final String identifier; - private final Class type; + private final Class type; - public Field(String identifier, Class type) { + private final SqlTypeParser parser; + private final String sqlType; + + public Field(String identifier, Class type) { this.identifier = identifier; this.type = type; + this.parser = (SqlTypeParser) sqlTypeParser.get(type); + this.sqlType = sqlTypeMapping.get(type); } public String identifier() { return identifier; } - public Class type() { + public Class type() { return type; } + + public String sqlType() { + return sqlType; + } + + public T parse(ResultSet rs) throws SQLException { + return parser.parse(rs, this); + } } diff --git a/src/de/steamwar/sql/Row.java b/src/de/steamwar/sql/Row.java index c9ae439..afd7056 100644 --- a/src/de/steamwar/sql/Row.java +++ b/src/de/steamwar/sql/Row.java @@ -24,12 +24,16 @@ public class Row { private final Table table; private Object[] values; - public Row(Table table, Object[] values) { + public Row(Table table, Object... values) { this.table = table; this.values = values; } - void update(Field field, Object value) { + private T get(Field field) { + return (T) values[table.getFieldId(field)]; + } + void update(Field[] fields, Object... values) { + table.update(values[table.keyId()], fields, values); } } diff --git a/src/de/steamwar/sql/SqlTypeParser.java b/src/de/steamwar/sql/SqlTypeParser.java new file mode 100644 index 0000000..377097c --- /dev/null +++ b/src/de/steamwar/sql/SqlTypeParser.java @@ -0,0 +1,27 @@ +/* + * 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 java.sql.ResultSet; +import java.sql.SQLException; + +public interface SqlTypeParser { + T parse(ResultSet rs, Field field) throws SQLException; +} diff --git a/src/de/steamwar/sql/Statement.java b/src/de/steamwar/sql/Statement.java index a54a4ee..bc9e32f 100644 --- a/src/de/steamwar/sql/Statement.java +++ b/src/de/steamwar/sql/Statement.java @@ -47,7 +47,7 @@ public class Statement implements AutoCloseable { throw new SecurityException("Could not load SQL connection", e); } - String url = "jdbc:mysql://" + properties.getProperty("host") + ":" + properties.getProperty("port") + "/" + properties.getProperty("database") + "?autoReconnect=true&useServerPrepStmts=true"; + 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"); diff --git a/src/de/steamwar/sql/Table.java b/src/de/steamwar/sql/Table.java index 239b3f8..4027532 100644 --- a/src/de/steamwar/sql/Table.java +++ b/src/de/steamwar/sql/Table.java @@ -19,69 +19,96 @@ package de.steamwar.sql; +import java.sql.ResultSet; +import java.sql.SQLException; import java.util.*; -import java.util.logging.Level; import java.util.stream.Collectors; public class Table { private final String name; - private final Field key; - private final Field[] fields; + private final Field key; + private final Field[] fields; + private final Map, Integer> fieldIds = new HashMap<>(); + private final int keyId; - private final Map selectBy = new HashMap<>(); + private final Map, Statement> cachedSelect = new HashMap<>(); + private final Map[], Statement> cachedInsert = new HashMap<>(); + private final Map[], Statement> cachedUpdate = new HashMap<>(); - public Table(String name, Field key, Field... fields) { + public Table(String name, Field key, Field... fields) { this.name = name; this.key = key; this.fields = fields; + for(int i = 0; i < fields.length; i++) { + fieldIds.put(fields[i], i); + } + keyId = fieldIds.get(key); } - public Row selectSingle(Field field, Object value) { + public Row selectSingle(Field field, Object value) { return select(rs -> { - if(!rs.next()) - return null; - - Object[] values = new Object[fields.length]; - for(int i = 0; i < fields.length; i++) { - values[i] = fields[i].type().cast(rs.getObject(i)); - } - if(rs.next()) - SQLConfig.impl.getLogger().log(Level.WARNING, "Statement returned more than one result " + selectBy.get(field).getSql() + " for value " + value); - - return new Row(this, values); + return read(rs); + return null; }, field, value); } - public List selectMulti(Field field, Object value) { + public List selectMulti(Field field, Object value) { return select(rs -> { List result = new ArrayList<>(); - while(rs.next()) { - Object[] values = new Object[fields.length]; - for(int i = 0; i < fields.length; i++) { - values[i] = fields[i].type().cast(rs.getObject(i)); - } + while(rs.next()) + result.add(read(rs)); - result.add(new Row(this, values)); - } return result; }, field, value); } - public void create() { - //TODO and alter table - } - - public Field getKey() { - return key; - } - - private T select(Statement.ResultSetUser u, Field field, Object value) { + public void insert(Field[] fields, Object... values) { Statement statement; - synchronized (selectBy) { - statement = selectBy.computeIfAbsent(field, f -> new Statement("SELECT " + Arrays.stream(fields).map(Field::identifier).collect(Collectors.joining(", ")) + " FROM " + name + " WHERE " + field.identifier() + " = ?")); + synchronized (cachedInsert) { + statement = cachedInsert.computeIfAbsent(fields, fs -> new Statement("INSERT INTO " + name + " (" + Arrays.stream(fs).map(Field::identifier).collect(Collectors.joining(", ")) + ") VALUES (" + Arrays.stream(fs).map(f -> "?").collect(Collectors.joining(", ")) + ")")); + } + statement.update(values); + } + + public void update(Object keyvalue, Field[] fields, Object... values) { + Statement statement; + synchronized (cachedUpdate) { + statement = cachedUpdate.computeIfAbsent(fields, fs -> new Statement("UPDATE " + name + " SET " + Arrays.stream(fs).map(f -> f.identifier() + " = ?").collect(Collectors.joining(", ")) + " WHERE " + key.identifier() + " = ?")); + } + statement.update(values, keyvalue); + } + + public void create() { + //TODO syntax mysql/sqlite + try (Statement statement = new Statement("CREATE TABLE IF NOT EXISTS " + name + "(" + Arrays.stream(fields).map(field -> field.identifier() + " " + field.sqlType() + (field == key ? " PRIMARY KEY" : "")).collect(Collectors.joining(", ")) + ") STRICT")) { + statement.update(); + } + } + + public int keyId() { + return keyId; + } + + public int getFieldId(Field field) { + return fieldIds.get(field); + } + + private T select(Statement.ResultSetUser u, Field field, Object value) { + Statement statement; + synchronized (cachedSelect) { + statement = cachedSelect.computeIfAbsent(field, f -> new Statement("SELECT " + Arrays.stream(fields).map(Field::identifier).collect(Collectors.joining(", ")) + " FROM " + name + " WHERE " + field.identifier() + " = ?")); } return statement.select(u, value); } + + private Row read(ResultSet rs) throws SQLException { + Object[] values = new Object[fields.length]; + for(int i = 0; i < fields.length; i++) { + values[i] = fields[i].parse(rs); + } + + return new Row(this, values); + } } From c5bed0acfa10d7f24a6a100a38d5afd649eafa75 Mon Sep 17 00:00:00 2001 From: Lixfel Date: Fri, 1 Jul 2022 21:37:33 +0200 Subject: [PATCH 09/85] WIP commonDb Signed-off-by: Lixfel --- src/de/steamwar/sql/Field.java | 1 + src/de/steamwar/sql/Row.java | 2 +- src/de/steamwar/sql/Table.java | 38 +++++++++++++++++----------------- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/de/steamwar/sql/Field.java b/src/de/steamwar/sql/Field.java index 2b40c8f..2180ca8 100644 --- a/src/de/steamwar/sql/Field.java +++ b/src/de/steamwar/sql/Field.java @@ -29,6 +29,7 @@ public class Field { private static final Map, String> sqlTypeMapping = new HashMap<>(); private static final Map, SqlTypeParser> sqlTypeParser = new HashMap<>(); + //TODO andere Richtung Objekt -> SQL public static void addTypeMapping(Class clazz, String sqlType, SqlTypeParser parser) { sqlTypeMapping.put(clazz, sqlType); sqlTypeParser.put(clazz, parser); diff --git a/src/de/steamwar/sql/Row.java b/src/de/steamwar/sql/Row.java index afd7056..53c6db2 100644 --- a/src/de/steamwar/sql/Row.java +++ b/src/de/steamwar/sql/Row.java @@ -34,6 +34,6 @@ public class Row { } void update(Field[] fields, Object... values) { - table.update(values[table.keyId()], fields, values); + table.update(values[table.keyIds()], fields, values); } } diff --git a/src/de/steamwar/sql/Table.java b/src/de/steamwar/sql/Table.java index 4027532..b8971f4 100644 --- a/src/de/steamwar/sql/Table.java +++ b/src/de/steamwar/sql/Table.java @@ -27,41 +27,41 @@ import java.util.stream.Collectors; public class Table { private final String name; - private final Field key; + private final Set> keys; private final Field[] fields; private final Map, Integer> fieldIds = new HashMap<>(); - private final int keyId; + private final List keyIds; - private final Map, Statement> cachedSelect = new HashMap<>(); + private final Map[], Statement> cachedSelect = new HashMap<>(); private final Map[], Statement> cachedInsert = new HashMap<>(); private final Map[], Statement> cachedUpdate = new HashMap<>(); - public Table(String name, Field key, Field... fields) { + public Table(String name, Field[] keys, Field... fields) { this.name = name; - this.key = key; + this.keys = Arrays.stream(keys).collect(Collectors.toSet()); this.fields = fields; for(int i = 0; i < fields.length; i++) { fieldIds.put(fields[i], i); } - keyId = fieldIds.get(key); + keyIds = Arrays.stream(keys).map(fieldIds::get).collect(Collectors.toList()); } - public Row selectSingle(Field field, Object value) { + public Row selectSingle(Field[] fields, Object... values) { return select(rs -> { if(rs.next()) return read(rs); return null; - }, field, value); + }, fields, values); } - public List selectMulti(Field field, Object value) { + public List selectMulti(Field[] fields, Object... values) { return select(rs -> { List result = new ArrayList<>(); while(rs.next()) result.add(read(rs)); return result; - }, field, value); + }, fields, values); } public void insert(Field[] fields, Object... values) { @@ -72,35 +72,35 @@ public class Table { statement.update(values); } - public void update(Object keyvalue, Field[] fields, Object... values) { + public void update(Object[] keyvalues, Field[] fields, Object... values) { Statement statement; synchronized (cachedUpdate) { - statement = cachedUpdate.computeIfAbsent(fields, fs -> new Statement("UPDATE " + name + " SET " + Arrays.stream(fs).map(f -> f.identifier() + " = ?").collect(Collectors.joining(", ")) + " WHERE " + key.identifier() + " = ?")); + statement = cachedUpdate.computeIfAbsent(fields, fs -> new Statement("UPDATE " + name + " SET " + Arrays.stream(fs).map(f -> f.identifier() + " = ?").collect(Collectors.joining(", ")) + " WHERE " + keys.stream().map(f -> f.identifier() + " = ?").collect(Collectors.joining(", ")))); } - statement.update(values, keyvalue); + statement.update(values, keyvalues); } public void create() { //TODO syntax mysql/sqlite - try (Statement statement = new Statement("CREATE TABLE IF NOT EXISTS " + name + "(" + Arrays.stream(fields).map(field -> field.identifier() + " " + field.sqlType() + (field == key ? " PRIMARY KEY" : "")).collect(Collectors.joining(", ")) + ") STRICT")) { + try (Statement statement = new Statement("CREATE TABLE IF NOT EXISTS " + name + "(" + Arrays.stream(fields).map(field -> field.identifier() + " " + field.sqlType() + (keys.contains(field) ? " PRIMARY KEY" : "")).collect(Collectors.joining(", ")) + ") STRICT")) { statement.update(); } } - public int keyId() { - return keyId; + public List keyIds() { + return keyIds; } public int getFieldId(Field field) { return fieldIds.get(field); } - private T select(Statement.ResultSetUser u, Field field, Object value) { + private T select(Statement.ResultSetUser u, Field[] fields, Object... values) { Statement statement; synchronized (cachedSelect) { - statement = cachedSelect.computeIfAbsent(field, f -> new Statement("SELECT " + Arrays.stream(fields).map(Field::identifier).collect(Collectors.joining(", ")) + " FROM " + name + " WHERE " + field.identifier() + " = ?")); + statement = cachedSelect.computeIfAbsent(fields, fs -> new Statement("SELECT " + Arrays.stream(fields).map(Field::identifier).collect(Collectors.joining(", ")) + " FROM " + name + " WHERE " + Arrays.stream(fs).map(f -> f.identifier() + " = ?").collect(Collectors.joining(", ")))); } - return statement.select(u, value); + return statement.select(u, values); } private Row read(ResultSet rs) throws SQLException { From 9ccb678522ee43e37bbaab5876268214d4de2653 Mon Sep 17 00:00:00 2001 From: Lixfel Date: Thu, 1 Sep 2022 17:17:39 +0200 Subject: [PATCH 10/85] WIP commonDB Signed-off-by: Lixfel --- src/de/steamwar/sql/Field.java | 65 ++-------- src/de/steamwar/sql/Row.java | 39 ------ src/de/steamwar/sql/SelectStatement.java | 72 +++++++++++ src/de/steamwar/sql/SqlTypeMapper.java | 100 ++++++++++++++++ src/de/steamwar/sql/SqlTypeParser.java | 27 ----- src/de/steamwar/sql/Statement.java | 13 +- src/de/steamwar/sql/Table.java | 145 +++++++++++++---------- 7 files changed, 273 insertions(+), 188 deletions(-) delete mode 100644 src/de/steamwar/sql/Row.java create mode 100644 src/de/steamwar/sql/SelectStatement.java create mode 100644 src/de/steamwar/sql/SqlTypeMapper.java delete mode 100644 src/de/steamwar/sql/SqlTypeParser.java diff --git a/src/de/steamwar/sql/Field.java b/src/de/steamwar/sql/Field.java index 2180ca8..656fdd9 100644 --- a/src/de/steamwar/sql/Field.java +++ b/src/de/steamwar/sql/Field.java @@ -19,59 +19,16 @@ package de.steamwar.sql; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.HashMap; -import java.util.Map; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; -public class Field { - - private static final Map, String> sqlTypeMapping = new HashMap<>(); - private static final Map, SqlTypeParser> sqlTypeParser = new HashMap<>(); - - //TODO andere Richtung Objekt -> SQL - public static void addTypeMapping(Class clazz, String sqlType, SqlTypeParser parser) { - sqlTypeMapping.put(clazz, sqlType); - sqlTypeParser.put(clazz, parser); - } - - static { - addTypeMapping(String.class, "TEXT", (rs, field) -> rs.getString(field.identifier())); - addTypeMapping(boolean.class, "INTEGER(1)", (rs, field) -> rs.getBoolean(field.identifier())); - addTypeMapping(byte.class, "INTEGER(1)", (rs, field) -> rs.getByte(field.identifier())); - addTypeMapping(short.class, "INTEGER(2)", (rs, field) -> rs.getShort(field.identifier())); - addTypeMapping(int.class, "INTEGER(4)", (rs, field) -> rs.getInt(field.identifier())); - addTypeMapping(long.class, "INTEGER(8)", (rs, field) -> rs.getLong(field.identifier())); - addTypeMapping(float.class, "REAL", (rs, field) -> rs.getFloat(field.identifier())); - addTypeMapping(double.class, "REAL", (rs, field) -> rs.getDouble(field.identifier())); - } - - private final String identifier; - private final Class type; - - private final SqlTypeParser parser; - private final String sqlType; - - public Field(String identifier, Class type) { - this.identifier = identifier; - this.type = type; - this.parser = (SqlTypeParser) sqlTypeParser.get(type); - this.sqlType = sqlTypeMapping.get(type); - } - - public String identifier() { - return identifier; - } - - public Class type() { - return type; - } - - public String sqlType() { - return sqlType; - } - - public T parse(ResultSet rs) throws SQLException { - return parser.parse(rs, this); - } +@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/Row.java b/src/de/steamwar/sql/Row.java deleted file mode 100644 index 53c6db2..0000000 --- a/src/de/steamwar/sql/Row.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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; - -public class Row { - - private final Table table; - private Object[] values; - - public Row(Table table, Object... values) { - this.table = table; - this.values = values; - } - - private T get(Field field) { - return (T) values[table.getFieldId(field)]; - } - - void update(Field[] fields, Object... values) { - table.update(values[table.keyIds()], fields, values); - } -} diff --git a/src/de/steamwar/sql/SelectStatement.java b/src/de/steamwar/sql/SelectStatement.java new file mode 100644 index 0000000..cc00ecc --- /dev/null +++ b/src/de/steamwar/sql/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; + +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(", "))); + } + + 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/SqlTypeMapper.java b/src/de/steamwar/sql/SqlTypeMapper.java new file mode 100644 index 0000000..68d2ee8 --- /dev/null +++ b/src/de/steamwar/sql/SqlTypeMapper.java @@ -0,0 +1,100 @@ +/* + * 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 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); + } + + static { + new SqlTypeMapper<>(String.class, "TEXT", ResultSet::getString, PreparedStatement::setString); + new SqlTypeMapper<>(Boolean.class, "BOOLEAN", ResultSet::getBoolean, PreparedStatement::setBoolean); + new SqlTypeMapper<>(Byte.class, "INTEGER(1)", ResultSet::getByte, PreparedStatement::setByte); + new SqlTypeMapper<>(Short.class, "INTEGER(2)", ResultSet::getShort, PreparedStatement::setShort); + new SqlTypeMapper<>(Integer.class, "INTEGER(4)", ResultSet::getInt, PreparedStatement::setInt); + new SqlTypeMapper<>(Long.class, "INTEGER(8)", ResultSet::getLong, PreparedStatement::setLong); + new SqlTypeMapper<>(Float.class, "REAL", ResultSet::getFloat, PreparedStatement::setFloat); + new SqlTypeMapper<>(Double.class, "REAL", ResultSet::getDouble, PreparedStatement::setDouble); + new SqlTypeMapper<>(Timestamp.class, "TIMESTAMP", ResultSet::getTimestamp, PreparedStatement::setTimestamp); + } + + 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) + ")", + (rs, identifier) -> Enum.valueOf(type, rs.getString(identifier)), + (st, index, value) -> st.setString(index, value.name()) + ); + } + + 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/SqlTypeParser.java b/src/de/steamwar/sql/SqlTypeParser.java deleted file mode 100644 index 377097c..0000000 --- a/src/de/steamwar/sql/SqlTypeParser.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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 java.sql.ResultSet; -import java.sql.SQLException; - -public interface SqlTypeParser { - T parse(ResultSet rs, Field field) throws SQLException; -} diff --git a/src/de/steamwar/sql/Statement.java b/src/de/steamwar/sql/Statement.java index bc9e32f..9626f0e 100644 --- a/src/de/steamwar/sql/Statement.java +++ b/src/de/steamwar/sql/Statement.java @@ -24,6 +24,7 @@ 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.logging.Level; import java.util.logging.Logger; @@ -36,8 +37,10 @@ public class Statement implements AutoCloseable { private static final Deque connections = new ArrayDeque<>(); private static final int MAX_CONNECTIONS; private static final Supplier conProvider; + static final Consumer> schemaCreator; + static { - File file = new File(new File("plugins", "SpigotCore"), "mysql.properties"); + File file = new File(System.getProperty("user.home"), "mysql.properties"); if(file.exists()) { Properties properties = new Properties(); @@ -59,19 +62,20 @@ public class Statement implements AutoCloseable { throw new SecurityException("Could not create MySQL connection", e); } }; + schemaCreator = table -> {}; } else { MAX_CONNECTIONS = 1; Connection connection; try { Class.forName("org.sqlite.JDBC"); - connection = DriverManager.getConnection("jdbc:sqlite:standalone.db"); - //TODO schema + connection = DriverManager.getConnection("jdbc:sqlite:" + System.getProperty("user.home") + "/standalone.db"); } catch (SQLException | ClassNotFoundException e) { throw new SecurityException("Could not create sqlite connection", e); } conProvider = () -> connection; + schemaCreator = Table::ensureExistanceInSqlite; } } @@ -151,7 +155,8 @@ public class Statement implements AutoCloseable { } for (int i = 0; i < objects.length; i++) { - st.setObject(i + 1, objects[i]); + Object o = objects[i]; + SqlTypeMapper.getMapper(o.getClass()).write(st, i+1, o); } return runnable.run(st); diff --git a/src/de/steamwar/sql/Table.java b/src/de/steamwar/sql/Table.java index b8971f4..74dfc83 100644 --- a/src/de/steamwar/sql/Table.java +++ b/src/de/steamwar/sql/Table.java @@ -19,96 +19,113 @@ package de.steamwar.sql; +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 class Table { + public static final String PRIMARY = "primary"; - private final String name; - private final Set> keys; - private final Field[] fields; - private final Map, Integer> fieldIds = new HashMap<>(); - private final List keyIds; + final String name; + final TableField[] fields; + private final Map> fieldsByIdentifier = new HashMap<>(); + final Constructor constructor; - private final Map[], Statement> cachedSelect = new HashMap<>(); - private final Map[], Statement> cachedInsert = new HashMap<>(); - private final Map[], Statement> cachedUpdate = new HashMap<>(); + private final Map[]> keys; - public Table(String name, Field[] keys, Field... fields) { + + public Table(Class clazz) { + this(clazz, clazz.getSimpleName()); + } + + public Table(Class clazz, String name) { this.name = name; - this.keys = Arrays.stream(keys).collect(Collectors.toSet()); - this.fields = fields; - for(int i = 0; i < fields.length; i++) { - fieldIds.put(fields[i], i); + 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); } - keyIds = Arrays.stream(keys).map(fieldIds::get).collect(Collectors.toList()); - } - public Row selectSingle(Field[] fields, Object... values) { - return select(rs -> { - if(rs.next()) - return read(rs); - return null; - }, fields, values); - } + 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))); - public List selectMulti(Field[] fields, Object... values) { - return select(rs -> { - List result = new ArrayList<>(); - while(rs.next()) - result.add(read(rs)); - - return result; - }, fields, values); - } - - public void insert(Field[] fields, Object... values) { - Statement statement; - synchronized (cachedInsert) { - statement = cachedInsert.computeIfAbsent(fields, fs -> new Statement("INSERT INTO " + name + " (" + Arrays.stream(fs).map(Field::identifier).collect(Collectors.joining(", ")) + ") VALUES (" + Arrays.stream(fs).map(f -> "?").collect(Collectors.joining(", ")) + ")")); + for (TableField field : fields) { + fieldsByIdentifier.put(field.identifier, field); } - statement.update(values); + + Statement.schemaCreator.accept(this); } - public void update(Object[] keyvalues, Field[] fields, Object... values) { - Statement statement; - synchronized (cachedUpdate) { - statement = cachedUpdate.computeIfAbsent(fields, fs -> new Statement("UPDATE " + name + " SET " + Arrays.stream(fs).map(f -> f.identifier() + " = ?").collect(Collectors.joining(", ")) + " WHERE " + keys.stream().map(f -> f.identifier() + " = ?").collect(Collectors.joining(", ")))); - } - statement.update(values, keyvalues); + + public SelectStatement select(String name) { + return selectFields(keyFields(name)); + } + public SelectStatement selectFields(String... kfields) { + return new SelectStatement<>(this, kfields); } - public void create() { - //TODO syntax mysql/sqlite - try (Statement statement = new Statement("CREATE TABLE IF NOT EXISTS " + name + "(" + Arrays.stream(fields).map(field -> field.identifier() + " " + field.sqlType() + (keys.contains(field) ? " PRIMARY KEY" : "")).collect(Collectors.joining(", ")) + ") STRICT")) { + 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(", "))); + } + + public Statement insert(String name) { + return insertFields(keyFields(name)); + } + + public Statement insertFields(String... fields) { + List nonKeyFields = Arrays.stream(fields).filter(f -> fieldsByIdentifier.get(f).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() ? "" : " ON DUPLICATE KEY UPDATE " + nonKeyFields.stream().map(f -> f + " = VALUES(" + f + ")").collect(Collectors.joining(", ")))); + } + + public Statement deleteWithKey(String name) { + return delete(keyFields(name)); + } + + public Statement delete(String... kfields) { + return new Statement("DELETE FROM " + name + " WHERE " + Arrays.stream(kfields).map(f -> f + " = ?").collect(Collectors.joining(", "))); + } + + void ensureExistanceInSqlite() { + List> primaryKey = keys.containsKey(PRIMARY) ? Arrays.asList(keys.get(PRIMARY)) : Collections.emptyList(); + try (Statement statement = new Statement( + "CREATE TABLE IF NOT EXISTS " + name + "(" + + Arrays.stream(fields).map(field -> field.identifier + " " + field.mapper.sqlType() + (field.field.nullable() ? "" : " NOT NULL DEFAULT NULL") + (!field.field.nullable() && field.field.def().equals("") ? "" : " DEFAULT " + field.field.def()) + (primaryKey.contains(field) ? " PRIMARY KEY" : "") + (field.field.autoincrement() ? " AUTOINCREMENT" : "")).collect(Collectors.joining(", ")) + + keys.entrySet().stream().filter(entry -> !entry.getKey().equals(PRIMARY)).map(key -> ", UNIQUE (" + Arrays.stream(key.getValue()).map(field -> field.identifier).collect(Collectors.joining(", ")) + ")").collect(Collectors.joining(" ")) + + ") STRICT, WITHOUT ROWID")) { statement.update(); } } - public List keyIds() { - return keyIds; + private String[] keyFields(String name) { + return Arrays.stream(keys.get(name)).map(f -> f.identifier).toArray(String[]::new); } - public int getFieldId(Field field) { - return fieldIds.get(field); - } + static class TableField { - private T select(Statement.ResultSetUser u, Field[] fields, Object... values) { - Statement statement; - synchronized (cachedSelect) { - statement = cachedSelect.computeIfAbsent(fields, fs -> new Statement("SELECT " + Arrays.stream(fields).map(Field::identifier).collect(Collectors.joining(", ")) + " FROM " + name + " WHERE " + Arrays.stream(fs).map(f -> f.identifier() + " = ?").collect(Collectors.joining(", ")))); - } - return statement.select(u, values); - } + final String identifier; - private Row read(ResultSet rs) throws SQLException { - Object[] values = new Object[fields.length]; - for(int i = 0; i < fields.length; i++) { - values[i] = fields[i].parse(rs); + final SqlTypeMapper mapper; + private final Field field; + + private TableField(java.lang.reflect.Field field) { + this.identifier = field.getName(); + this.mapper = (SqlTypeMapper) SqlTypeMapper.getMapper(field.getDeclaringClass()); + this.field = field.getAnnotation(Field.class); } - return new Row(this, values); + T read(ResultSet rs) throws SQLException { + return mapper.read(rs, identifier); + } } } From c1e175c3e84280c160ae5c7f84e44805f159bbf3 Mon Sep 17 00:00:00 2001 From: Lixfel Date: Thu, 8 Sep 2022 10:42:57 +0200 Subject: [PATCH 11/85] WIP current state --- src/de/steamwar/sql/BauweltMember.java | 79 +++++++++ src/de/steamwar/sql/Event.java | 72 +++++++++ src/de/steamwar/sql/EventFight.java | 72 +++++++++ src/de/steamwar/sql/Field.java | 34 ---- src/de/steamwar/sql/Fight.java | 61 +++++++ src/de/steamwar/sql/FightPlayer.java | 49 ++++++ src/de/steamwar/sql/NoClipboardException.java | 23 +++ src/de/steamwar/sql/NodeMember.java | 69 ++++++++ src/de/steamwar/sql/Replay.java | 73 +++++++++ src/de/steamwar/sql/SQLConfig.java | 34 ---- src/de/steamwar/sql/SQLWrapper.java | 31 ++++ src/de/steamwar/sql/SchematicType.java | 116 +++++++++++++ src/de/steamwar/sql/SteamwarUser.java | 153 ++++++++++++++++++ src/de/steamwar/sql/UserGroup.java | 45 ++++++ src/de/steamwar/sql/internal/Field.java | 34 ++++ src/de/steamwar/sql/internal/SQLConfig.java | 34 ++++ .../sql/{ => internal}/SelectStatement.java | 26 +-- .../sql/{ => internal}/SqlTypeMapper.java | 58 ++++--- .../sql/{ => internal}/Statement.java | 54 ++++--- src/de/steamwar/sql/{ => internal}/Table.java | 42 +++-- 20 files changed, 1018 insertions(+), 141 deletions(-) create mode 100644 src/de/steamwar/sql/BauweltMember.java create mode 100644 src/de/steamwar/sql/Event.java create mode 100644 src/de/steamwar/sql/EventFight.java delete mode 100644 src/de/steamwar/sql/Field.java create mode 100644 src/de/steamwar/sql/Fight.java create mode 100644 src/de/steamwar/sql/FightPlayer.java create mode 100644 src/de/steamwar/sql/NoClipboardException.java create mode 100644 src/de/steamwar/sql/NodeMember.java create mode 100644 src/de/steamwar/sql/Replay.java delete mode 100644 src/de/steamwar/sql/SQLConfig.java create mode 100644 src/de/steamwar/sql/SQLWrapper.java create mode 100644 src/de/steamwar/sql/SchematicType.java create mode 100644 src/de/steamwar/sql/SteamwarUser.java create mode 100644 src/de/steamwar/sql/UserGroup.java create mode 100644 src/de/steamwar/sql/internal/Field.java create mode 100644 src/de/steamwar/sql/internal/SQLConfig.java rename src/de/steamwar/sql/{ => internal}/SelectStatement.java (66%) rename src/de/steamwar/sql/{ => internal}/SqlTypeMapper.java (55%) rename src/de/steamwar/sql/{ => internal}/Statement.java (82%) rename src/de/steamwar/sql/{ => internal}/Table.java (79%) 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/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/Field.java b/src/de/steamwar/sql/Field.java deleted file mode 100644 index 656fdd9..0000000 --- a/src/de/steamwar/sql/Field.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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 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/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/NodeMember.java b/src/de/steamwar/sql/NodeMember.java new file mode 100644 index 0000000..f0de53c --- /dev/null +++ b/src/de/steamwar/sql/NodeMember.java @@ -0,0 +1,69 @@ +/* + * 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; + +import java.util.HashSet; +import java.util.Set; + +@AllArgsConstructor +public class NodeMember { + + 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("Node"); + private static final SelectStatement getSchematics = table.selectFields("Member"); + private static final Statement create = table.insertAll(); + private static final Statement delete = table.delete(Table.PRIMARY); + + @Getter + @Field(keys = {Table.PRIMARY}) + private final int node; + @Getter + @Field(keys = {Table.PRIMARY}) + private final int member; + + public void delete() { + delete.update(node, member); + } + + 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/Replay.java b/src/de/steamwar/sql/Replay.java new file mode 100644 index 0000000..90d2b34 --- /dev/null +++ b/src/de/steamwar/sql/Replay.java @@ -0,0 +1,73 @@ +/* + * 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.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()); + 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/SQLConfig.java b/src/de/steamwar/sql/SQLConfig.java deleted file mode 100644 index 2420a0e..0000000 --- a/src/de/steamwar/sql/SQLConfig.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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.logging.Logger; - -public interface SQLConfig { - SQLConfig impl = ImplementationProvider.getImpl("de.steamwar.sql.SQLConfigImpl"); - - Logger getLogger(); - - int maxConnections(); - - -} diff --git a/src/de/steamwar/sql/SQLWrapper.java b/src/de/steamwar/sql/SQLWrapper.java new file mode 100644 index 0000000..81abe61 --- /dev/null +++ b/src/de/steamwar/sql/SQLWrapper.java @@ -0,0 +1,31 @@ +/* + * 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); +} diff --git a/src/de/steamwar/sql/SchematicType.java b/src/de/steamwar/sql/SchematicType.java new file mode 100644 index 0000000..56c46ad --- /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.getOrDefault(input.toLowerCase(), null); + } + + public static List values(){ + return types; + } + + enum Type{ + NORMAL, + CHECK_TYPE, + FIGHT_TYPE + } +} diff --git a/src/de/steamwar/sql/SteamwarUser.java b/src/de/steamwar/sql/SteamwarUser.java new file mode 100644 index 0000000..7245db7 --- /dev/null +++ b/src/de/steamwar/sql/SteamwarUser.java @@ -0,0 +1,153 @@ +/* + * 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 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 != 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(); //TODO test correct locale on join (z.B. FightSystem Hotbarkit) + } + + 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 List getServerTeam() { + return getServerTeam.listSelect(); + } +} 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/SelectStatement.java b/src/de/steamwar/sql/internal/SelectStatement.java similarity index 66% rename from src/de/steamwar/sql/SelectStatement.java rename to src/de/steamwar/sql/internal/SelectStatement.java index cc00ecc..0f4a329 100644 --- a/src/de/steamwar/sql/SelectStatement.java +++ b/src/de/steamwar/sql/internal/SelectStatement.java @@ -1,23 +1,23 @@ /* - * This file is a part of the SteamWar software. + * This file is a part of the SteamWar software. * - * Copyright (C) 2022 SteamWar.de-Serverteam + * 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 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. + * 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 . + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . */ -package de.steamwar.sql; +package de.steamwar.sql.internal; import java.lang.reflect.InvocationTargetException; import java.sql.ResultSet; diff --git a/src/de/steamwar/sql/SqlTypeMapper.java b/src/de/steamwar/sql/internal/SqlTypeMapper.java similarity index 55% rename from src/de/steamwar/sql/SqlTypeMapper.java rename to src/de/steamwar/sql/internal/SqlTypeMapper.java index 68d2ee8..d364bc4 100644 --- a/src/de/steamwar/sql/SqlTypeMapper.java +++ b/src/de/steamwar/sql/internal/SqlTypeMapper.java @@ -1,23 +1,23 @@ /* - * This file is a part of the SteamWar software. + * This file is a part of the SteamWar software. * - * Copyright (C) 2022 SteamWar.de-Serverteam + * 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 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. + * 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 . + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . */ -package de.steamwar.sql; +package de.steamwar.sql.internal; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -34,18 +34,6 @@ public final class SqlTypeMapper { return (SqlTypeMapper) mappers.get(clazz); } - static { - new SqlTypeMapper<>(String.class, "TEXT", ResultSet::getString, PreparedStatement::setString); - new SqlTypeMapper<>(Boolean.class, "BOOLEAN", ResultSet::getBoolean, PreparedStatement::setBoolean); - new SqlTypeMapper<>(Byte.class, "INTEGER(1)", ResultSet::getByte, PreparedStatement::setByte); - new SqlTypeMapper<>(Short.class, "INTEGER(2)", ResultSet::getShort, PreparedStatement::setShort); - new SqlTypeMapper<>(Integer.class, "INTEGER(4)", ResultSet::getInt, PreparedStatement::setInt); - new SqlTypeMapper<>(Long.class, "INTEGER(8)", ResultSet::getLong, PreparedStatement::setLong); - new SqlTypeMapper<>(Float.class, "REAL", ResultSet::getFloat, PreparedStatement::setFloat); - new SqlTypeMapper<>(Double.class, "REAL", ResultSet::getDouble, PreparedStatement::setDouble); - new SqlTypeMapper<>(Timestamp.class, "TIMESTAMP", ResultSet::getTimestamp, PreparedStatement::setTimestamp); - } - public static > void ordinalEnumMapper(Class type) { T[] enumConstants = type.getEnumConstants(); new SqlTypeMapper<>( @@ -65,6 +53,26 @@ public final class SqlTypeMapper { ); } + 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(4)", 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); + } + + 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; diff --git a/src/de/steamwar/sql/Statement.java b/src/de/steamwar/sql/internal/Statement.java similarity index 82% rename from src/de/steamwar/sql/Statement.java rename to src/de/steamwar/sql/internal/Statement.java index 9626f0e..2e9edb8 100644 --- a/src/de/steamwar/sql/Statement.java +++ b/src/de/steamwar/sql/internal/Statement.java @@ -1,23 +1,23 @@ /* - 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 . + * 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; +package de.steamwar.sql.internal; import java.io.File; import java.io.FileReader; @@ -92,11 +92,17 @@ public class Statement implements AutoCloseable { } } + 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); } @@ -115,6 +121,15 @@ public class Statement implements AutoCloseable { 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; } @@ -150,7 +165,10 @@ public class Statement implements AutoCloseable { private T tryWithConnection(Connection connection, SQLRunnable runnable, Object... objects) throws SQLException { PreparedStatement st = cachedStatements.get(connection); if(st == null) { - st = connection.prepareStatement(sql); + if(returnGeneratedKeys) + st = connection.prepareStatement(sql, java.sql.Statement.RETURN_GENERATED_KEYS); + else + st = connection.prepareStatement(sql); cachedStatements.put(connection, st); } diff --git a/src/de/steamwar/sql/Table.java b/src/de/steamwar/sql/internal/Table.java similarity index 79% rename from src/de/steamwar/sql/Table.java rename to src/de/steamwar/sql/internal/Table.java index 74dfc83..ffef01c 100644 --- a/src/de/steamwar/sql/Table.java +++ b/src/de/steamwar/sql/internal/Table.java @@ -1,23 +1,23 @@ /* - * This file is a part of the SteamWar software. + * This file is a part of the SteamWar software. * - * Copyright (C) 2022 SteamWar.de-Serverteam + * 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 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. + * 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 . + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . */ -package de.steamwar.sql; +package de.steamwar.sql.internal; import java.lang.reflect.Constructor; import java.sql.ResultSet; @@ -83,16 +83,24 @@ public class Table { 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).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() ? "" : " ON DUPLICATE KEY UPDATE " + nonKeyFields.stream().map(f -> f + " = VALUES(" + f + ")").collect(Collectors.joining(", ")))); + return new Statement("INSERT INTO " + name + " (" + String.join(", ", fields) + ") VALUES (" + Arrays.stream(fields).map(f -> "?").collect(Collectors.joining(", ")) + ")" + (nonKeyFields.isEmpty() ? "" : " ON DUPLICATE KEY UPDATE " + nonKeyFields.stream().map(f -> f + " = VALUES(" + f + ")").collect(Collectors.joining(", "))), returnGeneratedKeys); } - public Statement deleteWithKey(String name) { - return delete(keyFields(name)); + public Statement delete(String name) { + return deleteFields(keyFields(name)); } - public Statement delete(String... kfields) { + public Statement deleteFields(String... kfields) { return new Statement("DELETE FROM " + name + " WHERE " + Arrays.stream(kfields).map(f -> f + " = ?").collect(Collectors.joining(", "))); } From 1758fc4297da23f6cae2e69115b510a6fb7d4aeb Mon Sep 17 00:00:00 2001 From: Lixfel Date: Tue, 20 Sep 2022 16:41:07 +0200 Subject: [PATCH 12/85] Full CommonDB untested --- src/de/steamwar/sql/CheckedSchematic.java | 71 +++ src/de/steamwar/sql/NodeDownload.java | 69 +++ src/de/steamwar/sql/Punishment.java | 119 +++++ src/de/steamwar/sql/SQLWrapper.java | 2 + src/de/steamwar/sql/SWException.java | 52 ++ src/de/steamwar/sql/SchemElo.java | 43 ++ src/de/steamwar/sql/SchematicNode.java | 543 ++++++++++++++++++++ src/de/steamwar/sql/SchematicType.java | 2 +- src/de/steamwar/sql/Season.java | 54 ++ src/de/steamwar/sql/SteamwarUser.java | 9 + src/de/steamwar/sql/Team.java | 61 +++ src/de/steamwar/sql/TeamTeilnahme.java | 54 ++ src/de/steamwar/sql/UserConfig.java | 72 +++ src/de/steamwar/sql/internal/Statement.java | 18 +- src/de/steamwar/sql/internal/Table.java | 3 +- 15 files changed, 1167 insertions(+), 5 deletions(-) create mode 100644 src/de/steamwar/sql/CheckedSchematic.java create mode 100644 src/de/steamwar/sql/NodeDownload.java create mode 100644 src/de/steamwar/sql/Punishment.java create mode 100644 src/de/steamwar/sql/SWException.java create mode 100644 src/de/steamwar/sql/SchemElo.java create mode 100644 src/de/steamwar/sql/SchematicNode.java create mode 100644 src/de/steamwar/sql/Season.java create mode 100644 src/de/steamwar/sql/Team.java create mode 100644 src/de/steamwar/sql/TeamTeilnahme.java create mode 100644 src/de/steamwar/sql/UserConfig.java 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/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/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/SQLWrapper.java b/src/de/steamwar/sql/SQLWrapper.java index 81abe61..cff7dec 100644 --- a/src/de/steamwar/sql/SQLWrapper.java +++ b/src/de/steamwar/sql/SQLWrapper.java @@ -28,4 +28,6 @@ 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..af1f9c1 --- /dev/null +++ b/src/de/steamwar/sql/SWException.java @@ -0,0 +1,52 @@ +/* + * 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; + +@AllArgsConstructor +public class SWException { + + 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.insertAll(); + + @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..cc62acc --- /dev/null +++ b/src/de/steamwar/sql/SchematicNode.java @@ -0,0 +1,543 @@ +/* + * 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"); + + @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, type, item); + 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, nodeItem, nodeType, lastUpdate, 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 index 56c46ad..d87065c 100644 --- a/src/de/steamwar/sql/SchematicType.java +++ b/src/de/steamwar/sql/SchematicType.java @@ -101,7 +101,7 @@ public class SchematicType { } public static SchematicType fromDB(String input){ - return fromDB.getOrDefault(input.toLowerCase(), null); + return fromDB.get(input.toLowerCase()); } public static List values(){ 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 index 7245db7..c5ccbd5 100644 --- a/src/de/steamwar/sql/SteamwarUser.java +++ b/src/de/steamwar/sql/SteamwarUser.java @@ -42,6 +42,7 @@ public class SteamwarUser { 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"); @@ -147,7 +148,15 @@ public class SteamwarUser { 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..0b45f1f --- /dev/null +++ b/src/de/steamwar/sql/UserConfig.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 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) { + return select.select(player, config).value; + } + + 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/internal/Statement.java b/src/de/steamwar/sql/internal/Statement.java index 2e9edb8..c436fc2 100644 --- a/src/de/steamwar/sql/internal/Statement.java +++ b/src/de/steamwar/sql/internal/Statement.java @@ -39,10 +39,14 @@ public class Statement implements AutoCloseable { private static final Supplier conProvider; static final Consumer> schemaCreator; + private static final boolean mysqlMode; + private static final boolean productionDatabase; + static { File file = new File(System.getProperty("user.home"), "mysql.properties"); + mysqlMode = file.exists(); - if(file.exists()) { + if(mysqlMode) { Properties properties = new Properties(); try { properties.load(new FileReader(file)); @@ -54,6 +58,7 @@ public class Statement implements AutoCloseable { String user = properties.getProperty("user"); String password = properties.getProperty("password"); + productionDatabase = "core".equals(properties.getProperty("database")); MAX_CONNECTIONS = SQLConfig.impl.maxConnections(); conProvider = () -> { try { @@ -64,7 +69,6 @@ public class Statement implements AutoCloseable { }; schemaCreator = table -> {}; } else { - MAX_CONNECTIONS = 1; Connection connection; try { @@ -74,6 +78,8 @@ public class Statement implements AutoCloseable { throw new SecurityException("Could not create sqlite connection", e); } + productionDatabase = false; + MAX_CONNECTIONS = 1; conProvider = () -> connection; schemaCreator = Table::ensureExistanceInSqlite; } @@ -92,6 +98,14 @@ public class Statement implements AutoCloseable { } } + public static boolean mysqlMode() { + return mysqlMode; + } + + public static boolean productionDatabase() { + return productionDatabase; + } + private final boolean returnGeneratedKeys; private final String sql; private final Map cachedStatements = new HashMap<>(); diff --git a/src/de/steamwar/sql/internal/Table.java b/src/de/steamwar/sql/internal/Table.java index ffef01c..937f54d 100644 --- a/src/de/steamwar/sql/internal/Table.java +++ b/src/de/steamwar/sql/internal/Table.java @@ -59,7 +59,6 @@ public class Table { Statement.schemaCreator.accept(this); } - public SelectStatement select(String name) { return selectFields(keyFields(name)); } @@ -108,7 +107,7 @@ public class Table { List> primaryKey = keys.containsKey(PRIMARY) ? Arrays.asList(keys.get(PRIMARY)) : Collections.emptyList(); try (Statement statement = new Statement( "CREATE TABLE IF NOT EXISTS " + name + "(" + - Arrays.stream(fields).map(field -> field.identifier + " " + field.mapper.sqlType() + (field.field.nullable() ? "" : " NOT NULL DEFAULT NULL") + (!field.field.nullable() && field.field.def().equals("") ? "" : " DEFAULT " + field.field.def()) + (primaryKey.contains(field) ? " PRIMARY KEY" : "") + (field.field.autoincrement() ? " AUTOINCREMENT" : "")).collect(Collectors.joining(", ")) + + 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()) + (primaryKey.contains(field) ? " PRIMARY KEY" : "") + (field.field.autoincrement() ? " AUTOINCREMENT" : "")).collect(Collectors.joining(", ")) + keys.entrySet().stream().filter(entry -> !entry.getKey().equals(PRIMARY)).map(key -> ", UNIQUE (" + Arrays.stream(key.getValue()).map(field -> field.identifier).collect(Collectors.joining(", ")) + ")").collect(Collectors.joining(" ")) + ") STRICT, WITHOUT ROWID")) { statement.update(); From d6213acab0e87e4d622c5a6b9611e3ba55289b7c Mon Sep 17 00:00:00 2001 From: Lixfel Date: Sat, 29 Oct 2022 12:42:41 +0200 Subject: [PATCH 13/85] Fix merge issues --- src/de/steamwar/sql/SWException.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/de/steamwar/sql/SWException.java b/src/de/steamwar/sql/SWException.java index af1f9c1..3e8084b 100644 --- a/src/de/steamwar/sql/SWException.java +++ b/src/de/steamwar/sql/SWException.java @@ -29,6 +29,10 @@ import java.io.File; @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(); From 92bea6255f6cae312674c7badffb7a8a44c4daee Mon Sep 17 00:00:00 2001 From: Lixfel Date: Sat, 29 Oct 2022 13:19:36 +0200 Subject: [PATCH 14/85] Fixes --- build.gradle | 2 +- src/de/steamwar/sql/SWException.java | 7 ++++++- src/de/steamwar/sql/internal/SqlTypeMapper.java | 4 ++-- src/de/steamwar/sql/internal/Statement.java | 6 ++++++ src/de/steamwar/sql/internal/Table.java | 6 +++--- 5 files changed, 18 insertions(+), 7 deletions(-) diff --git a/build.gradle b/build.gradle index 48d33bd..d5f000a 100644 --- a/build.gradle +++ b/build.gradle @@ -82,7 +82,7 @@ dependencies { testImplementation 'junit:junit:4.13.2' testImplementation 'org.hamcrest:hamcrest:2.2' - implementation 'org.xerial:sqlite-jdbc:3.36.0' + compileOnly 'org.xerial:sqlite-jdbc:3.36.0' } task buildResources { diff --git a/src/de/steamwar/sql/SWException.java b/src/de/steamwar/sql/SWException.java index 3e8084b..4354867 100644 --- a/src/de/steamwar/sql/SWException.java +++ b/src/de/steamwar/sql/SWException.java @@ -25,6 +25,7 @@ import de.steamwar.sql.internal.Table; import lombok.AllArgsConstructor; import java.io.File; +import java.sql.Timestamp; @AllArgsConstructor public class SWException { @@ -37,8 +38,12 @@ public class SWException { 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.insertAll(); + private static final Statement insert = table.insertFields("server", "message", "stacktrace"); + @Field(keys = {Table.PRIMARY}) + private final int id; + @Field(def = "CURRENT_TIMESTAMP") + private final Timestamp time; @Field private final String server; @Field diff --git a/src/de/steamwar/sql/internal/SqlTypeMapper.java b/src/de/steamwar/sql/internal/SqlTypeMapper.java index d364bc4..5c41010 100644 --- a/src/de/steamwar/sql/internal/SqlTypeMapper.java +++ b/src/de/steamwar/sql/internal/SqlTypeMapper.java @@ -30,7 +30,7 @@ import java.util.Map; public final class SqlTypeMapper { private static final Map, SqlTypeMapper> mappers = new IdentityHashMap<>(); - public static SqlTypeMapper getMapper(Class clazz) { + public static SqlTypeMapper getMapper(Class clazz) { return (SqlTypeMapper) mappers.get(clazz); } @@ -47,7 +47,7 @@ public final class SqlTypeMapper { public static > void nameEnumMapper(Class type) { new SqlTypeMapper<>( type, - "VARCHAR(" + Arrays.stream(type.getEnumConstants()).map(e -> e.name().length()).max(Integer::compareTo) + ")", + "VARCHAR(" + Arrays.stream(type.getEnumConstants()).map(e -> e.name().length()).max(Integer::compareTo).get() + ")", (rs, identifier) -> Enum.valueOf(type, rs.getString(identifier)), (st, index, value) -> st.setString(index, value.name()) ); diff --git a/src/de/steamwar/sql/internal/Statement.java b/src/de/steamwar/sql/internal/Statement.java index c436fc2..152784f 100644 --- a/src/de/steamwar/sql/internal/Statement.java +++ b/src/de/steamwar/sql/internal/Statement.java @@ -115,6 +115,7 @@ public class Statement implements AutoCloseable { } public Statement(String sql, boolean returnGeneratedKeys) { + System.out.println(sql); this.sql = sql; this.returnGeneratedKeys = returnGeneratedKeys; synchronized (statements) { @@ -165,6 +166,11 @@ public class Statement implements AutoCloseable { throw new SecurityException("Could not test connection validity", ex); } + synchronized (connections) { + connections.push(connection); + connections.notify(); + } + throw new SecurityException("Failing sql statement", e); } diff --git a/src/de/steamwar/sql/internal/Table.java b/src/de/steamwar/sql/internal/Table.java index 937f54d..f9dd6f7 100644 --- a/src/de/steamwar/sql/internal/Table.java +++ b/src/de/steamwar/sql/internal/Table.java @@ -107,9 +107,9 @@ public class Table { List> primaryKey = keys.containsKey(PRIMARY) ? Arrays.asList(keys.get(PRIMARY)) : Collections.emptyList(); 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()) + (primaryKey.contains(field) ? " PRIMARY KEY" : "") + (field.field.autoincrement() ? " AUTOINCREMENT" : "")).collect(Collectors.joining(", ")) + + 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()) + (primaryKey.contains(field) ? " PRIMARY KEY" : "")).collect(Collectors.joining(", ")) + keys.entrySet().stream().filter(entry -> !entry.getKey().equals(PRIMARY)).map(key -> ", UNIQUE (" + Arrays.stream(key.getValue()).map(field -> field.identifier).collect(Collectors.joining(", ")) + ")").collect(Collectors.joining(" ")) + - ") STRICT, WITHOUT ROWID")) { + ") WITHOUT ROWID")) { statement.update(); } } @@ -127,7 +127,7 @@ public class Table { private TableField(java.lang.reflect.Field field) { this.identifier = field.getName(); - this.mapper = (SqlTypeMapper) SqlTypeMapper.getMapper(field.getDeclaringClass()); + this.mapper = SqlTypeMapper.getMapper(field.getType()); this.field = field.getAnnotation(Field.class); } From bd626bb4e6366f1e5e7e64765a6e47fefe05b16a Mon Sep 17 00:00:00 2001 From: Lixfel Date: Wed, 2 Nov 2022 22:38:21 +0100 Subject: [PATCH 15/85] Current state --- src/de/steamwar/sql/NodeMember.java | 21 ++++++++++++----- src/de/steamwar/sql/Replay.java | 5 ++-- src/de/steamwar/sql/SWException.java | 2 +- src/de/steamwar/sql/SchematicNode.java | 6 ++++- src/de/steamwar/sql/SteamwarUser.java | 13 +++++++++-- .../sql/internal/SelectStatement.java | 2 +- .../steamwar/sql/internal/SqlTypeMapper.java | 4 ++-- src/de/steamwar/sql/internal/Statement.java | 23 ++++++++++++------- src/de/steamwar/sql/internal/Table.java | 13 +++++------ 9 files changed, 59 insertions(+), 30 deletions(-) diff --git a/src/de/steamwar/sql/NodeMember.java b/src/de/steamwar/sql/NodeMember.java index f0de53c..32036ab 100644 --- a/src/de/steamwar/sql/NodeMember.java +++ b/src/de/steamwar/sql/NodeMember.java @@ -24,7 +24,6 @@ import de.steamwar.sql.internal.SelectStatement; import de.steamwar.sql.internal.Statement; import de.steamwar.sql.internal.Table; import lombok.AllArgsConstructor; -import lombok.Getter; import java.util.HashSet; import java.util.Set; @@ -32,6 +31,10 @@ 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("Node"); @@ -39,15 +42,21 @@ public class NodeMember { private static final Statement create = table.insertAll(); private static final Statement delete = table.delete(Table.PRIMARY); - @Getter @Field(keys = {Table.PRIMARY}) - private final int node; - @Getter + private final int nodeId; @Field(keys = {Table.PRIMARY}) - private final int member; + private final int userId; + + public int getNodeId() { + return nodeId; + } + + public int getUserId() { + return userId; + } public void delete() { - delete.update(node, member); + delete.update(nodeId, userId); } public static NodeMember createNodeMember(int node, int member) { diff --git a/src/de/steamwar/sql/Replay.java b/src/de/steamwar/sql/Replay.java index 90d2b34..a90e239 100644 --- a/src/de/steamwar/sql/Replay.java +++ b/src/de/steamwar/sql/Replay.java @@ -28,6 +28,7 @@ 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 @@ -36,9 +37,9 @@ public class Replay { static { new SqlTypeMapper<>(File.class, "BLOB", (rs, identifier) -> { try { - File file = File.createTempFile("replay", "replay"); + File file = File.createTempFile("replay", ".replay"); file.deleteOnExit(); - Files.copy(rs.getBinaryStream(identifier), file.toPath()); + Files.copy(rs.getBinaryStream(identifier), file.toPath(), StandardCopyOption.REPLACE_EXISTING); return file; } catch (IOException e) { throw new SQLException(e); diff --git a/src/de/steamwar/sql/SWException.java b/src/de/steamwar/sql/SWException.java index 4354867..0a8ece3 100644 --- a/src/de/steamwar/sql/SWException.java +++ b/src/de/steamwar/sql/SWException.java @@ -40,7 +40,7 @@ public class SWException { private static final Table table = new Table<>(SWException.class, "Exception"); private static final Statement insert = table.insertFields("server", "message", "stacktrace"); - @Field(keys = {Table.PRIMARY}) + @Field(keys = {Table.PRIMARY}, autoincrement = true) private final int id; @Field(def = "CURRENT_TIMESTAMP") private final Timestamp time; diff --git a/src/de/steamwar/sql/SchematicNode.java b/src/de/steamwar/sql/SchematicNode.java index cc62acc..be7f1b1 100644 --- a/src/de/steamwar/sql/SchematicNode.java +++ b/src/de/steamwar/sql/SchematicNode.java @@ -72,6 +72,10 @@ public class SchematicNode { 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"}) @@ -476,7 +480,7 @@ public class SchematicNode { 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())) { + while (currentNode.getParentNode() != null && nodeMembers.stream().noneMatch(nodeMember -> nodeMember.getNodeId() == i.get())) { currentNode = currentNode.getParentNode(); i.set(currentNode.getId()); builder.insert(0, split) diff --git a/src/de/steamwar/sql/SteamwarUser.java b/src/de/steamwar/sql/SteamwarUser.java index c5ccbd5..874643f 100644 --- a/src/de/steamwar/sql/SteamwarUser.java +++ b/src/de/steamwar/sql/SteamwarUser.java @@ -101,7 +101,7 @@ public class SteamwarUser { this.leader = leader; this.locale = locale; this.manualLocale = manualLocale; - this.discordId = discordId != 0 ? discordId : null; + this.discordId = discordId != null && discordId != 0 ? discordId : null; usersById.put(id, this); usersByName.put(userName.toLowerCase(), this); @@ -118,7 +118,16 @@ public class SteamwarUser { public Locale getLocale() { if(locale != null) return locale; - return Locale.getDefault(); //TODO test correct locale on join (z.B. FightSystem Hotbarkit) + 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){ diff --git a/src/de/steamwar/sql/internal/SelectStatement.java b/src/de/steamwar/sql/internal/SelectStatement.java index 0f4a329..e17dcb7 100644 --- a/src/de/steamwar/sql/internal/SelectStatement.java +++ b/src/de/steamwar/sql/internal/SelectStatement.java @@ -31,7 +31,7 @@ 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(", "))); + 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) { diff --git a/src/de/steamwar/sql/internal/SqlTypeMapper.java b/src/de/steamwar/sql/internal/SqlTypeMapper.java index 5c41010..ae3b7c8 100644 --- a/src/de/steamwar/sql/internal/SqlTypeMapper.java +++ b/src/de/steamwar/sql/internal/SqlTypeMapper.java @@ -47,7 +47,7 @@ public final class SqlTypeMapper { public static > void nameEnumMapper(Class type) { new SqlTypeMapper<>( type, - "VARCHAR(" + Arrays.stream(type.getEnumConstants()).map(e -> e.name().length()).max(Integer::compareTo).get() + ")", + "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()) ); @@ -57,7 +57,7 @@ public final class SqlTypeMapper { 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(4)", ResultSet::getInt, PreparedStatement::setInt); + 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); diff --git a/src/de/steamwar/sql/internal/Statement.java b/src/de/steamwar/sql/internal/Statement.java index 152784f..88aa5d6 100644 --- a/src/de/steamwar/sql/internal/Statement.java +++ b/src/de/steamwar/sql/internal/Statement.java @@ -26,6 +26,7 @@ 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; @@ -38,15 +39,17 @@ public class Statement implements AutoCloseable { 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 mysqlMode; - private static final boolean productionDatabase; + private static final boolean MYSQL_MODE; + private static final boolean PRODUCTION_DATABASE; static { File file = new File(System.getProperty("user.home"), "mysql.properties"); - mysqlMode = file.exists(); + MYSQL_MODE = file.exists(); - if(mysqlMode) { + if(MYSQL_MODE) { Properties properties = new Properties(); try { properties.load(new FileReader(file)); @@ -58,7 +61,7 @@ public class Statement implements AutoCloseable { String user = properties.getProperty("user"); String password = properties.getProperty("password"); - productionDatabase = "core".equals(properties.getProperty("database")); + PRODUCTION_DATABASE = "core".equals(properties.getProperty("database")); MAX_CONNECTIONS = SQLConfig.impl.maxConnections(); conProvider = () -> { try { @@ -68,6 +71,8 @@ public class Statement implements AutoCloseable { } }; schemaCreator = table -> {}; + ON_DUPLICATE_KEY = " ON DUPLICATE KEY UPDATE "; + upsertWrapper = f -> f + " = VALUES(" + f + ")"; } else { Connection connection; @@ -78,10 +83,12 @@ public class Statement implements AutoCloseable { throw new SecurityException("Could not create sqlite connection", e); } - productionDatabase = false; + PRODUCTION_DATABASE = false; MAX_CONNECTIONS = 1; conProvider = () -> connection; schemaCreator = Table::ensureExistanceInSqlite; + ON_DUPLICATE_KEY = " ON CONFLICT DO UPDATE SET "; + upsertWrapper = f -> f + " = " + f; } } @@ -99,11 +106,11 @@ public class Statement implements AutoCloseable { } public static boolean mysqlMode() { - return mysqlMode; + return MYSQL_MODE; } public static boolean productionDatabase() { - return productionDatabase; + return PRODUCTION_DATABASE; } private final boolean returnGeneratedKeys; diff --git a/src/de/steamwar/sql/internal/Table.java b/src/de/steamwar/sql/internal/Table.java index f9dd6f7..fb72709 100644 --- a/src/de/steamwar/sql/internal/Table.java +++ b/src/de/steamwar/sql/internal/Table.java @@ -53,7 +53,7 @@ public class Table { 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, field); + fieldsByIdentifier.put(field.identifier.toLowerCase(), field); } Statement.schemaCreator.accept(this); @@ -91,8 +91,8 @@ public class Table { } public Statement insertFields(boolean returnGeneratedKeys, String... fields) { - List nonKeyFields = Arrays.stream(fields).filter(f -> fieldsByIdentifier.get(f).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() ? "" : " ON DUPLICATE KEY UPDATE " + nonKeyFields.stream().map(f -> f + " = VALUES(" + f + ")").collect(Collectors.joining(", "))), returnGeneratedKeys); + 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) { @@ -104,12 +104,11 @@ public class Table { } void ensureExistanceInSqlite() { - List> primaryKey = keys.containsKey(PRIMARY) ? Arrays.asList(keys.get(PRIMARY)) : Collections.emptyList(); 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()) + (primaryKey.contains(field) ? " PRIMARY KEY" : "")).collect(Collectors.joining(", ")) + - keys.entrySet().stream().filter(entry -> !entry.getKey().equals(PRIMARY)).map(key -> ", UNIQUE (" + Arrays.stream(key.getValue()).map(field -> field.identifier).collect(Collectors.joining(", ")) + ")").collect(Collectors.joining(" ")) + - ") WITHOUT ROWID")) { + 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(); } } From d4f9246f6815fa72db721bbe83e1ec3649679091 Mon Sep 17 00:00:00 2001 From: yoyosource Date: Sat, 12 Nov 2022 15:19:36 +0100 Subject: [PATCH 16/85] Update some stuff to strings --- .../steamwar/command/AbstractSWCommand.java | 52 +++++++++++++++++-- src/de/steamwar/command/CommandPart.java | 6 +++ src/de/steamwar/command/SWCommandUtils.java | 12 ++++- .../steamwar/command/ArgumentCommandTest.java | 2 + 4 files changed, 68 insertions(+), 4 deletions(-) diff --git a/src/de/steamwar/command/AbstractSWCommand.java b/src/de/steamwar/command/AbstractSWCommand.java index c23fd91..7e76d62 100644 --- a/src/de/steamwar/command/AbstractSWCommand.java +++ b/src/de/steamwar/command/AbstractSWCommand.java @@ -67,17 +67,54 @@ public abstract class AbstractSWCommand { protected void sendMessage(T sender, String message, Object[] args) {} + private String[] quote(String[] args) { + List list = new ArrayList<>(); + StringBuilder builder = new StringBuilder(); + boolean quote = false; + for (String arg : args) { + if (arg.startsWith("\"") && arg.endsWith("\"")) { + list.add(arg); + continue; + } + if (arg.startsWith("\"")) { + quote = true; + builder.append(arg); + continue; + } + if (arg.endsWith("\"")) { + quote = false; + builder.append(" ").append(arg); + list.add(builder.toString()); + builder = new StringBuilder(); + continue; + } + if (quote) { + builder.append(" ").append(arg); + continue; + } + list.add(arg); + } + if (quote) { + commandSystemWarning(() -> "Missing closing quote in command"); + } + if (builder.length() > 0) { + list.add(builder.toString()); + } + return list.toArray(new String[0]); + } + protected final void execute(T sender, String alias, String[] args) { initialize(); + String[] finalArgs = quote(args); List errors = new ArrayList<>(); try { - if (!commandList.stream().anyMatch(s -> s.invoke(errors::add, sender, alias, args))) { + if (!commandList.stream().anyMatch(s -> s.invoke(errors::add, sender, alias, finalArgs))) { if (!errors.isEmpty()) { errors.forEach(Runnable::run); return; } commandHelpList.stream().anyMatch(s -> s.invoke((ignore) -> { - }, sender, alias, args)); + }, sender, alias, finalArgs)); } } catch (CommandNoHelpException e) { // Ignored @@ -89,10 +126,11 @@ public abstract class AbstractSWCommand { protected final List tabComplete(T sender, String alias, String[] args) throws IllegalArgumentException { initialize(); + String[] finalArgs = quote(args); String string = args[args.length - 1].toLowerCase(); return commandList.stream() .filter(s -> !s.noTabComplete) - .map(s -> s.tabComplete(sender, args)) + .map(s -> s.tabComplete(sender, finalArgs)) .filter(Objects::nonNull) .flatMap(Collection::stream) .filter(s -> !s.isEmpty()) @@ -371,4 +409,12 @@ public abstract class AbstractSWCommand { @Target({ElementType.PARAMETER}) protected @interface AllowNull { } + + /** + * This annotation is used to mark a String to be quotable with multiple words. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.PARAMETER}) + protected @interface Quotable { + } } diff --git a/src/de/steamwar/command/CommandPart.java b/src/de/steamwar/command/CommandPart.java index 9f21ec8..a81c646 100644 --- a/src/de/steamwar/command/CommandPart.java +++ b/src/de/steamwar/command/CommandPart.java @@ -56,6 +56,9 @@ class CommandPart { @Setter private boolean allowNullValues = false; + @Setter + private boolean quotable = false; + private Parameter parameter; private int parameterIndex; @@ -183,6 +186,9 @@ class CommandPart { } catch (Exception e) { return new CheckArgumentResult(false, null); } + if (value instanceof String && !quotable && ((String) value).contains(" ")) { + return new CheckArgumentResult(false, null); + } if (validator != null && errors != null) { try { if (!validator.validate(sender, value, (s, objects) -> { diff --git a/src/de/steamwar/command/SWCommandUtils.java b/src/de/steamwar/command/SWCommandUtils.java index 32c46c4..5d0312c 100644 --- a/src/de/steamwar/command/SWCommandUtils.java +++ b/src/de/steamwar/command/SWCommandUtils.java @@ -62,7 +62,15 @@ public class SWCommandUtils { addMapper(double.class, Double.class, createMapper(numberMapper(Double::parseDouble), numberCompleter(Double::parseDouble))); addMapper(int.class, Integer.class, createMapper(numberMapper(Integer::parseInt), numberCompleter(Integer::parseInt))); addMapper(long.class, Long.class, createMapper(numberMapper(Long::parseLong), numberCompleter(Long::parseLong))); - MAPPER_FUNCTIONS.put(String.class.getTypeName(), createMapper(s -> s, Collections::singletonList)); + MAPPER_FUNCTIONS.put(String.class.getTypeName(), createMapper(s -> { + if (s.startsWith("\"") && s.endsWith("\"")) { + return s.substring(1, s.length() - 1); + } + if (s.startsWith("'") && s.endsWith("'")) { + return s.substring(1, s.length() - 1); + } + return s; + }, Collections::singletonList)); } public static , K, V> void init(SWTypeMapperCreator swTypeMapperCreator) { @@ -95,10 +103,12 @@ public class SWCommandUtils { Class varArgType = parameter.isVarArgs() ? parameter.getType().getComponentType() : null; AbstractSWCommand.OptionalValue optionalValue = parameter.getAnnotation(AbstractSWCommand.OptionalValue.class); AbstractSWCommand.AllowNull allowNull = parameter.getAnnotation(AbstractSWCommand.AllowNull.class); + AbstractSWCommand.Quotable quotable = parameter.getAnnotation(AbstractSWCommand.Quotable.class); CommandPart commandPart = new CommandPart<>(command, typeMapper, validator, varArgType, optionalValue != null ? optionalValue.value() : null, parameter, i); commandPart.setOnlyUseIfNoneIsGiven(optionalValue != null && optionalValue.onlyUINIG()); commandPart.setAllowNullValues(allowNull != null); + commandPart.setQuotable(quotable != null); if (current != null) { current.setNext(commandPart); } diff --git a/testsrc/de/steamwar/command/ArgumentCommandTest.java b/testsrc/de/steamwar/command/ArgumentCommandTest.java index e57edd0..013e035 100644 --- a/testsrc/de/steamwar/command/ArgumentCommandTest.java +++ b/testsrc/de/steamwar/command/ArgumentCommandTest.java @@ -97,6 +97,8 @@ public class ArgumentCommandTest { } catch (Exception e) { assertCMDFramework(e, ExecutionIdentifier.class, "RunArgument with String"); } + cmd.execute("test", "", new String[]{"\"Hello World\""}); + cmd.execute("test", "", new String[]{"\"Hello", "World\""}); } @Test From e9a39d007d83a93e7dc50ab2706c9f16213bc900 Mon Sep 17 00:00:00 2001 From: Lixfel Date: Tue, 15 Nov 2022 18:40:48 +0100 Subject: [PATCH 17/85] Current state --- src/de/steamwar/sql/NodeMember.java | 8 ++-- src/de/steamwar/sql/SchematicNode.java | 6 +-- src/de/steamwar/sql/UserConfig.java | 3 +- .../steamwar/sql/internal/SqlTypeMapper.java | 2 + src/de/steamwar/sql/internal/Statement.java | 45 ++++++++++--------- src/de/steamwar/sql/internal/Table.java | 4 +- 6 files changed, 37 insertions(+), 31 deletions(-) diff --git a/src/de/steamwar/sql/NodeMember.java b/src/de/steamwar/sql/NodeMember.java index 32036ab..e704ad0 100644 --- a/src/de/steamwar/sql/NodeMember.java +++ b/src/de/steamwar/sql/NodeMember.java @@ -37,8 +37,8 @@ public class NodeMember { 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("Node"); - private static final SelectStatement getSchematics = table.selectFields("Member"); + 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); @@ -47,11 +47,11 @@ public class NodeMember { @Field(keys = {Table.PRIMARY}) private final int userId; - public int getNodeId() { + public int getNode() { return nodeId; } - public int getUserId() { + public int getMember() { return userId; } diff --git a/src/de/steamwar/sql/SchematicNode.java b/src/de/steamwar/sql/SchematicNode.java index be7f1b1..78fa24b 100644 --- a/src/de/steamwar/sql/SchematicNode.java +++ b/src/de/steamwar/sql/SchematicNode.java @@ -139,7 +139,7 @@ public class SchematicNode { 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, type, item); + int nodeId = create.insertGetKey(owner, name, parent, item, type); return getSchematicNode(nodeId); } @@ -447,7 +447,7 @@ public class SchematicNode { private void updateDB() { this.lastUpdate = Timestamp.from(Instant.now()); - update.update(nodeId, nodeOwner, nodeName, parentNode, nodeItem, nodeType, lastUpdate, nodeRank, replaceColor, allowReplay, nodeFormat); + update.update(nodeId, nodeOwner, nodeName, parentNode, lastUpdate, nodeItem, nodeType, nodeRank, replaceColor, allowReplay, nodeFormat); this.brCache.clear(); TAB_CACHE.clear(); } @@ -480,7 +480,7 @@ public class SchematicNode { final Set nodeMembers = NodeMember.getSchematics(user.getId()); AtomicInteger i = new AtomicInteger(); i.set(currentNode.getId()); - while (currentNode.getParentNode() != null && nodeMembers.stream().noneMatch(nodeMember -> nodeMember.getNodeId() == i.get())) { + while (currentNode.getParentNode() != null && nodeMembers.stream().noneMatch(nodeMember -> nodeMember.getNode() == i.get())) { currentNode = currentNode.getParentNode(); i.set(currentNode.getId()); builder.insert(0, split) diff --git a/src/de/steamwar/sql/UserConfig.java b/src/de/steamwar/sql/UserConfig.java index 0b45f1f..f705dfd 100644 --- a/src/de/steamwar/sql/UserConfig.java +++ b/src/de/steamwar/sql/UserConfig.java @@ -47,7 +47,8 @@ public class UserConfig { } public static String getConfig(int player, String config) { - return select.select(player, config).value; + UserConfig value = select.select(player, config); + return value != null ? value.value : null; } public static void updatePlayerConfig(UUID uuid, String config, String value) { diff --git a/src/de/steamwar/sql/internal/SqlTypeMapper.java b/src/de/steamwar/sql/internal/SqlTypeMapper.java index ae3b7c8..34c6173 100644 --- a/src/de/steamwar/sql/internal/SqlTypeMapper.java +++ b/src/de/steamwar/sql/internal/SqlTypeMapper.java @@ -19,6 +19,7 @@ package de.steamwar.sql.internal; +import java.io.InputStream; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -63,6 +64,7 @@ public final class SqlTypeMapper { 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) { diff --git a/src/de/steamwar/sql/internal/Statement.java b/src/de/steamwar/sql/internal/Statement.java index 88aa5d6..6c44332 100644 --- a/src/de/steamwar/sql/internal/Statement.java +++ b/src/de/steamwar/sql/internal/Statement.java @@ -122,7 +122,6 @@ public class Statement implements AutoCloseable { } public Statement(String sql, boolean returnGeneratedKeys) { - System.out.println(sql); this.sql = sql; this.returnGeneratedKeys = returnGeneratedKeys; synchronized (statements) { @@ -159,34 +158,35 @@ public class Statement implements AutoCloseable { private T withConnection(SQLRunnable runnable, Object... objects) { Connection connection = aquireConnection(); - T result; try { - result = tryWithConnection(connection, runnable, objects); - } catch (SQLException e) { try { - if(connection.isClosed() || !connection.isValid(1)) { + return tryWithConnection(connection, runnable, objects); + } finally { + if(connectionInvalid(connection)) { closeConnection(connection); - return withConnection(runnable, objects); + } else { + synchronized (connections) { + connections.push(connection); + connections.notify(); + } } - } catch (SQLException ex) { - closeConnection(connection); - throw new SecurityException("Could not test connection validity", ex); } - - 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); } - - throw new SecurityException("Failing sql statement", e); } + } - synchronized (connections) { - connections.push(connection); - connections.notify(); + 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; } - - return result; } private T tryWithConnection(Connection connection, SQLRunnable runnable, Object... objects) throws SQLException { @@ -201,7 +201,10 @@ public class Statement implements AutoCloseable { for (int i = 0; i < objects.length; i++) { Object o = objects[i]; - SqlTypeMapper.getMapper(o.getClass()).write(st, i+1, o); + if(o != null) + SqlTypeMapper.getMapper(o.getClass()).write(st, i+1, o); + else + st.setNull(i+1, Types.NULL); } return runnable.run(st); diff --git a/src/de/steamwar/sql/internal/Table.java b/src/de/steamwar/sql/internal/Table.java index fb72709..cbad10a 100644 --- a/src/de/steamwar/sql/internal/Table.java +++ b/src/de/steamwar/sql/internal/Table.java @@ -75,7 +75,7 @@ public class Table { } 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(", "))); + 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) { @@ -100,7 +100,7 @@ public class Table { } public Statement deleteFields(String... kfields) { - return new Statement("DELETE FROM " + name + " WHERE " + Arrays.stream(kfields).map(f -> f + " = ?").collect(Collectors.joining(", "))); + return new Statement("DELETE FROM " + name + " WHERE " + Arrays.stream(kfields).map(f -> f + " = ?").collect(Collectors.joining(" AND "))); } void ensureExistanceInSqlite() { From 4b97753167c5b051c87abbe5ee9721bc989dbc20 Mon Sep 17 00:00:00 2001 From: Lixfel Date: Tue, 22 Nov 2022 14:52:06 +0100 Subject: [PATCH 18/85] Fix SchemElo NPE --- src/de/steamwar/sql/SchemElo.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/de/steamwar/sql/SchemElo.java b/src/de/steamwar/sql/SchemElo.java index 3aedaba..64090ce 100644 --- a/src/de/steamwar/sql/SchemElo.java +++ b/src/de/steamwar/sql/SchemElo.java @@ -38,6 +38,7 @@ public class SchemElo { private final int season; public static int getElo(SchematicNode node, int season) { - return select.select(node, season).elo; + SchemElo elo = select.select(node, season); + return elo != null ? elo.elo : 0; } } From bb601245f94e13e49021994fe61c6ef02f3beaad Mon Sep 17 00:00:00 2001 From: Lixfel Date: Tue, 22 Nov 2022 19:37:58 +0100 Subject: [PATCH 19/85] Fix Punishment userid column --- src/de/steamwar/sql/Punishment.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/de/steamwar/sql/Punishment.java b/src/de/steamwar/sql/Punishment.java index 22f54ce..086f445 100644 --- a/src/de/steamwar/sql/Punishment.java +++ b/src/de/steamwar/sql/Punishment.java @@ -47,8 +47,7 @@ public class Punishment { @Field(keys = {Table.PRIMARY}, autoincrement = true) private final int punishmentId; @Field - @Getter - private final int user; + private final int userId; @Field @Getter private final int punisher; @@ -95,6 +94,10 @@ public class Punishment { } } + public int getUserId() { + return userId; + } + public boolean isCurrent() { return isPerma() || getEndTime().after(new Date()); } From 2dcddd9cc2c5dd608db74feff2e22c3a7061000c Mon Sep 17 00:00:00 2001 From: Lixfel Date: Wed, 23 Nov 2022 20:33:04 +0100 Subject: [PATCH 20/85] Fix NodeDownload --- src/de/steamwar/sql/NodeDownload.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/de/steamwar/sql/NodeDownload.java b/src/de/steamwar/sql/NodeDownload.java index 0984096..b1bd29c 100644 --- a/src/de/steamwar/sql/NodeDownload.java +++ b/src/de/steamwar/sql/NodeDownload.java @@ -63,7 +63,7 @@ public class NodeDownload { 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]); + hexBuffer.append(HEX[b >>> 4]).append(HEX[b & 0xF]); return hexBuffer.toString(); } } From 4706c1525ceb40d8ea21aaeed4857ca8a32fd623 Mon Sep 17 00:00:00 2001 From: Lixfel Date: Wed, 23 Nov 2022 21:02:49 +0100 Subject: [PATCH 21/85] Fix NodeDownload --- src/de/steamwar/sql/NodeDownload.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/de/steamwar/sql/NodeDownload.java b/src/de/steamwar/sql/NodeDownload.java index b1bd29c..585b3db 100644 --- a/src/de/steamwar/sql/NodeDownload.java +++ b/src/de/steamwar/sql/NodeDownload.java @@ -63,7 +63,7 @@ public class NodeDownload { 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]); + hexBuffer.append(HEX[(b >>> 4) & 0xF]).append(HEX[b & 0xF]); return hexBuffer.toString(); } } From 746ae701d8abf1534de14b57ae650ac76e9c4d9d Mon Sep 17 00:00:00 2001 From: Lixfel Date: Wed, 30 Nov 2022 14:22:50 +0100 Subject: [PATCH 22/85] Fix Event SchemType --- src/de/steamwar/sql/Event.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/de/steamwar/sql/Event.java b/src/de/steamwar/sql/Event.java index b3f8f8c..a4c30e6 100644 --- a/src/de/steamwar/sql/Event.java +++ b/src/de/steamwar/sql/Event.java @@ -55,9 +55,8 @@ public class Event { @Getter @Field private final int maximumTeamMembers; - @Getter @Field(nullable = true) - private final SchematicType schematicType; + private final SchematicType schemType; @Field private final boolean publicSchemsOnly; @Field @@ -69,4 +68,8 @@ public class Event { public boolean spectateSystem(){ return spectateSystem; } + + public SchematicType getSchematicType() { + return schemType; + } } From 441661a8f59775032a95abe463ed1e03189fc570 Mon Sep 17 00:00:00 2001 From: yoyosource Date: Wed, 7 Dec 2022 22:33:37 +0100 Subject: [PATCH 23/85] Hotfix LinkageProcessor with generics --- src/de/steamwar/linkage/LinkageProcessor.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/de/steamwar/linkage/LinkageProcessor.java b/src/de/steamwar/linkage/LinkageProcessor.java index 5e34d44..9a650d6 100644 --- a/src/de/steamwar/linkage/LinkageProcessor.java +++ b/src/de/steamwar/linkage/LinkageProcessor.java @@ -292,6 +292,7 @@ public class LinkageProcessor extends AbstractProcessor { private LinkageType resolveSingle(TypeMirror typeMirror) { String qualifier = typeMirror.toString(); + if (qualifier.contains("<")) qualifier = qualifier.substring(0, qualifier.indexOf('<')); qualifier = qualifier.substring(qualifier.lastIndexOf('.') + 1); try { return (LinkageType) Class.forName("de.steamwar.linkage.types." + qualifier + "_" + context.name()).getDeclaredConstructor().newInstance(); From eb41602253f373fbe8d9eac42c9b998c8ea39745 Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Wed, 14 Dec 2022 21:47:13 +0100 Subject: [PATCH 24/85] Fix --- src/de/steamwar/sql/SchematicNode.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/de/steamwar/sql/SchematicNode.java b/src/de/steamwar/sql/SchematicNode.java index 78fa24b..0680aba 100644 --- a/src/de/steamwar/sql/SchematicNode.java +++ b/src/de/steamwar/sql/SchematicNode.java @@ -50,7 +50,7 @@ public class 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 update = table.updateFields(new String[]{"NodeName", "ParentNode", "NodeItem", "NodeType", "NodeRank", "ReplaceColor", "AllowReplay", "NodeFormat"}, "NodeId"); private static final Statement delete = table.delete(Table.PRIMARY); private static final SelectStatement byId = table.select(Table.PRIMARY); @@ -447,7 +447,7 @@ public class SchematicNode { private void updateDB() { this.lastUpdate = Timestamp.from(Instant.now()); - update.update(nodeId, nodeOwner, nodeName, parentNode, lastUpdate, nodeItem, nodeType, nodeRank, replaceColor, allowReplay, nodeFormat); + update.update(nodeOwner, nodeName, parentNode, lastUpdate, nodeItem, nodeType, nodeRank, replaceColor, allowReplay, nodeFormat, nodeId); this.brCache.clear(); TAB_CACHE.clear(); } From 136dc4a021092a5f24ebcd0472722cac31739cad Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Wed, 14 Dec 2022 21:50:09 +0100 Subject: [PATCH 25/85] Fix --- src/de/steamwar/sql/SchematicNode.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/de/steamwar/sql/SchematicNode.java b/src/de/steamwar/sql/SchematicNode.java index 0680aba..2def5c6 100644 --- a/src/de/steamwar/sql/SchematicNode.java +++ b/src/de/steamwar/sql/SchematicNode.java @@ -447,7 +447,7 @@ public class SchematicNode { private void updateDB() { this.lastUpdate = Timestamp.from(Instant.now()); - update.update(nodeOwner, nodeName, parentNode, lastUpdate, nodeItem, nodeType, nodeRank, replaceColor, allowReplay, nodeFormat, nodeId); + update.update(nodeOwner, nodeName, parentNode, nodeItem, nodeType, nodeRank, replaceColor, allowReplay, nodeFormat, nodeId); this.brCache.clear(); TAB_CACHE.clear(); } From fb9abf4c9cb9f76a1a99c47950e1019a945acd94 Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Wed, 14 Dec 2022 21:52:08 +0100 Subject: [PATCH 26/85] Fix --- src/de/steamwar/sql/SchematicNode.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/de/steamwar/sql/SchematicNode.java b/src/de/steamwar/sql/SchematicNode.java index 2def5c6..f77dfaa 100644 --- a/src/de/steamwar/sql/SchematicNode.java +++ b/src/de/steamwar/sql/SchematicNode.java @@ -49,8 +49,8 @@ public class 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.updateFields(new String[]{"NodeName", "ParentNode", "NodeItem", "NodeType", "NodeRank", "ReplaceColor", "AllowReplay", "NodeFormat"}, "NodeId"); + private static final Statement create = table.insertAll(); + private static final Statement update = table.update(Table.PRIMARY, "NodeName", "ParentNode", "NodeItem", "NodeType", "NodeRank", "ReplaceColor", "AllowReplay", "NodeFormat"); private static final Statement delete = table.delete(Table.PRIMARY); private static final SelectStatement byId = table.select(Table.PRIMARY); @@ -447,7 +447,7 @@ public class SchematicNode { private void updateDB() { this.lastUpdate = Timestamp.from(Instant.now()); - update.update(nodeOwner, nodeName, parentNode, nodeItem, nodeType, nodeRank, replaceColor, allowReplay, nodeFormat, nodeId); + update.update(nodeName, parentNode, nodeItem, nodeType, nodeRank, replaceColor, allowReplay, nodeFormat, nodeId); this.brCache.clear(); TAB_CACHE.clear(); } From 6fe09a9579bc2b333ba2a51712512e2ff83a8bdc Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Wed, 14 Dec 2022 21:53:52 +0100 Subject: [PATCH 27/85] Fix --- src/de/steamwar/sql/SchematicNode.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/de/steamwar/sql/SchematicNode.java b/src/de/steamwar/sql/SchematicNode.java index f77dfaa..3e610a6 100644 --- a/src/de/steamwar/sql/SchematicNode.java +++ b/src/de/steamwar/sql/SchematicNode.java @@ -49,7 +49,7 @@ public class SchematicNode { } private static final Table table = new Table<>(SchematicNode.class); - private static final Statement create = table.insertAll(); + private static final Statement create = table.insertFields(true, "NodeOwner", "NodeName", "ParentNode", "NodeItem", "NodeType"); private static final Statement update = table.update(Table.PRIMARY, "NodeName", "ParentNode", "NodeItem", "NodeType", "NodeRank", "ReplaceColor", "AllowReplay", "NodeFormat"); private static final Statement delete = table.delete(Table.PRIMARY); From 8d1b5c5f6658bd617bf6bf68b4994144741dc99d Mon Sep 17 00:00:00 2001 From: yoyosource Date: Sun, 18 Dec 2022 13:31:33 +0100 Subject: [PATCH 28/85] Update number type mapper --- src/de/steamwar/command/SWCommandUtils.java | 31 +++++++++++++++------ 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/de/steamwar/command/SWCommandUtils.java b/src/de/steamwar/command/SWCommandUtils.java index 32c46c4..b31ed20 100644 --- a/src/de/steamwar/command/SWCommandUtils.java +++ b/src/de/steamwar/command/SWCommandUtils.java @@ -58,10 +58,10 @@ public class SWCommandUtils { if (s.equalsIgnoreCase("false")) return false; return null; }, s -> Arrays.asList("true", "false"))); - addMapper(float.class, Float.class, createMapper(numberMapper(Float::parseFloat), numberCompleter(Float::parseFloat))); - addMapper(double.class, Double.class, createMapper(numberMapper(Double::parseDouble), numberCompleter(Double::parseDouble))); - addMapper(int.class, Integer.class, createMapper(numberMapper(Integer::parseInt), numberCompleter(Integer::parseInt))); - addMapper(long.class, Long.class, createMapper(numberMapper(Long::parseLong), numberCompleter(Long::parseLong))); + addMapper(float.class, Float.class, createMapper(numberMapper(Float::parseFloat), numberCompleter(Float::parseFloat, true))); + addMapper(double.class, Double.class, createMapper(numberMapper(Double::parseDouble), numberCompleter(Double::parseDouble, true))); + addMapper(int.class, Integer.class, createMapper(numberMapper(Integer::parseInt), numberCompleter(Integer::parseInt, false))); + addMapper(long.class, Long.class, createMapper(numberMapper(Long::parseLong), numberCompleter(Long::parseLong, false))); MAPPER_FUNCTIONS.put(String.class.getTypeName(), createMapper(s -> s, Collections::singletonList)); } @@ -255,10 +255,25 @@ public class SWCommandUtils { }; } - private static Function> numberCompleter(Function mapper) { - return s -> numberMapper(mapper).apply(s) != null - ? Collections.singletonList(s) - : Collections.emptyList(); + private static Function> numberCompleter(Function mapper, boolean comma) { + return s -> { + if (numberMapper(mapper).apply(s) == null) { + return Collections.emptyList(); + } + List strings = new ArrayList<>(); + if (s.length() == 0) { + strings.add("-"); + } else { + strings.add(s); + } + for (int i = 0; i < 10; i++) { + strings.add(s + i); + } + if (comma && (!s.contains(".") || !s.contains(","))) { + strings.add(s + "."); + } + return strings; + }; } static T[] getAnnotation(Method method, Class annotation) { From 2ae13e36425abc62358340cf4994d4c861cc4114 Mon Sep 17 00:00:00 2001 From: yoyosource Date: Sun, 18 Dec 2022 13:55:11 +0100 Subject: [PATCH 29/85] Add Quotable strings --- src/de/steamwar/command/SWCommandUtils.java | 2 ++ .../de/steamwar/command/ArgumentCommand.java | 2 +- .../steamwar/command/ArgumentCommandTest.java | 20 +++++++++++++++++-- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/de/steamwar/command/SWCommandUtils.java b/src/de/steamwar/command/SWCommandUtils.java index 8ebfec7..871439a 100644 --- a/src/de/steamwar/command/SWCommandUtils.java +++ b/src/de/steamwar/command/SWCommandUtils.java @@ -103,10 +103,12 @@ public class SWCommandUtils { Class varArgType = parameter.isVarArgs() ? parameter.getType().getComponentType() : null; AbstractSWCommand.OptionalValue optionalValue = parameter.getAnnotation(AbstractSWCommand.OptionalValue.class); AbstractSWCommand.AllowNull allowNull = parameter.getAnnotation(AbstractSWCommand.AllowNull.class); + AbstractSWCommand.Quotable quotable = parameter.getAnnotation(AbstractSWCommand.Quotable.class); CommandPart commandPart = new CommandPart<>(command, typeMapper, validator, varArgType, optionalValue != null ? optionalValue.value() : null, parameter, i); commandPart.setOnlyUseIfNoneIsGiven(optionalValue != null && optionalValue.onlyUINIG()); commandPart.setAllowNullValues(allowNull != null); + commandPart.setQuotable(quotable != null); if (current != null) { current.setNext(commandPart); } diff --git a/testsrc/de/steamwar/command/ArgumentCommand.java b/testsrc/de/steamwar/command/ArgumentCommand.java index a107270..ad1c1ec 100644 --- a/testsrc/de/steamwar/command/ArgumentCommand.java +++ b/testsrc/de/steamwar/command/ArgumentCommand.java @@ -54,7 +54,7 @@ public class ArgumentCommand extends TestSWCommand { } @Register - public void argument(String sender, String arg) { + public void argument(String sender, @Quotable String arg) { throw new ExecutionIdentifier("RunArgument with String"); } } diff --git a/testsrc/de/steamwar/command/ArgumentCommandTest.java b/testsrc/de/steamwar/command/ArgumentCommandTest.java index 013e035..fd4fb5f 100644 --- a/testsrc/de/steamwar/command/ArgumentCommandTest.java +++ b/testsrc/de/steamwar/command/ArgumentCommandTest.java @@ -44,6 +44,7 @@ public class ArgumentCommandTest { ArgumentCommand cmd = new ArgumentCommand(); try { cmd.execute("test", "", new String[]{"true", "false"}); + assert false; } catch (Exception e) { assertCMDFramework(e, ExecutionIdentifier.class, "RunArgument with Boolean"); } @@ -54,6 +55,7 @@ public class ArgumentCommandTest { ArgumentCommand cmd = new ArgumentCommand(); try { cmd.execute("test", "", new String[]{"0.0", "0.0", "0.0"}); + assert false; } catch (Exception e) { assertCMDFramework(e, ExecutionIdentifier.class, "RunArgument with Float"); } @@ -64,6 +66,7 @@ public class ArgumentCommandTest { ArgumentCommand cmd = new ArgumentCommand(); try { cmd.execute("test", "", new String[]{"0.0", "0.0", "0.0", "0.0"}); + assert false; } catch (Exception e) { assertCMDFramework(e, ExecutionIdentifier.class, "RunArgument with Double"); } @@ -74,6 +77,7 @@ public class ArgumentCommandTest { ArgumentCommand cmd = new ArgumentCommand(); try { cmd.execute("test", "", new String[]{"0"}); + assert false; } catch (Exception e) { assertCMDFramework(e, ExecutionIdentifier.class, "RunArgument with Integer"); } @@ -84,6 +88,7 @@ public class ArgumentCommandTest { ArgumentCommand cmd = new ArgumentCommand(); try { cmd.execute("test", "", new String[]{"0", "0"}); + assert false; } catch (Exception e) { assertCMDFramework(e, ExecutionIdentifier.class, "RunArgument with Long"); } @@ -94,11 +99,22 @@ public class ArgumentCommandTest { ArgumentCommand cmd = new ArgumentCommand(); try { cmd.execute("test", "", new String[]{"Hello World"}); + assert false; + } catch (Exception e) { + assertCMDFramework(e, ExecutionIdentifier.class, "RunArgument with String"); + } + try { + cmd.execute("test", "", new String[]{"\"Hello World\""}); + assert false; + } catch (Exception e) { + assertCMDFramework(e, ExecutionIdentifier.class, "RunArgument with String"); + } + try { + cmd.execute("test", "", new String[]{"\"Hello", "World\""}); + assert false; } catch (Exception e) { assertCMDFramework(e, ExecutionIdentifier.class, "RunArgument with String"); } - cmd.execute("test", "", new String[]{"\"Hello World\""}); - cmd.execute("test", "", new String[]{"\"Hello", "World\""}); } @Test From 4b8310d4f855d90de26ab4630e9e26e371557b31 Mon Sep 17 00:00:00 2001 From: yoyosource Date: Sun, 18 Dec 2022 17:14:01 +0100 Subject: [PATCH 30/85] Add ApplicableTypes for better type safety Add AbstractSWCommand.Min Add AbstractSWCommand.Max --- .../steamwar/command/AbstractSWCommand.java | 107 ++++++++++------- .../steamwar/command/AbstractValidator.java | 2 + ...elpException.java => ApplicableTypes.java} | 14 ++- src/de/steamwar/command/CommandPart.java | 40 +++---- src/de/steamwar/command/SWCommandUtils.java | 38 ------ src/de/steamwar/command/SubCommand.java | 112 +++++++++++++++++- .../command/BetterExceptionCommand.java | 1 + .../command/NumberValidatorCommand.java | 35 ++++++ .../command/NumberValidatorCommandTest.java | 51 ++++++++ .../de/steamwar/command/SimpleCommand.java | 3 +- .../steamwar/command/dto/TestSWCommand.java | 2 + 11 files changed, 294 insertions(+), 111 deletions(-) rename src/de/steamwar/command/{CommandNoHelpException.java => ApplicableTypes.java} (69%) create mode 100644 testsrc/de/steamwar/command/NumberValidatorCommand.java create mode 100644 testsrc/de/steamwar/command/NumberValidatorCommandTest.java diff --git a/src/de/steamwar/command/AbstractSWCommand.java b/src/de/steamwar/command/AbstractSWCommand.java index 7e76d62..9ff2e28 100644 --- a/src/de/steamwar/command/AbstractSWCommand.java +++ b/src/de/steamwar/command/AbstractSWCommand.java @@ -35,7 +35,6 @@ public abstract class AbstractSWCommand { private boolean initialized = false; protected final List> commandList = new ArrayList<>(); - protected final List> commandHelpList = new ArrayList<>(); private final Map> localTypeMapper = new HashMap<>(); private final Map> localValidators = new HashMap<>(); @@ -109,15 +108,8 @@ public abstract class AbstractSWCommand { List errors = new ArrayList<>(); try { if (!commandList.stream().anyMatch(s -> s.invoke(errors::add, sender, alias, finalArgs))) { - if (!errors.isEmpty()) { - errors.forEach(Runnable::run); - return; - } - commandHelpList.stream().anyMatch(s -> s.invoke((ignore) -> { - }, sender, alias, finalArgs)); + errors.forEach(Runnable::run); } - } catch (CommandNoHelpException e) { - // Ignored } catch (CommandFrameworkException e) { commandSystemError(sender, e); throw e; @@ -139,12 +131,8 @@ public abstract class AbstractSWCommand { .collect(Collectors.toList()); } - private void initialize() { + private synchronized void initialize() { if (initialized) return; - createMapping(); - } - - private synchronized void createMapping() { List methods = methods(); for (Method method : methods) { Cached cached = method.getAnnotation(Cached.class); @@ -181,32 +169,13 @@ public abstract class AbstractSWCommand { } for (Method method : methods) { add(Register.class, method, i -> i > 0, true, null, (anno, parameters) -> { - if (!anno.help()) return; - boolean error = false; - if (parameters.length != 2) { - commandSystemWarning(() -> "The method '" + method.toString() + "' is lacking parameters or has too many"); - error = true; - } - if (!parameters[parameters.length - 1].isVarArgs()) { - commandSystemWarning(() -> "The method '" + method.toString() + "' is lacking the varArgs parameters as last Argument"); - error = true; - } - if (parameters[parameters.length - 1].getType().getComponentType() != String.class) { - commandSystemWarning(() -> "The method '" + method.toString() + "' is lacking the varArgs parameters of type '" + String.class.getTypeName() + "' as last Argument"); - error = true; - } - if (error) return; - commandHelpList.add(new SubCommand<>(this, method, anno.value(), new HashMap<>(), new HashMap<>(), true, null, anno.noTabComplete())); - }); - - add(Register.class, method, i -> i > 0, true, null, (anno, parameters) -> { - if (anno.help()) return; for (int i = 1; i < parameters.length; i++) { Parameter parameter = parameters[i]; Class clazz = parameter.getType(); if (parameter.isVarArgs() && i == parameters.length - 1) { clazz = parameter.getType().getComponentType(); } + checkAnnotationApplicability(parameter, clazz); Mapper mapper = parameter.getAnnotation(Mapper.class); if (clazz.isEnum() && mapper == null && !SWCommandUtils.getMAPPER_FUNCTIONS().containsKey(clazz.getTypeName())) { continue; @@ -217,7 +186,7 @@ public abstract class AbstractSWCommand { return; } } - commandList.add(new SubCommand<>(this, method, anno.value(), localTypeMapper, localValidators, false, anno.description(), anno.noTabComplete())); + commandList.add(new SubCommand<>(this, method, anno.value(), localTypeMapper, localValidators, anno.description(), anno.noTabComplete())); }); } @@ -229,18 +198,28 @@ public abstract class AbstractSWCommand { return Integer.compare(o1.comparableValue, o2.comparableValue); } }); - commandHelpList.sort((o1, o2) -> { - int compare = Integer.compare(-o1.subCommand.length, -o2.subCommand.length); - if (compare != 0) { - return compare; - } else { - return Integer.compare(o1.method.getDeclaringClass() == AbstractSWCommand.class ? 1 : 0, - o2.method.getDeclaringClass() == AbstractSWCommand.class ? 1 : 0); - } - }); initialized = true; } + private void checkAnnotationApplicability(Parameter parameter, Class clazz) { + Annotation[] annotations = parameter.getAnnotations(); + for (Annotation annotation : annotations) { + ApplicableTypes applicableTypes = annotation.annotationType().getAnnotation(ApplicableTypes.class); + if (applicableTypes == null) continue; + Class[] types = applicableTypes.value(); + boolean applicable = false; + for (Class type : types) { + if (type.isAssignableFrom(clazz)) { + applicable = true; + break; + } + } + if (!applicable) { + commandSystemWarning(() -> "The parameter '" + parameter.toString() + "' is using an unsupported annotation of type '" + annotation.annotationType().getName() + "'"); + } + } + } + private void add(Class annotation, Method method, IntPredicate parameterTester, boolean firstParameter, Class returnType, BiConsumer consumer) { T[] anno = SWCommandUtils.getAnnotation(method, annotation); if (anno == null || anno.length == 0) return; @@ -300,12 +279,22 @@ public abstract class AbstractSWCommand { return methods; } + // --- Annotation for the command --- + + /** + * Annotation for registering a method as a command + */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) @Repeatable(Register.Registeres.class) protected @interface Register { + + /** + * Identifier of subcommand + */ String[] value() default {}; + @Deprecated boolean help() default false; String[] description() default {}; @@ -359,8 +348,11 @@ public abstract class AbstractSWCommand { boolean local() default false; } + // --- Implicit TypeMapper --- + @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) + @ApplicableTypes({String.class, int.class, Integer.class, boolean.class, Boolean.class}) protected @interface StaticValue { String[] value(); @@ -391,6 +383,8 @@ public abstract class AbstractSWCommand { boolean onlyUINIG() default false; } + // --- Implicit Validator --- + @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) protected @interface ErrorMessage { @@ -415,6 +409,31 @@ public abstract class AbstractSWCommand { */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) + @ApplicableTypes({String.class}) protected @interface Quotable { } + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.PARAMETER}) + @ApplicableTypes({int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class}) + protected @interface Min { + int intValue() default Integer.MIN_VALUE; + long longValue() default Long.MIN_VALUE; + float floatValue() default Float.MIN_VALUE; + double doubleValue() default Double.MIN_VALUE; + + boolean inclusive() default true; + } + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.PARAMETER}) + @ApplicableTypes({int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class}) + protected @interface Max { + int intValue() default Integer.MAX_VALUE; + long longValue() default Long.MAX_VALUE; + float floatValue() default Float.MAX_VALUE; + double doubleValue() default Double.MAX_VALUE; + + boolean inclusive() default true; + } } diff --git a/src/de/steamwar/command/AbstractValidator.java b/src/de/steamwar/command/AbstractValidator.java index aab4876..7dc0b72 100644 --- a/src/de/steamwar/command/AbstractValidator.java +++ b/src/de/steamwar/command/AbstractValidator.java @@ -38,10 +38,12 @@ public interface AbstractValidator { */ boolean validate(K sender, T value, MessageSender messageSender); + @Deprecated default Validator validate(C value, MessageSender messageSender) { return new Validator<>(value, messageSender); } + @Deprecated @RequiredArgsConstructor class Validator { private final C value; diff --git a/src/de/steamwar/command/CommandNoHelpException.java b/src/de/steamwar/command/ApplicableTypes.java similarity index 69% rename from src/de/steamwar/command/CommandNoHelpException.java rename to src/de/steamwar/command/ApplicableTypes.java index e3d476a..2385b43 100644 --- a/src/de/steamwar/command/CommandNoHelpException.java +++ b/src/de/steamwar/command/ApplicableTypes.java @@ -1,7 +1,7 @@ /* * This file is a part of the SteamWar software. * - * Copyright (C) 2020 SteamWar.de-Serverteam + * 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 @@ -19,7 +19,13 @@ package de.steamwar.command; -class CommandNoHelpException extends RuntimeException { - - CommandNoHelpException() {} +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.ANNOTATION_TYPE) +@interface ApplicableTypes { + Class[] value(); } diff --git a/src/de/steamwar/command/CommandPart.java b/src/de/steamwar/command/CommandPart.java index a81c646..bb39119 100644 --- a/src/de/steamwar/command/CommandPart.java +++ b/src/de/steamwar/command/CommandPart.java @@ -24,6 +24,7 @@ import lombok.Setter; import java.lang.reflect.Array; import java.lang.reflect.Parameter; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; @@ -41,7 +42,7 @@ class CommandPart { private AbstractSWCommand command; private AbstractTypeMapper typeMapper; - private AbstractValidator validator; + private List> validators = new ArrayList<>(); private Class varArgType; private String optional; @@ -53,19 +54,12 @@ class CommandPart { @Setter private boolean onlyUseIfNoneIsGiven = false; - @Setter - private boolean allowNullValues = false; - - @Setter - private boolean quotable = false; - private Parameter parameter; private int parameterIndex; - public CommandPart(AbstractSWCommand command, AbstractTypeMapper typeMapper, AbstractValidator validator, Class varArgType, String optional, Parameter parameter, int parameterIndex) { + public CommandPart(AbstractSWCommand command, AbstractTypeMapper typeMapper, Class varArgType, String optional, Parameter parameter, int parameterIndex) { this.command = command; this.typeMapper = typeMapper; - this.validator = validator; this.varArgType = varArgType; this.optional = optional; this.parameter = parameter; @@ -74,6 +68,11 @@ class CommandPart { validatePart(); } + void addValidator(AbstractValidator validator) { + if (validator == null) return; + validators.add(validator); + } + public void setNext(CommandPart next) { if (varArgType != null) { throw new IllegalArgumentException("There can't be a next part if this is a vararg part! In method " + parameter.getDeclaringExecutable() + " with parameter " + parameterIndex); @@ -97,10 +96,12 @@ class CommandPart { } Array.set(array, i - startIndex, validArgument.value); } - if (validator != null && !validator.validate(sender, array, (s, objects) -> { - errors.accept(() -> command.sendMessage(sender, s, objects)); - })) { - throw new CommandParseException(); + for (AbstractValidator validator : validators) { + if (!validator.validate(sender, array, (s, objects) -> { + errors.accept(() -> command.sendMessage(sender, s, objects)); + })) { + throw new CommandParseException(); + } } current.add(array); return; @@ -136,7 +137,8 @@ class CommandPart { public void generateTabComplete(List current, T sender, String[] args, int startIndex) { if (varArgType != null) { for (int i = startIndex; i < args.length - 1; i++) { - CheckArgumentResult validArgument = checkArgument((ignore) -> {}, sender, args, i); + CheckArgumentResult validArgument = checkArgument((ignore) -> { + }, sender, args, i); if (!validArgument.success) { return; } @@ -149,7 +151,8 @@ class CommandPart { } if (args.length - 1 > startIndex) { - CheckArgumentResult checkArgumentResult = checkArgument((ignore) -> {}, sender, args, startIndex); + CheckArgumentResult checkArgumentResult = checkArgument((ignore) -> { + }, sender, args, startIndex); if (checkArgumentResult.success && next != null) { next.generateTabComplete(current, sender, args, startIndex + 1); return; @@ -186,10 +189,7 @@ class CommandPart { } catch (Exception e) { return new CheckArgumentResult(false, null); } - if (value instanceof String && !quotable && ((String) value).contains(" ")) { - return new CheckArgumentResult(false, null); - } - if (validator != null && errors != null) { + for (AbstractValidator validator : validators) { try { if (!validator.validate(sender, value, (s, objects) -> { errors.accept(() -> { @@ -202,6 +202,6 @@ class CommandPart { throw CommandFrameworkException.commandPartExceptions("validating", e, args[index], (varArgType != null ? varArgType : parameter.getType()), parameter.getDeclaringExecutable(), parameterIndex); } } - return new CheckArgumentResult(allowNullValues || value != null, value); + return new CheckArgumentResult(true, value); } } diff --git a/src/de/steamwar/command/SWCommandUtils.java b/src/de/steamwar/command/SWCommandUtils.java index 871439a..203bdd8 100644 --- a/src/de/steamwar/command/SWCommandUtils.java +++ b/src/de/steamwar/command/SWCommandUtils.java @@ -82,44 +82,6 @@ public class SWCommandUtils { MAPPER_FUNCTIONS.put(alternativeClazz.getTypeName(), mapper); } - static CommandPart generateCommandPart(AbstractSWCommand command, boolean help, String[] subCommand, Parameter[] parameters, Map> localTypeMapper, Map> localValidator) { - CommandPart first = null; - CommandPart current = null; - for (String s : subCommand) { - CommandPart commandPart = new CommandPart(command, createMapper(s), null, null, null, null, -1); - commandPart.setIgnoreAsArgument(true); - if (current != null) { - current.setNext(commandPart); - } - current = commandPart; - if (first == null) { - first = current; - } - } - for (int i = 1; i < parameters.length; i++) { - Parameter parameter = parameters[i]; - AbstractTypeMapper typeMapper = getTypeMapper(parameter, localTypeMapper); - AbstractValidator validator = (AbstractValidator) getValidator(parameter, localValidator); - Class varArgType = parameter.isVarArgs() ? parameter.getType().getComponentType() : null; - AbstractSWCommand.OptionalValue optionalValue = parameter.getAnnotation(AbstractSWCommand.OptionalValue.class); - AbstractSWCommand.AllowNull allowNull = parameter.getAnnotation(AbstractSWCommand.AllowNull.class); - AbstractSWCommand.Quotable quotable = parameter.getAnnotation(AbstractSWCommand.Quotable.class); - - CommandPart commandPart = new CommandPart<>(command, typeMapper, validator, varArgType, optionalValue != null ? optionalValue.value() : null, parameter, i); - commandPart.setOnlyUseIfNoneIsGiven(optionalValue != null && optionalValue.onlyUINIG()); - commandPart.setAllowNullValues(allowNull != null); - commandPart.setQuotable(quotable != null); - if (current != null) { - current.setNext(commandPart); - } - current = commandPart; - if (first == null) { - first = current; - } - } - return first; - } - public static AbstractTypeMapper getTypeMapper(Parameter parameter, Map> localTypeMapper) { Class clazz = parameter.getType(); if (parameter.isVarArgs()) { diff --git a/src/de/steamwar/command/SubCommand.java b/src/de/steamwar/command/SubCommand.java index 9eb6e4f..bf11674 100644 --- a/src/de/steamwar/command/SubCommand.java +++ b/src/de/steamwar/command/SubCommand.java @@ -43,7 +43,7 @@ public class SubCommand { private CommandPart commandPart; - SubCommand(AbstractSWCommand abstractSWCommand, Method method, String[] subCommand, Map> localTypeMapper, Map> localValidator, boolean help, String[] description, boolean noTabComplete) { + SubCommand(AbstractSWCommand abstractSWCommand, Method method, String[] subCommand, Map> localTypeMapper, Map> localValidator, String[] description, boolean noTabComplete) { this.abstractSWCommand = abstractSWCommand; this.method = method; try { @@ -60,7 +60,7 @@ public class SubCommand { validator = (AbstractValidator) SWCommandUtils.getValidator(parameters[0], localValidator); - commandPart = SWCommandUtils.generateCommandPart(abstractSWCommand, help, subCommand, parameters, localTypeMapper, localValidator); + commandPart = generateCommandPart(abstractSWCommand, subCommand, parameters, localTypeMapper, localValidator); senderPredicate = t -> parameters[0].getType().isAssignableFrom(t.getClass()); senderFunction = t -> parameters[0].getType().cast(t); @@ -97,7 +97,7 @@ public class SubCommand { objects.add(0, senderFunction.apply(sender)); method.invoke(abstractSWCommand, objects.toArray()); } - } catch (CommandNoHelpException | CommandFrameworkException e) { + } catch (CommandFrameworkException e) { throw e; } catch (CommandParseException e) { return false; @@ -124,4 +124,110 @@ public class SubCommand { commandPart.generateTabComplete(list, sender, args, 0); return list; } + + private static CommandPart generateCommandPart(AbstractSWCommand command, String[] subCommand, Parameter[] parameters, Map> localTypeMapper, Map> localValidator) { + CommandPart first = null; + CommandPart current = null; + for (String s : subCommand) { + CommandPart commandPart = new CommandPart(command, SWCommandUtils.createMapper(s), null, null, null, -1); + commandPart.addValidator(NULL_FILTER); + commandPart.setIgnoreAsArgument(true); + if (current != null) { + current.setNext(commandPart); + } + current = commandPart; + if (first == null) { + first = current; + } + } + for (int i = 1; i < parameters.length; i++) { + Parameter parameter = parameters[i]; + AbstractTypeMapper typeMapper = SWCommandUtils.getTypeMapper(parameter, localTypeMapper); + AbstractValidator validator = (AbstractValidator) SWCommandUtils.getValidator(parameter, localValidator); + Class varArgType = parameter.isVarArgs() ? parameter.getType().getComponentType() : null; + AbstractSWCommand.OptionalValue optionalValue = parameter.getAnnotation(AbstractSWCommand.OptionalValue.class); + AbstractSWCommand.Min min = parameter.getAnnotation(AbstractSWCommand.Min.class); + AbstractSWCommand.Max max = parameter.getAnnotation(AbstractSWCommand.Max.class); + + CommandPart commandPart = new CommandPart<>(command, typeMapper, varArgType, optionalValue != null ? optionalValue.value() : null, parameter, i); + commandPart.setOnlyUseIfNoneIsGiven(optionalValue != null && optionalValue.onlyUINIG()); + if (parameter.getAnnotation(AbstractSWCommand.Quotable.class) == null) { + commandPart.addValidator((AbstractValidator) STRING_SPACE_FILTER); + } + commandPart.addValidator(validator); + if (min != null) { + commandPart.addValidator((AbstractValidator) createMinValidator(varArgType != null ? varArgType : parameter.getType(), min)); + } + if (max != null) { + commandPart.addValidator((AbstractValidator) createMaxValidator(varArgType != null ? varArgType : parameter.getType(), max)); + } + if (parameter.getAnnotation(AbstractSWCommand.AllowNull.class) == null) { + commandPart.addValidator((AbstractValidator) NULL_FILTER); + } + if (current != null) { + current.setNext(commandPart); + } + current = commandPart; + if (first == null) { + first = current; + } + } + return first; + } + + private static final AbstractValidator NULL_FILTER = (sender, value, messageSender) -> value != null; + + private static final AbstractValidator STRING_SPACE_FILTER = (sender, value, messageSender) -> { + if (!(value instanceof String)) return true; + String s = (String) value; + return !s.contains(" "); + }; + + private static AbstractValidator createMinValidator(Class clazz, AbstractSWCommand.Min min) { + Function comparator; + if (clazz == int.class || clazz == Integer.class) { + int minValue = min.intValue(); + comparator = number -> Integer.compare(number.intValue(), minValue); + } else if (clazz == long.class || clazz == Long.class) { + long minValue = min.longValue(); + comparator = number -> Long.compare(number.longValue(), minValue); + } else if (clazz == float.class || clazz == Float.class) { + float minValue = min.floatValue(); + comparator = number -> Float.compare(number.floatValue(), minValue); + } else if (clazz == double.class || clazz == Double.class) { + double minValue = min.doubleValue(); + comparator = number -> Double.compare(number.doubleValue(), minValue); + } else { + throw new IllegalArgumentException("Min annotation is not supported for " + clazz); + } + if (min.inclusive()) { + return (sender, value, messageSender) -> comparator.apply((Number) value).intValue() >= 0; + } else { + return (sender, value, messageSender) -> comparator.apply((Number) value).intValue() > 0; + } + } + + private static AbstractValidator createMaxValidator(Class clazz, AbstractSWCommand.Max max) { + Function comparator; + if (clazz == int.class || clazz == Integer.class) { + int minValue = max.intValue(); + comparator = number -> Integer.compare(number.intValue(), minValue); + } else if (clazz == long.class || clazz == Long.class) { + long minValue = max.longValue(); + comparator = number -> Long.compare(number.longValue(), minValue); + } else if (clazz == float.class || clazz == Float.class) { + float minValue = max.floatValue(); + comparator = number -> Float.compare(number.floatValue(), minValue); + } else if (clazz == double.class || clazz == Double.class) { + double minValue = max.doubleValue(); + comparator = number -> Double.compare(number.doubleValue(), minValue); + } else { + throw new IllegalArgumentException("Max annotation is not supported for " + clazz); + } + if (max.inclusive()) { + return (sender, value, messageSender) -> comparator.apply((Number) value).intValue() <= 0; + } else { + return (sender, value, messageSender) -> comparator.apply((Number) value).intValue() < 0; + } + } } diff --git a/testsrc/de/steamwar/command/BetterExceptionCommand.java b/testsrc/de/steamwar/command/BetterExceptionCommand.java index cd38179..d014186 100644 --- a/testsrc/de/steamwar/command/BetterExceptionCommand.java +++ b/testsrc/de/steamwar/command/BetterExceptionCommand.java @@ -45,6 +45,7 @@ public class BetterExceptionCommand extends TestSWCommand { @Override public boolean validate(String sender, String value, MessageSender messageSender) { + System.out.println("Validate: " + value); throw new SecurityException(); } diff --git a/testsrc/de/steamwar/command/NumberValidatorCommand.java b/testsrc/de/steamwar/command/NumberValidatorCommand.java new file mode 100644 index 0000000..9ba53a6 --- /dev/null +++ b/testsrc/de/steamwar/command/NumberValidatorCommand.java @@ -0,0 +1,35 @@ +/* + * 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.command; + +import de.steamwar.command.dto.ExecutionIdentifier; +import de.steamwar.command.dto.TestSWCommand; + +public class NumberValidatorCommand extends TestSWCommand { + + public NumberValidatorCommand() { + super("numberValidator"); + } + + @Register + public void test(String sender, @Min(intValue = 0) @Max(intValue = 10) int i) { + throw new ExecutionIdentifier("RunNumberValidator with int"); + } +} diff --git a/testsrc/de/steamwar/command/NumberValidatorCommandTest.java b/testsrc/de/steamwar/command/NumberValidatorCommandTest.java new file mode 100644 index 0000000..c45b4a8 --- /dev/null +++ b/testsrc/de/steamwar/command/NumberValidatorCommandTest.java @@ -0,0 +1,51 @@ +/* + * 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.command; + +import de.steamwar.command.dto.ExecutionIdentifier; +import org.junit.Test; + +import static de.steamwar.AssertionUtils.assertCMDFramework; + +public class NumberValidatorCommandTest { + + @Test + public void testMinValue() { + NumberValidatorCommand command = new NumberValidatorCommand(); + command.execute("sender", "", new String[]{"-1"}); + } + + @Test + public void testMaxValue() { + NumberValidatorCommand command = new NumberValidatorCommand(); + command.execute("sender", "", new String[]{"11"}); + } + + @Test + public void testValidValue() { + try { + NumberValidatorCommand command = new NumberValidatorCommand(); + command.execute("sender", "", new String[]{"2"}); + assert false; + } catch (Exception e) { + assertCMDFramework(e, ExecutionIdentifier.class, "RunNumberValidator with int"); + } + } +} diff --git a/testsrc/de/steamwar/command/SimpleCommand.java b/testsrc/de/steamwar/command/SimpleCommand.java index 95aa43d..7388633 100644 --- a/testsrc/de/steamwar/command/SimpleCommand.java +++ b/testsrc/de/steamwar/command/SimpleCommand.java @@ -19,7 +19,6 @@ package de.steamwar.command; -import de.steamwar.command.AbstractSWCommand.Register; import de.steamwar.command.dto.ExecutionIdentifier; import de.steamwar.command.dto.TestSWCommand; @@ -29,7 +28,7 @@ public class SimpleCommand extends TestSWCommand { super("simple"); } - @Register(value = "a", help = true) + @Register(value = "a", noTabComplete = true) public void test(String s, String... varargs) { throw new ExecutionIdentifier("RunSimple with Varargs"); } diff --git a/testsrc/de/steamwar/command/dto/TestSWCommand.java b/testsrc/de/steamwar/command/dto/TestSWCommand.java index 5fd3b3f..e0ac34b 100644 --- a/testsrc/de/steamwar/command/dto/TestSWCommand.java +++ b/testsrc/de/steamwar/command/dto/TestSWCommand.java @@ -51,9 +51,11 @@ public class TestSWCommand extends AbstractSWCommand { @Override protected void commandSystemError(String sender, CommandFrameworkException e) { + System.out.println("CommandSystemError: " + e.getMessage()); } @Override protected void commandSystemWarning(Supplier message) { + System.out.println("CommandSystemWarning: " + message.get()); } } From f2c0a2a16b98af98c90c6389dec656f137baf5d4 Mon Sep 17 00:00:00 2001 From: yoyosource Date: Sun, 18 Dec 2022 18:14:44 +0100 Subject: [PATCH 31/85] Add MethodMetaData and ParameterMetaData for better type safety --- .../steamwar/command/AbstractSWCommand.java | 121 ++++++++++-------- src/de/steamwar/command/MethodMetaData.java | 33 +++++ ...cableTypes.java => ParameterMetaData.java} | 4 +- 3 files changed, 103 insertions(+), 55 deletions(-) create mode 100644 src/de/steamwar/command/MethodMetaData.java rename src/de/steamwar/command/{ApplicableTypes.java => ParameterMetaData.java} (94%) diff --git a/src/de/steamwar/command/AbstractSWCommand.java b/src/de/steamwar/command/AbstractSWCommand.java index 9ff2e28..30af847 100644 --- a/src/de/steamwar/command/AbstractSWCommand.java +++ b/src/de/steamwar/command/AbstractSWCommand.java @@ -25,6 +25,7 @@ import java.lang.reflect.Parameter; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; +import java.util.function.Function; import java.util.function.IntPredicate; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -133,49 +134,32 @@ public abstract class AbstractSWCommand { private synchronized void initialize() { if (initialized) return; - List methods = methods(); + List methods = methods().stream() + .filter(this::validateMethod) + .collect(Collectors.toList()); for (Method method : methods) { Cached cached = method.getAnnotation(Cached.class); - addMapper(Mapper.class, method, i -> i == 0, false, AbstractTypeMapper.class, (anno, typeMapper) -> { + addMapper(Mapper.class, method, (anno, typeMapper) -> { TabCompletionCache.add(typeMapper, cached); - if (anno.local()) { - localTypeMapper.putIfAbsent(anno.value(), (AbstractTypeMapper) typeMapper); - } else { - SWCommandUtils.getMAPPER_FUNCTIONS().putIfAbsent(anno.value(), typeMapper); - } + (anno.local() ? ((Map) localTypeMapper) : SWCommandUtils.getMAPPER_FUNCTIONS()).put(anno.value(), typeMapper); }); - addMapper(ClassMapper.class, method, i -> i == 0, false, AbstractTypeMapper.class, (anno, typeMapper) -> { + addMapper(ClassMapper.class, method, (anno, typeMapper) -> { TabCompletionCache.add(typeMapper, cached); - if (anno.local()) { - localTypeMapper.putIfAbsent(anno.value().getTypeName(), (AbstractTypeMapper) typeMapper); - } else { - SWCommandUtils.getMAPPER_FUNCTIONS().putIfAbsent(anno.value().getTypeName(), typeMapper); - } + (anno.local() ? ((Map) localTypeMapper) : SWCommandUtils.getMAPPER_FUNCTIONS()).put(anno.value().getName(), typeMapper); }); - addValidator(Validator.class, method, i -> i == 0, false, AbstractValidator.class, (anno, validator) -> { - if (anno.local()) { - localValidators.putIfAbsent(anno.value(), (AbstractValidator) validator); - } else { - SWCommandUtils.getVALIDATOR_FUNCTIONS().putIfAbsent(anno.value(), validator); - } + addValidator(Validator.class, method, (anno, validator) -> { + (anno.local() ? ((Map) localValidators) : SWCommandUtils.getVALIDATOR_FUNCTIONS()).put(anno.value(), validator); }); - addValidator(ClassValidator.class, method, i -> i == 0, false, AbstractValidator.class, (anno, validator) -> { - if (anno.local()) { - localValidators.putIfAbsent(anno.value().getTypeName(), (AbstractValidator) validator); - } else { - SWCommandUtils.getVALIDATOR_FUNCTIONS().putIfAbsent(anno.value().getTypeName(), validator); - } + addValidator(ClassValidator.class, method, (anno, validator) -> { + (anno.local() ? ((Map) localValidators) : SWCommandUtils.getVALIDATOR_FUNCTIONS()).put(anno.value().getName(), validator); }); } for (Method method : methods) { - add(Register.class, method, i -> i > 0, true, null, (anno, parameters) -> { + add(Register.class, method, true, (anno, parameters) -> { for (int i = 1; i < parameters.length; i++) { Parameter parameter = parameters[i]; Class clazz = parameter.getType(); - if (parameter.isVarArgs() && i == parameters.length - 1) { - clazz = parameter.getType().getComponentType(); - } - checkAnnotationApplicability(parameter, clazz); + if (parameter.isVarArgs()) clazz = clazz.getComponentType(); Mapper mapper = parameter.getAnnotation(Mapper.class); if (clazz.isEnum() && mapper == null && !SWCommandUtils.getMAPPER_FUNCTIONS().containsKey(clazz.getTypeName())) { continue; @@ -201,12 +185,42 @@ public abstract class AbstractSWCommand { initialized = true; } - private void checkAnnotationApplicability(Parameter parameter, Class clazz) { - Annotation[] annotations = parameter.getAnnotations(); + private boolean validateMethod(Method method) { + if (!checkType(method.getAnnotations(), method.getReturnType(), annotation -> { + MethodMetaData methodMetaData = annotation.annotationType().getAnnotation(MethodMetaData.class); + if (methodMetaData == null) return null; + if (method.getParameterCount() > methodMetaData.maxParameterCount()) { + return new Class[0]; + } + if (method.getParameterCount() < methodMetaData.minParameterCount()) { + return new Class[0]; + } + return methodMetaData.possibleReturnTypes(); + }, "The method '" + method + "'")) { + return false; + } + boolean valid = true; + for (Parameter parameter : method.getParameters()) { + Class type = parameter.getType(); + if (parameter.isVarArgs()) { + type = type.getComponentType(); + } + if (!checkType(parameter.getAnnotations(), type, annotation -> { + ParameterMetaData parameterMetaData = annotation.annotationType().getAnnotation(ParameterMetaData.class); + if (parameterMetaData == null) return null; + return parameterMetaData.possibleTypes(); + }, "The parameter '" + parameter + "'")) { + valid = false; + } + } + return valid; + } + + private boolean checkType(Annotation[] annotations, Class clazz, Function[]> toApplicableTypes, String warning) { + boolean valid = true; for (Annotation annotation : annotations) { - ApplicableTypes applicableTypes = annotation.annotationType().getAnnotation(ApplicableTypes.class); - if (applicableTypes == null) continue; - Class[] types = applicableTypes.value(); + Class[] types = toApplicableTypes.apply(annotation); + if (types == null) continue; boolean applicable = false; for (Class type : types) { if (type.isAssignableFrom(clazz)) { @@ -215,33 +229,27 @@ public abstract class AbstractSWCommand { } } if (!applicable) { - commandSystemWarning(() -> "The parameter '" + parameter.toString() + "' is using an unsupported annotation of type '" + annotation.annotationType().getName() + "'"); + commandSystemWarning(() -> warning + " is using an unsupported annotation of type '" + annotation.annotationType().getName() + "'"); + valid = false; } } + return valid; } - private void add(Class annotation, Method method, IntPredicate parameterTester, boolean firstParameter, Class returnType, BiConsumer consumer) { + private void add(Class annotation, Method method, boolean firstParameter, BiConsumer consumer) { T[] anno = SWCommandUtils.getAnnotation(method, annotation); if (anno == null || anno.length == 0) return; Parameter[] parameters = method.getParameters(); - if (!parameterTester.test(parameters.length)) { - commandSystemWarning(() -> "The method '" + method.toString() + "' is lacking parameters or has too many"); - return; - } if (firstParameter && !clazz.isAssignableFrom(parameters[0].getType())) { commandSystemWarning(() -> "The method '" + method.toString() + "' is lacking the first parameter of type '" + clazz.getTypeName() + "'"); return; } - if (returnType != null && !returnType.isAssignableFrom(method.getReturnType())) { - commandSystemWarning(() -> "The method '" + method.toString() + "' is lacking the desired return type '" + returnType.getTypeName() + "'"); - return; - } Arrays.stream(anno).forEach(t -> consumer.accept(t, parameters)); } - private void addMapper(Class annotation, Method method, IntPredicate parameterTester, boolean firstParameter, Class returnType, BiConsumer> consumer) { - add(annotation, method, parameterTester, firstParameter, returnType, (anno, parameters) -> { + private void addMapper(Class annotation, Method method, BiConsumer> consumer) { + add(annotation, method, false, (anno, parameters) -> { try { method.setAccessible(true); consumer.accept(anno, (AbstractTypeMapper) method.invoke(this)); @@ -251,8 +259,8 @@ public abstract class AbstractSWCommand { }); } - private void addValidator(Class annotation, Method method, IntPredicate parameterTester, boolean firstParameter, Class returnType, BiConsumer> consumer) { - add(annotation, method, parameterTester, firstParameter, returnType, (anno, parameters) -> { + private void addValidator(Class annotation, Method method, BiConsumer> consumer) { + add(annotation, method, false, (anno, parameters) -> { try { method.setAccessible(true); consumer.accept(anno, (AbstractValidator) method.invoke(this)); @@ -287,6 +295,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) @Repeatable(Register.Registeres.class) + @MethodMetaData(possibleReturnTypes = void.class, minParameterCount = 1) protected @interface Register { /** @@ -303,6 +312,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) + @MethodMetaData(possibleReturnTypes = void.class, minParameterCount = 1) @interface Registeres { Register[] value(); } @@ -310,6 +320,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER, ElementType.METHOD}) + @MethodMetaData(possibleReturnTypes = AbstractTypeMapper.class, minParameterCount = 0, maxParameterCount = 0) protected @interface Mapper { String value(); @@ -318,6 +329,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) + @MethodMetaData(possibleReturnTypes = AbstractTypeMapper.class, minParameterCount = 0, maxParameterCount = 0) protected @interface ClassMapper { Class value(); @@ -326,6 +338,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) + @MethodMetaData(possibleReturnTypes = AbstractTypeMapper.class, minParameterCount = 0, maxParameterCount = 0) protected @interface Cached { long cacheDuration() default 5; TimeUnit timeUnit() default TimeUnit.SECONDS; @@ -334,6 +347,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER, ElementType.METHOD}) + @MethodMetaData(possibleReturnTypes = AbstractValidator.class, minParameterCount = 0, maxParameterCount = 0) protected @interface Validator { String value() default ""; @@ -342,6 +356,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) + @MethodMetaData(possibleReturnTypes = AbstractValidator.class, minParameterCount = 0, maxParameterCount = 0) protected @interface ClassValidator { Class value(); @@ -352,7 +367,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) - @ApplicableTypes({String.class, int.class, Integer.class, boolean.class, Boolean.class}) + @ParameterMetaData(possibleTypes = {String.class, int.class, Integer.class, long.class, Long.class, boolean.class, Boolean.class}) protected @interface StaticValue { String[] value(); @@ -409,13 +424,13 @@ public abstract class AbstractSWCommand { */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) - @ApplicableTypes({String.class}) + @ParameterMetaData(possibleTypes = {String.class}) protected @interface Quotable { } @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) - @ApplicableTypes({int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class}) + @ParameterMetaData(possibleTypes = {int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class}) protected @interface Min { int intValue() default Integer.MIN_VALUE; long longValue() default Long.MIN_VALUE; @@ -427,7 +442,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) - @ApplicableTypes({int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class}) + @ParameterMetaData(possibleTypes = {int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class}) protected @interface Max { int intValue() default Integer.MAX_VALUE; long longValue() default Long.MAX_VALUE; diff --git a/src/de/steamwar/command/MethodMetaData.java b/src/de/steamwar/command/MethodMetaData.java new file mode 100644 index 0000000..4975ca8 --- /dev/null +++ b/src/de/steamwar/command/MethodMetaData.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.command; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.ANNOTATION_TYPE) +@interface MethodMetaData { + Class[] possibleReturnTypes(); + int minParameterCount() default 0; + int maxParameterCount() default Integer.MAX_VALUE; +} diff --git a/src/de/steamwar/command/ApplicableTypes.java b/src/de/steamwar/command/ParameterMetaData.java similarity index 94% rename from src/de/steamwar/command/ApplicableTypes.java rename to src/de/steamwar/command/ParameterMetaData.java index 2385b43..c7c0467 100644 --- a/src/de/steamwar/command/ApplicableTypes.java +++ b/src/de/steamwar/command/ParameterMetaData.java @@ -26,6 +26,6 @@ import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) -@interface ApplicableTypes { - Class[] value(); +@interface ParameterMetaData { + Class[] possibleTypes(); } From 19e4949048ecaf7bb40159ca600ff6b3ab626eb7 Mon Sep 17 00:00:00 2001 From: yoyosource Date: Sun, 18 Dec 2022 18:37:10 +0100 Subject: [PATCH 32/85] Add MetaData to bundle metadata annotations --- .../steamwar/command/AbstractSWCommand.java | 80 +++++++------------ .../{MethodMetaData.java => MetaData.java} | 21 +++-- .../steamwar/command/ParameterMetaData.java | 31 ------- 3 files changed, 42 insertions(+), 90 deletions(-) rename src/de/steamwar/command/{MethodMetaData.java => MetaData.java} (70%) delete mode 100644 src/de/steamwar/command/ParameterMetaData.java diff --git a/src/de/steamwar/command/AbstractSWCommand.java b/src/de/steamwar/command/AbstractSWCommand.java index 30af847..8ab7cbd 100644 --- a/src/de/steamwar/command/AbstractSWCommand.java +++ b/src/de/steamwar/command/AbstractSWCommand.java @@ -26,7 +26,6 @@ import java.util.*; import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; import java.util.function.Function; -import java.util.function.IntPredicate; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -139,18 +138,18 @@ public abstract class AbstractSWCommand { .collect(Collectors.toList()); for (Method method : methods) { Cached cached = method.getAnnotation(Cached.class); - addMapper(Mapper.class, method, (anno, typeMapper) -> { + this.>add(Mapper.class, method, (anno, typeMapper) -> { TabCompletionCache.add(typeMapper, cached); (anno.local() ? ((Map) localTypeMapper) : SWCommandUtils.getMAPPER_FUNCTIONS()).put(anno.value(), typeMapper); }); - addMapper(ClassMapper.class, method, (anno, typeMapper) -> { + this.>add(ClassMapper.class, method, (anno, typeMapper) -> { TabCompletionCache.add(typeMapper, cached); (anno.local() ? ((Map) localTypeMapper) : SWCommandUtils.getMAPPER_FUNCTIONS()).put(anno.value().getName(), typeMapper); }); - addValidator(Validator.class, method, (anno, validator) -> { + this.>add(Validator.class, method, (anno, validator) -> { (anno.local() ? ((Map) localValidators) : SWCommandUtils.getVALIDATOR_FUNCTIONS()).put(anno.value(), validator); }); - addValidator(ClassValidator.class, method, (anno, validator) -> { + this.>add(ClassValidator.class, method, (anno, validator) -> { (anno.local() ? ((Map) localValidators) : SWCommandUtils.getVALIDATOR_FUNCTIONS()).put(anno.value().getName(), validator); }); } @@ -176,42 +175,28 @@ public abstract class AbstractSWCommand { this.commandList.sort((o1, o2) -> { int compare = Integer.compare(-o1.subCommand.length, -o2.subCommand.length); - if (compare != 0) { - return compare; - } else { - return Integer.compare(o1.comparableValue, o2.comparableValue); - } + if (compare == 0) return Integer.compare(o1.comparableValue, o2.comparableValue); + return compare; }); initialized = true; } private boolean validateMethod(Method method) { if (!checkType(method.getAnnotations(), method.getReturnType(), annotation -> { - MethodMetaData methodMetaData = annotation.annotationType().getAnnotation(MethodMetaData.class); + MetaData.Method methodMetaData = annotation.annotationType().getAnnotation(MetaData.Method.class); if (methodMetaData == null) return null; - if (method.getParameterCount() > methodMetaData.maxParameterCount()) { - return new Class[0]; - } - if (method.getParameterCount() < methodMetaData.minParameterCount()) { - return new Class[0]; - } - return methodMetaData.possibleReturnTypes(); - }, "The method '" + method + "'")) { - return false; - } + if (method.getParameterCount() > methodMetaData.maxParameterCount() || method.getParameterCount() < methodMetaData.minParameterCount()) return new Class[0]; + return methodMetaData.value(); + }, "The method '" + method + "'")) return false; boolean valid = true; for (Parameter parameter : method.getParameters()) { Class type = parameter.getType(); - if (parameter.isVarArgs()) { - type = type.getComponentType(); - } + if (parameter.isVarArgs()) type = type.getComponentType(); if (!checkType(parameter.getAnnotations(), type, annotation -> { - ParameterMetaData parameterMetaData = annotation.annotationType().getAnnotation(ParameterMetaData.class); + MetaData.Parameter parameterMetaData = annotation.annotationType().getAnnotation(MetaData.Parameter.class); if (parameterMetaData == null) return null; - return parameterMetaData.possibleTypes(); - }, "The parameter '" + parameter + "'")) { - valid = false; - } + return parameterMetaData.value(); + }, "The parameter '" + parameter + "'")) valid = false; } return valid; } @@ -248,22 +233,11 @@ public abstract class AbstractSWCommand { Arrays.stream(anno).forEach(t -> consumer.accept(t, parameters)); } - private void addMapper(Class annotation, Method method, BiConsumer> consumer) { + private void add(Class annotation, Method method, BiConsumer consumer) { add(annotation, method, false, (anno, parameters) -> { try { method.setAccessible(true); - consumer.accept(anno, (AbstractTypeMapper) method.invoke(this)); - } catch (Exception e) { - throw new SecurityException(e.getMessage(), e); - } - }); - } - - private void addValidator(Class annotation, Method method, BiConsumer> consumer) { - add(annotation, method, false, (anno, parameters) -> { - try { - method.setAccessible(true); - consumer.accept(anno, (AbstractValidator) method.invoke(this)); + consumer.accept(anno, (K) method.invoke(this)); } catch (Exception e) { throw new SecurityException(e.getMessage(), e); } @@ -295,7 +269,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) @Repeatable(Register.Registeres.class) - @MethodMetaData(possibleReturnTypes = void.class, minParameterCount = 1) + @MetaData.Method(value = void.class, minParameterCount = 1) protected @interface Register { /** @@ -312,7 +286,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) - @MethodMetaData(possibleReturnTypes = void.class, minParameterCount = 1) + @MetaData.Method(value = void.class, minParameterCount = 1) @interface Registeres { Register[] value(); } @@ -320,7 +294,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER, ElementType.METHOD}) - @MethodMetaData(possibleReturnTypes = AbstractTypeMapper.class, minParameterCount = 0, maxParameterCount = 0) + @MetaData.Method(value = AbstractTypeMapper.class, maxParameterCount = 0) protected @interface Mapper { String value(); @@ -329,7 +303,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) - @MethodMetaData(possibleReturnTypes = AbstractTypeMapper.class, minParameterCount = 0, maxParameterCount = 0) + @MetaData.Method(value = AbstractTypeMapper.class, maxParameterCount = 0) protected @interface ClassMapper { Class value(); @@ -338,7 +312,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) - @MethodMetaData(possibleReturnTypes = AbstractTypeMapper.class, minParameterCount = 0, maxParameterCount = 0) + @MetaData.Method(value = AbstractTypeMapper.class, maxParameterCount = 0) protected @interface Cached { long cacheDuration() default 5; TimeUnit timeUnit() default TimeUnit.SECONDS; @@ -347,7 +321,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER, ElementType.METHOD}) - @MethodMetaData(possibleReturnTypes = AbstractValidator.class, minParameterCount = 0, maxParameterCount = 0) + @MetaData.Method(value = AbstractValidator.class, maxParameterCount = 0) protected @interface Validator { String value() default ""; @@ -356,7 +330,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) - @MethodMetaData(possibleReturnTypes = AbstractValidator.class, minParameterCount = 0, maxParameterCount = 0) + @MetaData.Method(value = AbstractValidator.class, maxParameterCount = 0) protected @interface ClassValidator { Class value(); @@ -367,7 +341,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) - @ParameterMetaData(possibleTypes = {String.class, int.class, Integer.class, long.class, Long.class, boolean.class, Boolean.class}) + @MetaData.Parameter({String.class, int.class, Integer.class, long.class, Long.class, boolean.class, Boolean.class}) protected @interface StaticValue { String[] value(); @@ -424,13 +398,13 @@ public abstract class AbstractSWCommand { */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) - @ParameterMetaData(possibleTypes = {String.class}) + @MetaData.Parameter({String.class}) protected @interface Quotable { } @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) - @ParameterMetaData(possibleTypes = {int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class}) + @MetaData.Parameter({int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class}) protected @interface Min { int intValue() default Integer.MIN_VALUE; long longValue() default Long.MIN_VALUE; @@ -442,7 +416,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) - @ParameterMetaData(possibleTypes = {int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class}) + @MetaData.Parameter({int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class}) protected @interface Max { int intValue() default Integer.MAX_VALUE; long longValue() default Long.MAX_VALUE; diff --git a/src/de/steamwar/command/MethodMetaData.java b/src/de/steamwar/command/MetaData.java similarity index 70% rename from src/de/steamwar/command/MethodMetaData.java rename to src/de/steamwar/command/MetaData.java index 4975ca8..5745fc4 100644 --- a/src/de/steamwar/command/MethodMetaData.java +++ b/src/de/steamwar/command/MetaData.java @@ -24,10 +24,19 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.ANNOTATION_TYPE) -@interface MethodMetaData { - Class[] possibleReturnTypes(); - int minParameterCount() default 0; - int maxParameterCount() default Integer.MAX_VALUE; +@interface MetaData { + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.ANNOTATION_TYPE) + @interface Method { + Class[] value(); + int minParameterCount() default 0; + int maxParameterCount() default Integer.MAX_VALUE; + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.ANNOTATION_TYPE) + @interface Parameter { + Class[] value(); + } } diff --git a/src/de/steamwar/command/ParameterMetaData.java b/src/de/steamwar/command/ParameterMetaData.java deleted file mode 100644 index c7c0467..0000000 --- a/src/de/steamwar/command/ParameterMetaData.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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.command; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.ANNOTATION_TYPE) -@interface ParameterMetaData { - Class[] possibleTypes(); -} From 2e9e4e2e49645207f11da6332e687165a98b7c7a Mon Sep 17 00:00:00 2001 From: yoyosource Date: Sun, 18 Dec 2022 21:36:46 +0100 Subject: [PATCH 33/85] Fix ErrorMessage annotation and ClassValidator never being present on Parameter --- src/de/steamwar/command/SWCommandUtils.java | 11 +++-------- src/de/steamwar/command/SubCommand.java | 1 + 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/de/steamwar/command/SWCommandUtils.java b/src/de/steamwar/command/SWCommandUtils.java index 203bdd8..2f16d7a 100644 --- a/src/de/steamwar/command/SWCommandUtils.java +++ b/src/de/steamwar/command/SWCommandUtils.java @@ -135,14 +135,6 @@ public class SWCommandUtils { public static AbstractValidator getValidator(Parameter parameter, Map> localValidator) { Class clazz = parameter.getType(); - AbstractSWCommand.ClassValidator classValidator = parameter.getAnnotation(AbstractSWCommand.ClassValidator.class); - if (classValidator != null) { - if (classValidator.value() != null) { - return getValidator(classValidator.value().getTypeName(), localValidator); - } - return getValidator(clazz.getTypeName(), localValidator); - } - AbstractSWCommand.Validator validator = parameter.getAnnotation(AbstractSWCommand.Validator.class); if (validator != null) { if (validator.value() != null && !validator.value().isEmpty()) { @@ -150,7 +142,10 @@ public class SWCommandUtils { } return getValidator(clazz.getTypeName(), localValidator); } + return null; + } + public static AbstractValidator getErrorMessage(Parameter parameter) { AbstractSWCommand.ErrorMessage errorMessage = parameter.getAnnotation(AbstractSWCommand.ErrorMessage.class); if (errorMessage != null) { return (AbstractValidator) (sender, value, messageSender) -> { diff --git a/src/de/steamwar/command/SubCommand.java b/src/de/steamwar/command/SubCommand.java index bf11674..0e75d7b 100644 --- a/src/de/steamwar/command/SubCommand.java +++ b/src/de/steamwar/command/SubCommand.java @@ -155,6 +155,7 @@ public class SubCommand { commandPart.addValidator((AbstractValidator) STRING_SPACE_FILTER); } commandPart.addValidator(validator); + commandPart.addValidator((AbstractValidator) SWCommandUtils.getErrorMessage(parameter)); if (min != null) { commandPart.addValidator((AbstractValidator) createMinValidator(varArgType != null ? varArgType : parameter.getType(), min)); } From 518207f343ae8fa054eaa6efe25bbd59a8d23b79 Mon Sep 17 00:00:00 2001 From: yoyosource Date: Sun, 18 Dec 2022 21:49:36 +0100 Subject: [PATCH 34/85] Optimize some code --- src/de/steamwar/command/CommandPart.java | 28 ++++---------- src/de/steamwar/command/SubCommand.java | 47 +++++++----------------- 2 files changed, 21 insertions(+), 54 deletions(-) diff --git a/src/de/steamwar/command/CommandPart.java b/src/de/steamwar/command/CommandPart.java index bb39119..dc8c6a9 100644 --- a/src/de/steamwar/command/CommandPart.java +++ b/src/de/steamwar/command/CommandPart.java @@ -65,7 +65,9 @@ class CommandPart { this.parameter = parameter; this.parameterIndex = parameterIndex; - validatePart(); + if (optional != null && varArgType != null) { + throw new IllegalArgumentException("A vararg part can't have an optional part! In method " + parameter.getDeclaringExecutable() + " with parameter " + parameterIndex); + } } void addValidator(AbstractValidator validator) { @@ -80,28 +82,18 @@ class CommandPart { this.next = next; } - private void validatePart() { - if (optional != null && varArgType != null) { - throw new IllegalArgumentException("A vararg part can't have an optional part! In method " + parameter.getDeclaringExecutable() + " with parameter " + parameterIndex); - } - } - public void generateArgumentArray(Consumer errors, List current, T sender, String[] args, int startIndex) { if (varArgType != null) { Object array = Array.newInstance(varArgType, args.length - startIndex); for (int i = startIndex; i < args.length; i++) { CheckArgumentResult validArgument = checkArgument(null, sender, args, i); - if (!validArgument.success) { - throw new CommandParseException(); - } + if (!validArgument.success) throw new CommandParseException(); Array.set(array, i - startIndex, validArgument.value); } for (AbstractValidator validator : validators) { if (!validator.validate(sender, array, (s, objects) -> { errors.accept(() -> command.sendMessage(sender, s, objects)); - })) { - throw new CommandParseException(); - } + })) throw new CommandParseException(); } current.add(array); return; @@ -137,11 +129,8 @@ class CommandPart { public void generateTabComplete(List current, T sender, String[] args, int startIndex) { if (varArgType != null) { for (int i = startIndex; i < args.length - 1; i++) { - CheckArgumentResult validArgument = checkArgument((ignore) -> { - }, sender, args, i); - if (!validArgument.success) { - return; - } + CheckArgumentResult validArgument = checkArgument((ignore) -> {}, sender, args, i); + if (!validArgument.success) return; } Collection strings = tabCompletes(sender, args, args.length - 1); if (strings != null) { @@ -151,8 +140,7 @@ class CommandPart { } if (args.length - 1 > startIndex) { - CheckArgumentResult checkArgumentResult = checkArgument((ignore) -> { - }, sender, args, startIndex); + CheckArgumentResult checkArgumentResult = checkArgument((ignore) -> {}, sender, args, startIndex); if (checkArgumentResult.success && next != null) { next.generateTabComplete(current, sender, args, startIndex + 1); return; diff --git a/src/de/steamwar/command/SubCommand.java b/src/de/steamwar/command/SubCommand.java index 0e75d7b..9084fe7 100644 --- a/src/de/steamwar/command/SubCommand.java +++ b/src/de/steamwar/command/SubCommand.java @@ -110,12 +110,8 @@ public class SubCommand { } List tabComplete(T sender, String[] args) { - if (validator != null) { - if (!validator.validate(sender, sender, (s, objects) -> { - // ignore - })) { - return null; - } + if (validator != null && !validator.validate(sender, sender, (s, objects) -> {})) { + return null; } if (commandPart == null) { return null; @@ -180,55 +176,38 @@ public class SubCommand { private static final AbstractValidator STRING_SPACE_FILTER = (sender, value, messageSender) -> { if (!(value instanceof String)) return true; - String s = (String) value; - return !s.contains(" "); + return !((String) value).contains(" "); }; private static AbstractValidator createMinValidator(Class clazz, AbstractSWCommand.Min min) { Function comparator; if (clazz == int.class || clazz == Integer.class) { - int minValue = min.intValue(); - comparator = number -> Integer.compare(number.intValue(), minValue); + comparator = number -> Integer.compare(number.intValue(), min.intValue()); } else if (clazz == long.class || clazz == Long.class) { - long minValue = min.longValue(); - comparator = number -> Long.compare(number.longValue(), minValue); + comparator = number -> Long.compare(number.longValue(), min.longValue()); } else if (clazz == float.class || clazz == Float.class) { - float minValue = min.floatValue(); - comparator = number -> Float.compare(number.floatValue(), minValue); + comparator = number -> Float.compare(number.floatValue(), min.floatValue()); } else if (clazz == double.class || clazz == Double.class) { - double minValue = min.doubleValue(); - comparator = number -> Double.compare(number.doubleValue(), minValue); + comparator = number -> Double.compare(number.doubleValue(), min.doubleValue()); } else { throw new IllegalArgumentException("Min annotation is not supported for " + clazz); } - if (min.inclusive()) { - return (sender, value, messageSender) -> comparator.apply((Number) value).intValue() >= 0; - } else { - return (sender, value, messageSender) -> comparator.apply((Number) value).intValue() > 0; - } + return (sender, value, messageSender) -> comparator.apply((Number) value).intValue() >= (min.inclusive() ? 0 : 1); } private static AbstractValidator createMaxValidator(Class clazz, AbstractSWCommand.Max max) { Function comparator; if (clazz == int.class || clazz == Integer.class) { - int minValue = max.intValue(); - comparator = number -> Integer.compare(number.intValue(), minValue); + comparator = number -> Integer.compare(number.intValue(), max.intValue()); } else if (clazz == long.class || clazz == Long.class) { - long minValue = max.longValue(); - comparator = number -> Long.compare(number.longValue(), minValue); + comparator = number -> Long.compare(number.longValue(), max.longValue()); } else if (clazz == float.class || clazz == Float.class) { - float minValue = max.floatValue(); - comparator = number -> Float.compare(number.floatValue(), minValue); + comparator = number -> Float.compare(number.floatValue(), max.floatValue()); } else if (clazz == double.class || clazz == Double.class) { - double minValue = max.doubleValue(); - comparator = number -> Double.compare(number.doubleValue(), minValue); + comparator = number -> Double.compare(number.doubleValue(), max.doubleValue()); } else { throw new IllegalArgumentException("Max annotation is not supported for " + clazz); } - if (max.inclusive()) { - return (sender, value, messageSender) -> comparator.apply((Number) value).intValue() <= 0; - } else { - return (sender, value, messageSender) -> comparator.apply((Number) value).intValue() < 0; - } + return (sender, value, messageSender) -> comparator.apply((Number) value).intValue() <= (max.inclusive() ? 0 : -1); } } From 6d579b69818a2c4278e7f86544c20f6fda7d82ab Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Fri, 23 Dec 2022 23:14:52 +0100 Subject: [PATCH 35/85] Initial Commit --- src/de/steamwar/sql/Event.java | 71 ++++++++++++++++++++++++--- src/de/steamwar/sql/EventFight.java | 44 +++++++++++++++-- src/de/steamwar/sql/SteamwarUser.java | 6 +++ src/de/steamwar/sql/Team.java | 5 ++ 4 files changed, 113 insertions(+), 13 deletions(-) diff --git a/src/de/steamwar/sql/Event.java b/src/de/steamwar/sql/Event.java index a4c30e6..b51ee34 100644 --- a/src/de/steamwar/sql/Event.java +++ b/src/de/steamwar/sql/Event.java @@ -21,46 +21,65 @@ 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; import java.sql.Timestamp; +import java.util.List; @AllArgsConstructor public class Event { private static final Table table = new Table<>(Event.class); private static final SelectStatement byId = table.select(Table.PRIMARY); + private static final SelectStatement allShort = new SelectStatement<>(table, "SELECT * FROM Event"); + private static final Statement create = table.insertFields(true, "eventName", "deadline", "start", "end", "maximumTeamMembers", "publicSchemsOnly", "spectateSystem"); + private static final Statement update = table.update(Table.PRIMARY, "eventName", "deadline", "start", "end", "schemType", "maximumTeamMembers", "publicSchemsOnly", "spectateSystem"); + private static final Statement delete = table.delete(Table.PRIMARY); + public static Event get(int eventID){ return byId.select(eventID); } + public static List getAllShort(){ + return allShort.listSelect(); + } + + public static Event create(String eventName, Timestamp start, Timestamp end){ + return get(create.insertGetKey(eventName, start, start, end, 5, false, false)); + } + + public static void delete(int eventID){ + delete.update(eventID); + } + @Getter @Field(keys = {Table.PRIMARY}, autoincrement = true) private final int eventID; @Getter @Field(keys = {"eventName"}) - private final String eventName; + private String eventName; @Getter @Field - private final Timestamp deadline; + private Timestamp deadline; @Getter @Field - private final Timestamp start; + private Timestamp start; @Getter @Field - private final Timestamp end; + private Timestamp end; @Getter @Field - private final int maximumTeamMembers; + private int maximumTeamMembers; @Field(nullable = true) - private final SchematicType schemType; + private SchematicType schemType; @Field - private final boolean publicSchemsOnly; + private boolean publicSchemsOnly; @Field - private final boolean spectateSystem; + private boolean spectateSystem; public boolean publicSchemsOnly() { return publicSchemsOnly; @@ -72,4 +91,40 @@ public class Event { public SchematicType getSchematicType() { return schemType; } + + public void setEventName(String eventName) { + this.eventName = eventName; + } + + public void setDeadline(Timestamp deadline) { + this.deadline = deadline; + } + + public void setStart(Timestamp start) { + this.start = start; + } + + public void setEnd(Timestamp end) { + this.end = end; + } + + public void setMaximumTeamMembers(int maximumTeamMembers) { + this.maximumTeamMembers = maximumTeamMembers; + } + + public void setPublicSchemsOnly(boolean publicSchemsOnly) { + this.publicSchemsOnly = publicSchemsOnly; + } + + public void setSpectateSystem(boolean spectateSystem) { + this.spectateSystem = spectateSystem; + } + + public void setSchemType(SchematicType schemType) { + this.schemType = schemType; + } + + public void update(){ + update.update(eventName, deadline, start, end, schemType, maximumTeamMembers, publicSchemsOnly, spectateSystem, eventID); + } } diff --git a/src/de/steamwar/sql/EventFight.java b/src/de/steamwar/sql/EventFight.java index 60e4831..4dd8cb1 100644 --- a/src/de/steamwar/sql/EventFight.java +++ b/src/de/steamwar/sql/EventFight.java @@ -25,6 +25,10 @@ import de.steamwar.sql.internal.Statement; import de.steamwar.sql.internal.Table; import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.Setter; + +import java.sql.Timestamp; +import java.util.List; @AllArgsConstructor public class EventFight { @@ -33,26 +37,52 @@ public class EventFight { 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"); + private static final SelectStatement byEvent = table.selectFields("eventID"); + private static final Statement update = table.update(Table.PRIMARY, "startTime", "spielModus", "map", "teamBlue", "teamRed", "kampfleiter"); + private static final Statement create = table.insertFields(true, "eventID", "startTime", "spielModus", "map", "teamBlue", "teamRed", "kampfleiter"); public static EventFight get(int fightID) { return byId.select(fightID); } + public static EventFight create(int eventID, Timestamp startTime, String spielModus, String map, int teamBlue, int teamRed) { + return EventFight.get(create.insertGetKey(eventID, startTime, spielModus, map, teamBlue, teamRed, 0)); + } + + public static List getFromEvent(int eventID) { + return byEvent.listSelect(eventID); + } + @Getter @Field - private final int eventID; + private int eventID; @Getter @Field(keys = {Table.PRIMARY}, autoincrement = true) - private final int fightID; + private int fightID; @Getter + @Setter @Field - private final int teamBlue; + private Timestamp startTime; @Getter + @Setter @Field - private final int teamRed; + private String spielModus; @Getter + @Setter @Field - private final int kampfleiter; + private String map; + @Getter + @Setter + @Field + private int teamBlue; + @Getter + @Setter + @Field + private int teamRed; + @Getter + @Setter + @Field + private int kampfleiter; @Getter @Field(def = "0") private int ergebnis; @@ -69,4 +99,8 @@ public class EventFight { this.fight = fight; setFight.update(fight, fightID); } + + public void update() { + update.update(startTime, spielModus, map, teamBlue, teamRed, kampfleiter, fightID); + } } diff --git a/src/de/steamwar/sql/SteamwarUser.java b/src/de/steamwar/sql/SteamwarUser.java index 874643f..e36504e 100644 --- a/src/de/steamwar/sql/SteamwarUser.java +++ b/src/de/steamwar/sql/SteamwarUser.java @@ -44,6 +44,8 @@ public class SteamwarUser { 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 SelectStatement getAll = new SelectStatement<>(table, "SELECT * FROM UserData"); 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"); @@ -157,6 +159,10 @@ public class SteamwarUser { return byDiscord.select(discordId); } + public static List getAll() { + return getAll.listSelect(); + } + public static void createOrUpdateUsername(UUID uuid, String userName) { insert.update(uuid, userName); } diff --git a/src/de/steamwar/sql/Team.java b/src/de/steamwar/sql/Team.java index 10e6892..a5af6d8 100644 --- a/src/de/steamwar/sql/Team.java +++ b/src/de/steamwar/sql/Team.java @@ -33,6 +33,7 @@ public class Team { private static final Table table = new Table<>(Team.class); private static final SelectStatement select = table.select(Table.PRIMARY); + private static final SelectStatement all = new SelectStatement(table, "SELECT * FROM Team"); @Field(keys = {Table.PRIMARY}) @Getter @@ -55,6 +56,10 @@ public class Team { return select.select(id); } + public static List getAll() { + return all.listSelect(); + } + public List getMembers(){ return SteamwarUser.getTeam(teamId).stream().map(SteamwarUser::getId).collect(Collectors.toList()); } From bf480f6e6fc212362da34620385d4dee84250698 Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Fri, 23 Dec 2022 23:17:13 +0100 Subject: [PATCH 36/85] Revert "Initial Commit" This reverts commit 6d579b69818a2c4278e7f86544c20f6fda7d82ab. --- src/de/steamwar/sql/Event.java | 71 +++------------------------ src/de/steamwar/sql/EventFight.java | 44 ++--------------- src/de/steamwar/sql/SteamwarUser.java | 6 --- src/de/steamwar/sql/Team.java | 5 -- 4 files changed, 13 insertions(+), 113 deletions(-) diff --git a/src/de/steamwar/sql/Event.java b/src/de/steamwar/sql/Event.java index b51ee34..a4c30e6 100644 --- a/src/de/steamwar/sql/Event.java +++ b/src/de/steamwar/sql/Event.java @@ -21,65 +21,46 @@ 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; import java.sql.Timestamp; -import java.util.List; @AllArgsConstructor public class Event { private static final Table table = new Table<>(Event.class); private static final SelectStatement byId = table.select(Table.PRIMARY); - private static final SelectStatement allShort = new SelectStatement<>(table, "SELECT * FROM Event"); - private static final Statement create = table.insertFields(true, "eventName", "deadline", "start", "end", "maximumTeamMembers", "publicSchemsOnly", "spectateSystem"); - private static final Statement update = table.update(Table.PRIMARY, "eventName", "deadline", "start", "end", "schemType", "maximumTeamMembers", "publicSchemsOnly", "spectateSystem"); - private static final Statement delete = table.delete(Table.PRIMARY); - public static Event get(int eventID){ return byId.select(eventID); } - public static List getAllShort(){ - return allShort.listSelect(); - } - - public static Event create(String eventName, Timestamp start, Timestamp end){ - return get(create.insertGetKey(eventName, start, start, end, 5, false, false)); - } - - public static void delete(int eventID){ - delete.update(eventID); - } - @Getter @Field(keys = {Table.PRIMARY}, autoincrement = true) private final int eventID; @Getter @Field(keys = {"eventName"}) - private String eventName; + private final String eventName; @Getter @Field - private Timestamp deadline; + private final Timestamp deadline; @Getter @Field - private Timestamp start; + private final Timestamp start; @Getter @Field - private Timestamp end; + private final Timestamp end; @Getter @Field - private int maximumTeamMembers; + private final int maximumTeamMembers; @Field(nullable = true) - private SchematicType schemType; + private final SchematicType schemType; @Field - private boolean publicSchemsOnly; + private final boolean publicSchemsOnly; @Field - private boolean spectateSystem; + private final boolean spectateSystem; public boolean publicSchemsOnly() { return publicSchemsOnly; @@ -91,40 +72,4 @@ public class Event { public SchematicType getSchematicType() { return schemType; } - - public void setEventName(String eventName) { - this.eventName = eventName; - } - - public void setDeadline(Timestamp deadline) { - this.deadline = deadline; - } - - public void setStart(Timestamp start) { - this.start = start; - } - - public void setEnd(Timestamp end) { - this.end = end; - } - - public void setMaximumTeamMembers(int maximumTeamMembers) { - this.maximumTeamMembers = maximumTeamMembers; - } - - public void setPublicSchemsOnly(boolean publicSchemsOnly) { - this.publicSchemsOnly = publicSchemsOnly; - } - - public void setSpectateSystem(boolean spectateSystem) { - this.spectateSystem = spectateSystem; - } - - public void setSchemType(SchematicType schemType) { - this.schemType = schemType; - } - - public void update(){ - update.update(eventName, deadline, start, end, schemType, maximumTeamMembers, publicSchemsOnly, spectateSystem, eventID); - } } diff --git a/src/de/steamwar/sql/EventFight.java b/src/de/steamwar/sql/EventFight.java index 4dd8cb1..60e4831 100644 --- a/src/de/steamwar/sql/EventFight.java +++ b/src/de/steamwar/sql/EventFight.java @@ -25,10 +25,6 @@ import de.steamwar.sql.internal.Statement; import de.steamwar.sql.internal.Table; import lombok.AllArgsConstructor; import lombok.Getter; -import lombok.Setter; - -import java.sql.Timestamp; -import java.util.List; @AllArgsConstructor public class EventFight { @@ -37,52 +33,26 @@ public class EventFight { 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"); - private static final SelectStatement byEvent = table.selectFields("eventID"); - private static final Statement update = table.update(Table.PRIMARY, "startTime", "spielModus", "map", "teamBlue", "teamRed", "kampfleiter"); - private static final Statement create = table.insertFields(true, "eventID", "startTime", "spielModus", "map", "teamBlue", "teamRed", "kampfleiter"); public static EventFight get(int fightID) { return byId.select(fightID); } - public static EventFight create(int eventID, Timestamp startTime, String spielModus, String map, int teamBlue, int teamRed) { - return EventFight.get(create.insertGetKey(eventID, startTime, spielModus, map, teamBlue, teamRed, 0)); - } - - public static List getFromEvent(int eventID) { - return byEvent.listSelect(eventID); - } - @Getter @Field - private int eventID; + private final int eventID; @Getter @Field(keys = {Table.PRIMARY}, autoincrement = true) - private int fightID; + private final int fightID; @Getter - @Setter @Field - private Timestamp startTime; + private final int teamBlue; @Getter - @Setter @Field - private String spielModus; + private final int teamRed; @Getter - @Setter @Field - private String map; - @Getter - @Setter - @Field - private int teamBlue; - @Getter - @Setter - @Field - private int teamRed; - @Getter - @Setter - @Field - private int kampfleiter; + private final int kampfleiter; @Getter @Field(def = "0") private int ergebnis; @@ -99,8 +69,4 @@ public class EventFight { this.fight = fight; setFight.update(fight, fightID); } - - public void update() { - update.update(startTime, spielModus, map, teamBlue, teamRed, kampfleiter, fightID); - } } diff --git a/src/de/steamwar/sql/SteamwarUser.java b/src/de/steamwar/sql/SteamwarUser.java index e36504e..874643f 100644 --- a/src/de/steamwar/sql/SteamwarUser.java +++ b/src/de/steamwar/sql/SteamwarUser.java @@ -44,8 +44,6 @@ public class SteamwarUser { 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 SelectStatement getAll = new SelectStatement<>(table, "SELECT * FROM UserData"); 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"); @@ -159,10 +157,6 @@ public class SteamwarUser { return byDiscord.select(discordId); } - public static List getAll() { - return getAll.listSelect(); - } - public static void createOrUpdateUsername(UUID uuid, String userName) { insert.update(uuid, userName); } diff --git a/src/de/steamwar/sql/Team.java b/src/de/steamwar/sql/Team.java index a5af6d8..10e6892 100644 --- a/src/de/steamwar/sql/Team.java +++ b/src/de/steamwar/sql/Team.java @@ -33,7 +33,6 @@ public class Team { private static final Table table = new Table<>(Team.class); private static final SelectStatement select = table.select(Table.PRIMARY); - private static final SelectStatement all = new SelectStatement(table, "SELECT * FROM Team"); @Field(keys = {Table.PRIMARY}) @Getter @@ -56,10 +55,6 @@ public class Team { return select.select(id); } - public static List getAll() { - return all.listSelect(); - } - public List getMembers(){ return SteamwarUser.getTeam(teamId).stream().map(SteamwarUser::getId).collect(Collectors.toList()); } From 8ce900db31e5c4cf0cc5a39dcb61bed297b9d392 Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Thu, 12 Jan 2023 21:53:28 +0100 Subject: [PATCH 37/85] NodeMember: Move Member Schematics --- .../steamwar/sql/EffectiveSchematicNode.java | 248 ++++++++++++++++++ src/de/steamwar/sql/NodeMember.java | 17 +- src/de/steamwar/sql/SchematicNode.java | 108 ++------ 3 files changed, 278 insertions(+), 95 deletions(-) create mode 100644 src/de/steamwar/sql/EffectiveSchematicNode.java diff --git a/src/de/steamwar/sql/EffectiveSchematicNode.java b/src/de/steamwar/sql/EffectiveSchematicNode.java new file mode 100644 index 0000000..e62c7c9 --- /dev/null +++ b/src/de/steamwar/sql/EffectiveSchematicNode.java @@ -0,0 +1,248 @@ +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.*; +import java.util.stream.Collectors; + +import static de.steamwar.sql.SchematicNode.TAB_CACHE; + +public class EffectiveSchematicNode { + + private static final Table table = new Table<>(EffectiveSchematicNode.class, "EffectiveSchematicNode"); + + private static final SelectStatement all = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? ORDER BY NodeName"); + + private static final SelectStatement list_null = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND ParentNode is null ORDER BY NodeName"); + private static final SelectStatement list = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND ParentNode = ? ORDER BY NodeName"); + private static final SelectStatement byParentName = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND ParentNode = ? AND NodeName = ? ORDER BY NodeName"); + private static final SelectStatement byParentName_null = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND ParentNode is null AND NodeName = ? ORDER BY NodeName"); + private static final SelectStatement schematicAccessibleForUser = new SelectStatement<>(table, "SELECT COUNT(DISTINCT NodeId) FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND NodeId = ?"); + private static final SelectStatement accessibleByUserTypeParent = new SelectStatement<>(table, "WITH RECURSIVE RSN AS (SELECT NodeId, ParentNode FROM EffectiveSchematicNode WHERE NodeType = ? AND EffectiveOwner = ? UNION SELECT SN.NodeId, SN.ParentNode FROM RSN, EffectiveSchematicNode SN WHERE SN.NodeId = RSN.ParentNode AND EffectiveOwner = ?) SELECT SN.NodeId, SN.NodeOwner, ? AS EffectiveOwner, SN.NodeName, RSN.ParentNode, SN.LastUpdate, SN.NodeItem, SN.NodeType, SN.NodeRank FROM RSN INNER JOIN SchematicNode SN ON RSN.NodeId = SN.NodeId"); + private static final SelectStatement accessibleByUserTypeInParent = new SelectStatement<>(table, "WITH RECURSIVE RSN AS (SELECT NodeId, ParentNode FROM EffectiveSchematicNode WHERE NodeType = ? AND EffectiveOwner = ? UNION SELECT SN.NodeId, SN.ParentNode FROM RSN, EffectiveSchematicNode SN WHERE SN.NodeId = RSN.ParentNode AND EffectiveOwner = ?) SELECT SN.NodeId, SN.NodeOwner, ? AS EffectiveOwner, SN.NodeName, RSN.ParentNode, SN.LastUpdate, SN.NodeItem, SN.NodeType, SN.NodeRank FROM RSN INNER JOIN SchematicNode SN ON RSN.NodeId = SN.NodeId WHERE RSN.ParentNode = ?"); + private static final SelectStatement accessibleByUserTypeInParent_null = new SelectStatement<>(table, "WITH RECURSIVE RSN AS (SELECT NodeId, ParentNode FROM EffectiveSchematicNode WHERE NodeType = ? AND EffectiveOwner = ? UNION SELECT SN.NodeId, SN.ParentNode FROM RSN, EffectiveSchematicNode SN WHERE SN.NodeId = RSN.ParentNode AND EffectiveOwner = ?) SELECT SN.NodeId, SN.NodeOwner, ? AS EffectiveOwner, SN.NodeName, RSN.ParentNode, SN.LastUpdate, SN.NodeItem, SN.NodeType, SN.NodeRank FROM RSN INNER JOIN SchematicNode SN ON RSN.NodeId = SN.NodeId WHERE RSN.ParentNode is null"); + private static final SelectStatement accessibleByUserType = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND NodeType = ?"); + private static final SelectStatement byIdAndUser = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND NodeId = ?"); + private static final SelectStatement allParentsOfNode = new SelectStatement<>(table, "WITH RECURSIVE R AS (SELECT NodeId, ParentNode FROM EffectiveSchematicNode WHERE NodeId = ? AND EffectiveOwner = ? UNION SELECT E.NodeId, E.ParentNode FROM R, EffectiveSchematicNode E WHERE R.ParentNode = E.NodeId AND E.EffectiveOwner = ?) SELECT SN.NodeId, SN.NodeOwner, ? AS EffectiveOwner, SN.NodeName, R.ParentNode, SN.LastUpdate, SN.NodeItem, SN.NodeType, SN.NodeRank FROM R INNER JOIN SchematicNode SN ON SN.NodeId = R.NodeId"); + + public static List getAll(SteamwarUser user) { + return all.listSelect(user); + } + + public static Map> getAllMap(SteamwarUser user) { + return map(all.listSelect(user)); + } + + public static List list(SteamwarUser user, Integer schematicId) { + if(schematicId == null || schematicId == 0) { + return list_null.listSelect(user); + } else { + return list.listSelect(user, schematicId); + } + } + + public static Optional byParentName(SteamwarUser user, Integer schematicId, String name) { + if(schematicId == null || schematicId == 0) { + return Optional.ofNullable(byParentName_null.select(user, name)); + } else { + return Optional.ofNullable(byParentName.select(user, schematicId, name)); + } + } + + public static List accessibleByUserType(SteamwarUser user, SchematicType type) { + return accessibleByUserType.listSelect(type, user, user, user); + } + + public static Map> accessibleByUserTypeMap(SteamwarUser user, SchematicType type) { + return map(accessibleByUserTypeParent.listSelect(type, user, user, user)); + } + + public static boolean schematicAccessibleForUser(SteamwarUser user, Integer schematicId) { + return schematicAccessibleForUser.select(user, schematicId) != null; + } + + public static List accessibleByUserTypeParent(SteamwarUser user, SchematicType type, Integer parentId) { + if(parentId == null || parentId == 0) { + return accessibleByUserTypeInParent_null.listSelect(type, user, user, user); + } else { + return accessibleByUserTypeInParent.listSelect(type, user, user, user, parentId); + } + } + + public static Optional byIdAndUser(SteamwarUser user, Integer id) { + return Optional.ofNullable(byIdAndUser.select(user, id)); + } + + public static List parentsOfNode(SteamwarUser user, Integer id) { + return allParentsOfNode.listSelect(id, user, user, user).stream().filter(n -> n.getNodeId() != id).collect(Collectors.toList()); + } + + private static Map> map(List in) { + Map> map = new HashMap<>(); + for (EffectiveSchematicNode effectiveSchematicNode : in) { + map.computeIfAbsent(effectiveSchematicNode.getNodeParent().orElse(0), k -> new ArrayList<>()).add(effectiveSchematicNode); + } + return map; + } + + @Getter + @Field + private final int nodeId; + @Getter + @Field + private final int nodeOwner; + @Getter + @Field + private final int effectiveOwner; + @Getter + @Field + private final String nodeName; + @Field(nullable = true) + private final Integer parentNode; + @Getter + @Field + private final Timestamp lastUpdate; + @Field + private final String nodeItem; + @Field(nullable = true) + private final SchematicType nodeType; + @Field + private final int nodeRank; + + private String brCache = null; + + public EffectiveSchematicNode(int nodeId, int nodeOwner, int effectiveOwner, String nodeName, Integer nodeParent, Timestamp lastUpdate, String nodeItem, SchematicType nodeType, int nodeRank) { + this.nodeId = nodeId; + this.nodeOwner = nodeOwner; + this.effectiveOwner = effectiveOwner; + this.nodeName = nodeName; + this.parentNode = nodeParent; + this.lastUpdate = lastUpdate; + this.nodeItem = nodeItem; + this.nodeType = nodeType; + this.nodeRank = nodeRank; + } + + public boolean isDir() { + return nodeType == null; + } + + public Optional getNodeParent() { + return Optional.ofNullable(parentNode); + } + + public Optional getParentNode() { + return byIdAndUser(SteamwarUser.get(effectiveOwner), parentNode); + } + + public SchematicType getNodeType() { + if(isDir()) { + throw new IllegalStateException("This node is a directory"); + } + return nodeType; + } + + public int getNodeRank() { + if(isDir()) { + throw new IllegalStateException("This node is a directory"); + } + return nodeRank; + } + + public SchematicNode toSchematicNode() { + return SchematicNode.getSchematicNode(nodeId); + } + + public boolean isWritable() { + return effectiveOwner == nodeOwner; + } + + public String getNodeItem() { + if (nodeItem.isEmpty()) { + return isDir() ? "CHEST" : "CAULDRON_ITEM"; + } + return nodeItem; + } + + 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); + Optional pa = EffectiveSchematicNode.getNodeFromPath(user, preTab); + if (!pa.isPresent()) return Collections.emptyList(); + List nodes = EffectiveSchematicNode.list(user, pa.get().getNodeId()); + nodes.forEach(node -> list.add((sws ? "/" : "") + node.generateBreadcrumbs())); + } else { + List nodes = EffectiveSchematicNode.list(user, 0); + nodes.forEach(node -> list.add((sws ? "/" : "") + node.getNodeName() + (node.isDir() ? "/" : ""))); + } + list.remove("//copy"); + TAB_CACHE.computeIfAbsent(user.getId(), integer -> new HashMap<>()).putIfAbsent(cacheKey, list); + return list; + } + + public String generateBreadcrumbs() { + if(brCache == null) { + brCache = generateBreadcrumbs("/"); + } + return brCache; + } + + public String generateBreadcrumbs(String split) { + StringBuilder builder = new StringBuilder(getNodeName()); + Optional currentNode = Optional.of(this); + if(currentNode.map(EffectiveSchematicNode::isDir).orElse(false)) { + builder.append("/"); + } + while (currentNode.isPresent()) { + currentNode = currentNode.flatMap(EffectiveSchematicNode::getNodeParent).flatMap(integer -> byIdAndUser(SteamwarUser.get(effectiveOwner), integer)); + currentNode.ifPresent(node -> builder.insert(0, '/').insert(0, node.getNodeName())); + } + return builder.toString(); + } + + public static Optional getNodeFromPath(SteamwarUser user, String s) { + if (s.startsWith("/")) { + s = s.substring(1); + } + if (s.endsWith("/")) { + s = s.substring(0, s.length() - 1); + } + if (s.isEmpty()) { + return Optional.empty(); + } + if (s.contains("/")) { + String[] layers = s.split("/"); + Optional currentNode = EffectiveSchematicNode.byParentName(user, 0, layers[0]); + for (int i = 1; i < layers.length; i++) { + int finalI = i; + Optional node = currentNode.flatMap(effectiveSchematicNode -> EffectiveSchematicNode.byParentName(user, effectiveSchematicNode.getNodeId(), layers[finalI])); + if (!node.isPresent()) { + return Optional.empty(); + } else { + currentNode = node; + if (!currentNode.map(EffectiveSchematicNode::isDir).orElse(false) && i != layers.length - 1) { + return Optional.empty(); + } + } + } + return currentNode; + } else { + return EffectiveSchematicNode.byParentName(user, 0, s); + } + } +} diff --git a/src/de/steamwar/sql/NodeMember.java b/src/de/steamwar/sql/NodeMember.java index e704ad0..19417da 100644 --- a/src/de/steamwar/sql/NodeMember.java +++ b/src/de/steamwar/sql/NodeMember.java @@ -26,6 +26,7 @@ import de.steamwar.sql.internal.Table; import lombok.AllArgsConstructor; import java.util.HashSet; +import java.util.Optional; import java.util.Set; @AllArgsConstructor @@ -39,13 +40,16 @@ public class NodeMember { 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 create = table.insert(Table.PRIMARY); private static final Statement delete = table.delete(Table.PRIMARY); + private static final Statement updateParent = table.update(Table.PRIMARY, "ParentId"); @Field(keys = {Table.PRIMARY}) private final int nodeId; @Field(keys = {Table.PRIMARY}) private final int userId; + @Field(nullable = true, def = "null") + private Integer parentId; public int getNode() { return nodeId; @@ -55,13 +59,17 @@ public class NodeMember { return userId; } + public Optional getParent() { + return Optional.ofNullable(parentId); + } + public void delete() { delete.update(nodeId, userId); } public static NodeMember createNodeMember(int node, int member) { create.update(node, member); - return new NodeMember(node, member); + return new NodeMember(node, member, null); } public static NodeMember getNodeMember(int node, int member) { @@ -75,4 +83,9 @@ public class NodeMember { public static Set getSchematics(int member) { return new HashSet<>(getSchematics.listSelect(member)); } + + public void setParentId(Integer parentId) { + this.parentId = Optional.ofNullable(parentId).orElse(0) == 0 ? null : parentId; + updateParent.update(this.parentId, nodeId, userId); + } } diff --git a/src/de/steamwar/sql/SchematicNode.java b/src/de/steamwar/sql/SchematicNode.java index 3e610a6..0299e77 100644 --- a/src/de/steamwar/sql/SchematicNode.java +++ b/src/de/steamwar/sql/SchematicNode.java @@ -38,7 +38,7 @@ public class SchematicNode { 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<>(); + protected static final Map>> TAB_CACHE = new HashMap<>(); public static void clear() { TAB_CACHE.clear(); } @@ -62,14 +62,8 @@ public class SchematicNode { 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 { @@ -100,8 +94,6 @@ public class SchematicNode { @Field(def = "1") private boolean nodeFormat; - private final Map brCache = new HashMap<>(); - public SchematicNode( int nodeId, int nodeOwner, @@ -196,14 +188,14 @@ public class SchematicNode { return byId.select(id); } + @Deprecated 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); + return EffectiveSchematicNode.accessibleByUserTypeParent(SteamwarUser.get(owner), SchematicType.fromDB(schemType), parent).stream().map(EffectiveSchematicNode::toSchematicNode).collect(Collectors.toList()); } + @Deprecated public static List getAllAccessibleSchematicsOfType(int user, String schemType) { - return accessibleByUserType.listSelect(user, user, schemType); + return EffectiveSchematicNode.accessibleByUserType(SteamwarUser.get(user), SchematicType.fromDB(schemType)).stream().map(EffectiveSchematicNode::toSchematicNode).collect(Collectors.toList()); } public static List getAllSchematicsOfType(int owner, String schemType) { @@ -233,21 +225,14 @@ public class SchematicNode { return finalList; } + @Deprecated 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(); + return EffectiveSchematicNode.list(SteamwarUser.get(user), parent).stream().map(EffectiveSchematicNode::toSchematicNode).collect(Collectors.toList()); } + @Deprecated public static List getAllSchematicsAccessibleByUser(int user) { - return allAccessibleByUser.listSelect(user, user); + return EffectiveSchematicNode.getAll(SteamwarUser.get(user)).stream().map(EffectiveSchematicNode::toSchematicNode).collect(Collectors.toList()); } public static List getAllParentsOfNode(SchematicNode node) { @@ -258,38 +243,9 @@ public class SchematicNode { return allParentsOfNode.listSelect(node); } + @Deprecated 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); - } + return EffectiveSchematicNode.getNodeFromPath(user, s).map(EffectiveSchematicNode::toSchematicNode).orElse(null); } public static List filterSchems(int user, Predicate filter) { @@ -448,7 +404,6 @@ public class SchematicNode { private void updateDB() { this.lastUpdate = Timestamp.from(Instant.now()); update.update(nodeName, parentNode, nodeItem, nodeType, nodeRank, replaceColor, allowReplay, nodeFormat, nodeId); - this.brCache.clear(); TAB_CACHE.clear(); } @@ -470,23 +425,11 @@ public class SchematicNode { } public String generateBreadcrumbs(SteamwarUser user) { - return brCache.computeIfAbsent(user.getId(), integer -> generateBreadcrumbs("/", user)); + return EffectiveSchematicNode.byIdAndUser(user, nodeId).map(EffectiveSchematicNode::generateBreadcrumbs).orElse("/"); } 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(); + return EffectiveSchematicNode.byIdAndUser(user, nodeId).map(node -> node.generateBreadcrumbs(split)).orElse("/"); } private static final List FORBIDDEN_NAMES = Collections.unmodifiableList(Arrays.asList("public")); @@ -513,30 +456,9 @@ public class SchematicNode { return false; } + @Deprecated 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; + return EffectiveSchematicNode.getNodeTabcomplete(user, s); } private static void rootWarning() { From 45bc5b05d71fb7bc909537b4c12522b175e62ffd Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Sat, 14 Jan 2023 08:57:04 +0100 Subject: [PATCH 38/85] Add Copyright --- .../steamwar/sql/EffectiveSchematicNode.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/de/steamwar/sql/EffectiveSchematicNode.java b/src/de/steamwar/sql/EffectiveSchematicNode.java index e62c7c9..587034e 100644 --- a/src/de/steamwar/sql/EffectiveSchematicNode.java +++ b/src/de/steamwar/sql/EffectiveSchematicNode.java @@ -1,3 +1,22 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2023 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; From bd7635da0d686565fd45496e9966f14d336e3f1f Mon Sep 17 00:00:00 2001 From: yoyosource Date: Sun, 15 Jan 2023 11:23:58 +0100 Subject: [PATCH 39/85] Add CommandMetaData.ImplicitValidator for easier extension of implicit validators --- .../steamwar/command/AbstractSWCommand.java | 29 ++--- .../steamwar/command/AbstractTypeMapper.java | 2 + .../{MetaData.java => CommandMetaData.java} | 29 ++++- src/de/steamwar/command/CommandPart.java | 4 + .../command/ImplicitTypeValidators.java | 102 ++++++++++++++++++ src/de/steamwar/command/SubCommand.java | 48 +++++++-- 6 files changed, 192 insertions(+), 22 deletions(-) rename src/de/steamwar/command/{MetaData.java => CommandMetaData.java} (54%) create mode 100644 src/de/steamwar/command/ImplicitTypeValidators.java diff --git a/src/de/steamwar/command/AbstractSWCommand.java b/src/de/steamwar/command/AbstractSWCommand.java index 8ab7cbd..2793c3c 100644 --- a/src/de/steamwar/command/AbstractSWCommand.java +++ b/src/de/steamwar/command/AbstractSWCommand.java @@ -183,7 +183,7 @@ public abstract class AbstractSWCommand { private boolean validateMethod(Method method) { if (!checkType(method.getAnnotations(), method.getReturnType(), annotation -> { - MetaData.Method methodMetaData = annotation.annotationType().getAnnotation(MetaData.Method.class); + CommandMetaData.Method methodMetaData = annotation.annotationType().getAnnotation(CommandMetaData.Method.class); if (methodMetaData == null) return null; if (method.getParameterCount() > methodMetaData.maxParameterCount() || method.getParameterCount() < methodMetaData.minParameterCount()) return new Class[0]; return methodMetaData.value(); @@ -193,7 +193,7 @@ public abstract class AbstractSWCommand { Class type = parameter.getType(); if (parameter.isVarArgs()) type = type.getComponentType(); if (!checkType(parameter.getAnnotations(), type, annotation -> { - MetaData.Parameter parameterMetaData = annotation.annotationType().getAnnotation(MetaData.Parameter.class); + CommandMetaData.Parameter parameterMetaData = annotation.annotationType().getAnnotation(CommandMetaData.Parameter.class); if (parameterMetaData == null) return null; return parameterMetaData.value(); }, "The parameter '" + parameter + "'")) valid = false; @@ -269,7 +269,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) @Repeatable(Register.Registeres.class) - @MetaData.Method(value = void.class, minParameterCount = 1) + @CommandMetaData.Method(value = void.class, minParameterCount = 1) protected @interface Register { /** @@ -286,7 +286,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) - @MetaData.Method(value = void.class, minParameterCount = 1) + @CommandMetaData.Method(value = void.class, minParameterCount = 1) @interface Registeres { Register[] value(); } @@ -294,7 +294,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER, ElementType.METHOD}) - @MetaData.Method(value = AbstractTypeMapper.class, maxParameterCount = 0) + @CommandMetaData.Method(value = AbstractTypeMapper.class, maxParameterCount = 0) protected @interface Mapper { String value(); @@ -303,7 +303,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) - @MetaData.Method(value = AbstractTypeMapper.class, maxParameterCount = 0) + @CommandMetaData.Method(value = AbstractTypeMapper.class, maxParameterCount = 0) protected @interface ClassMapper { Class value(); @@ -312,7 +312,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) - @MetaData.Method(value = AbstractTypeMapper.class, maxParameterCount = 0) + @CommandMetaData.Method(value = AbstractTypeMapper.class, maxParameterCount = 0) protected @interface Cached { long cacheDuration() default 5; TimeUnit timeUnit() default TimeUnit.SECONDS; @@ -321,7 +321,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER, ElementType.METHOD}) - @MetaData.Method(value = AbstractValidator.class, maxParameterCount = 0) + @CommandMetaData.Method(value = AbstractValidator.class, maxParameterCount = 0) protected @interface Validator { String value() default ""; @@ -330,7 +330,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) - @MetaData.Method(value = AbstractValidator.class, maxParameterCount = 0) + @CommandMetaData.Method(value = AbstractValidator.class, maxParameterCount = 0) protected @interface ClassValidator { Class value(); @@ -341,7 +341,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) - @MetaData.Parameter({String.class, int.class, Integer.class, long.class, Long.class, boolean.class, Boolean.class}) + @CommandMetaData.Parameter({String.class, int.class, Integer.class, long.class, Long.class, boolean.class, Boolean.class}) protected @interface StaticValue { String[] value(); @@ -376,6 +376,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) + @CommandMetaData.ImplicitValidator(handler = ImplicitTypeValidators.ErrorMessageValidator.class, order = 1) protected @interface ErrorMessage { /** * Error message to be displayed when the parameter is invalid. @@ -398,13 +399,14 @@ public abstract class AbstractSWCommand { */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) - @MetaData.Parameter({String.class}) + @CommandMetaData.Parameter({String.class}) protected @interface Quotable { } @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) - @MetaData.Parameter({int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class}) + @CommandMetaData.Parameter({int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class}) + @CommandMetaData.ImplicitValidator(handler = ImplicitTypeValidators.MinValidator.class, order = 2) protected @interface Min { int intValue() default Integer.MIN_VALUE; long longValue() default Long.MIN_VALUE; @@ -416,7 +418,8 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) - @MetaData.Parameter({int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class}) + @CommandMetaData.Parameter({int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class}) + @CommandMetaData.ImplicitValidator(handler = ImplicitTypeValidators.MaxValidator.class, order = 2) protected @interface Max { int intValue() default Integer.MAX_VALUE; long longValue() default Long.MAX_VALUE; diff --git a/src/de/steamwar/command/AbstractTypeMapper.java b/src/de/steamwar/command/AbstractTypeMapper.java index 4a340d8..1c8550b 100644 --- a/src/de/steamwar/command/AbstractTypeMapper.java +++ b/src/de/steamwar/command/AbstractTypeMapper.java @@ -25,6 +25,8 @@ public interface AbstractTypeMapper extends AbstractValidator { /** * The CommandSender can be null! */ + // TODO: Change the 'previousArguments' to List or something like that + // TODO: Change T return value to Pair or something like that. This would make OptionalValue easier to implement as the Boolean would indicate if s should be consumed or not T map(K sender, String[] previousArguments, String s); @Override diff --git a/src/de/steamwar/command/MetaData.java b/src/de/steamwar/command/CommandMetaData.java similarity index 54% rename from src/de/steamwar/command/MetaData.java rename to src/de/steamwar/command/CommandMetaData.java index 5745fc4..c7f79c7 100644 --- a/src/de/steamwar/command/MetaData.java +++ b/src/de/steamwar/command/CommandMetaData.java @@ -24,8 +24,11 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -@interface MetaData { +public @interface CommandMetaData { + /** + * This annotation is only for internal use. + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) @interface Method { @@ -34,9 +37,33 @@ import java.lang.annotation.Target; int maxParameterCount() default Integer.MAX_VALUE; } + /** + * This annotation denotes what types are allowed as parameter types the annotation annotated with can use. + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) @interface Parameter { Class[] value(); } + + /** + * This annotation can be used in conjunction with a class that implement {@link AbstractValidator} to + * create custom validator short hands for commands. The validator class itself should contain a constructor + * with two parameters the first is the annotation this annotation is used on and the second is of type {@link Class}. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.ANNOTATION_TYPE) + @interface ImplicitValidator { + /** + * The validator class that should be used. + */ + Class handler(); + + /** + * Defines when this validator should be processed. Negative numbers denote that this will be + * processed before {@link AbstractSWCommand.Validator} and positive numbers + * denote that this will be processed after {@link AbstractSWCommand.Validator}. + */ + int order(); + } } diff --git a/src/de/steamwar/command/CommandPart.java b/src/de/steamwar/command/CommandPart.java index dc8c6a9..2969872 100644 --- a/src/de/steamwar/command/CommandPart.java +++ b/src/de/steamwar/command/CommandPart.java @@ -192,4 +192,8 @@ class CommandPart { } return new CheckArgumentResult(true, value); } + + public Class getType() { + return varArgType != null ? varArgType : parameter.getType(); + } } diff --git a/src/de/steamwar/command/ImplicitTypeValidators.java b/src/de/steamwar/command/ImplicitTypeValidators.java new file mode 100644 index 0000000..7204ce5 --- /dev/null +++ b/src/de/steamwar/command/ImplicitTypeValidators.java @@ -0,0 +1,102 @@ +/* + * 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.command; + +import lombok.experimental.UtilityClass; + +import java.lang.reflect.Array; +import java.util.function.Function; + +@UtilityClass +public class ImplicitTypeValidators { + + public static class ErrorMessageValidator implements AbstractValidator { + + private AbstractSWCommand.ErrorMessage errorMessage; + + public ErrorMessageValidator(AbstractSWCommand.ErrorMessage errorMessage, Class type) { + this.errorMessage = errorMessage; + } + + @Override + public boolean validate(T sender, Object value, MessageSender messageSender) { + if (value == null) messageSender.send(errorMessage.value()); + if (!errorMessage.allowEAs() && value != null && value.getClass().isArray() && Array.getLength(value) == 0) { + messageSender.send(errorMessage.value()); + return false; + } + return value != null; + } + } + + public static class MinValidator implements AbstractValidator { + + private int value; + private Function comparator; + + public MinValidator(AbstractSWCommand.Min min, Class clazz) { + this.value = min.inclusive() ? 0 : 1; + + if (clazz == int.class || clazz == Integer.class) { + comparator = number -> Integer.compare(number.intValue(), min.intValue()); + } else if (clazz == long.class || clazz == Long.class) { + comparator = number -> Long.compare(number.longValue(), min.longValue()); + } else if (clazz == float.class || clazz == Float.class) { + comparator = number -> Float.compare(number.floatValue(), min.floatValue()); + } else if (clazz == double.class || clazz == Double.class) { + comparator = number -> Double.compare(number.doubleValue(), min.doubleValue()); + } else { + throw new IllegalArgumentException("Min annotation is not supported for " + clazz); + } + } + + @Override + public boolean validate(T sender, Number value, MessageSender messageSender) { + return (comparator.apply(value).intValue()) >= this.value; + } + } + + public static class MaxValidator implements AbstractValidator { + + private int value; + private Function comparator; + + public MaxValidator(AbstractSWCommand.Max max, Class clazz) { + this.value = max.inclusive() ? 0 : -1; + + if (clazz == int.class || clazz == Integer.class) { + comparator = number -> Integer.compare(number.intValue(), max.intValue()); + } else if (clazz == long.class || clazz == Long.class) { + comparator = number -> Long.compare(number.longValue(), max.longValue()); + } else if (clazz == float.class || clazz == Float.class) { + comparator = number -> Float.compare(number.floatValue(), max.floatValue()); + } else if (clazz == double.class || clazz == Double.class) { + comparator = number -> Double.compare(number.doubleValue(), max.doubleValue()); + } else { + throw new IllegalArgumentException("Max annotation is not supported for " + clazz); + } + } + + @Override + public boolean validate(T sender, Number value, MessageSender messageSender) { + return (comparator.apply(value).intValue()) <= this.value; + } + } +} diff --git a/src/de/steamwar/command/SubCommand.java b/src/de/steamwar/command/SubCommand.java index 9084fe7..da3576e 100644 --- a/src/de/steamwar/command/SubCommand.java +++ b/src/de/steamwar/command/SubCommand.java @@ -19,10 +19,13 @@ package de.steamwar.command; +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Consumer; @@ -142,22 +145,16 @@ public class SubCommand { AbstractValidator validator = (AbstractValidator) SWCommandUtils.getValidator(parameter, localValidator); Class varArgType = parameter.isVarArgs() ? parameter.getType().getComponentType() : null; AbstractSWCommand.OptionalValue optionalValue = parameter.getAnnotation(AbstractSWCommand.OptionalValue.class); - AbstractSWCommand.Min min = parameter.getAnnotation(AbstractSWCommand.Min.class); - AbstractSWCommand.Max max = parameter.getAnnotation(AbstractSWCommand.Max.class); CommandPart commandPart = new CommandPart<>(command, typeMapper, varArgType, optionalValue != null ? optionalValue.value() : null, parameter, i); commandPart.setOnlyUseIfNoneIsGiven(optionalValue != null && optionalValue.onlyUINIG()); if (parameter.getAnnotation(AbstractSWCommand.Quotable.class) == null) { commandPart.addValidator((AbstractValidator) STRING_SPACE_FILTER); } + handleImplicitTypeValidator(parameter, commandPart, true); commandPart.addValidator(validator); commandPart.addValidator((AbstractValidator) SWCommandUtils.getErrorMessage(parameter)); - if (min != null) { - commandPart.addValidator((AbstractValidator) createMinValidator(varArgType != null ? varArgType : parameter.getType(), min)); - } - if (max != null) { - commandPart.addValidator((AbstractValidator) createMaxValidator(varArgType != null ? varArgType : parameter.getType(), max)); - } + handleImplicitTypeValidator(parameter, commandPart, false); if (parameter.getAnnotation(AbstractSWCommand.AllowNull.class) == null) { commandPart.addValidator((AbstractValidator) NULL_FILTER); } @@ -172,6 +169,41 @@ public class SubCommand { return first; } + private static void handleImplicitTypeValidator(Parameter parameter, CommandPart commandPart, boolean beforeValidatorAnnotation) { + Annotation[] annotations = parameter.getAnnotations(); + Map>> validators = new HashMap<>(); + for (Annotation annotation : annotations) { + CommandMetaData.ImplicitValidator implicitValidator = annotation.annotationType().getAnnotation(CommandMetaData.ImplicitValidator.class); + if (implicitValidator == null) continue; + if (beforeValidatorAnnotation && implicitValidator.order() >= 0) continue; + if (!beforeValidatorAnnotation && implicitValidator.order() <= 0) continue; + Class clazz = implicitValidator.handler(); + if (!AbstractValidator.class.isAssignableFrom(clazz)) continue; + Constructor[] constructors = clazz.getConstructors(); + if (constructors.length != 1) continue; + Constructor constructor = constructors[0]; + Class[] parameterTypes = constructor.getParameterTypes(); + if (parameterTypes.length != 2) continue; + AbstractValidator validator; + if (!annotation.annotationType().isAssignableFrom(parameterTypes[0])) continue; + if (!Class.class.isAssignableFrom(parameterTypes[1])) continue; + try { + validator = (AbstractValidator) constructor.newInstance(annotation, commandPart.getType()); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + validators.computeIfAbsent(implicitValidator.order(), integer -> new ArrayList<>()).add(validator); + } + List keys = new ArrayList<>(validators.keySet()); + keys.sort(Integer::compareTo); + for (Integer key : keys) { + List> list = validators.get(key); + for (AbstractValidator validator : list) { + commandPart.addValidator(validator); + } + } + } + private static final AbstractValidator NULL_FILTER = (sender, value, messageSender) -> value != null; private static final AbstractValidator STRING_SPACE_FILTER = (sender, value, messageSender) -> { From d62dd7614a352cc58fa4030b5d244349aa375a4a Mon Sep 17 00:00:00 2001 From: Lixfel Date: Sun, 15 Jan 2023 12:30:38 +0100 Subject: [PATCH 40/85] Adjust CI config to new CI --- steamwarci.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/steamwarci.yml b/steamwarci.yml index 73f8039..47930f4 100644 --- a/steamwarci.yml +++ b/steamwarci.yml @@ -1,5 +1,6 @@ -build: +setup: - "ln -s /home/gitea/lib" - - "cp ~/gradle.properties ." - - "chmod u+x build.gradle" + +build: - "./gradlew buildProject" + - "./gradlew --stop" From bf1dde08c2294a8877c908f0b8cc74dc7886434f Mon Sep 17 00:00:00 2001 From: Lixfel Date: Sun, 15 Jan 2023 12:30:57 +0100 Subject: [PATCH 41/85] Fix build.gradle --- build.gradle | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 build.gradle diff --git a/build.gradle b/build.gradle old mode 100644 new mode 100755 From dbd91bf41a95d138679112b18ebb3582be2606c8 Mon Sep 17 00:00:00 2001 From: Lixfel Date: Sun, 15 Jan 2023 12:34:18 +0100 Subject: [PATCH 42/85] Fix closing connection on invalid connection --- src/de/steamwar/sql/internal/Statement.java | 32 ++++++++++++--------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/de/steamwar/sql/internal/Statement.java b/src/de/steamwar/sql/internal/Statement.java index 6c44332..ded799c 100644 --- a/src/de/steamwar/sql/internal/Statement.java +++ b/src/de/steamwar/sql/internal/Statement.java @@ -157,32 +157,36 @@ public class Statement implements AutoCloseable { private T withConnection(SQLRunnable runnable, Object... objects) { Connection connection = aquireConnection(); + T result; try { - try { - return tryWithConnection(connection, runnable, objects); - } finally { - if(connectionInvalid(connection)) { - closeConnection(connection); - } else { - synchronized (connections) { - connections.push(connection); - connections.notify(); - } - } - } - } catch (SQLException e) { + result = tryWithConnection(connection, runnable, objects); + } catch (Throwable e) { if(connectionInvalid(connection)) { + closeConnection(connection); + return withConnection(runnable, objects); } else { + synchronized (connections) { + connections.push(connection); + connections.notify(); + } + throw new SecurityException("Failing sql statement", e); } } + + synchronized (connections) { + connections.push(connection); + connections.notify(); + } + + return result; } private boolean connectionInvalid(Connection connection) { try { - return connection.isClosed(); + return connection.isClosed() || !connection.isValid(1); } catch (SQLException e) { logger.log(Level.INFO, "Could not check SQL connection status", e); // No database logging possible at this state return true; From 7e67f745718b38d3cfff2995656becc943d21ccd Mon Sep 17 00:00:00 2001 From: yoyosource Date: Sun, 15 Jan 2023 16:40:39 +0100 Subject: [PATCH 43/85] Add PreviousArguments for better handling in AbstractTypeMapper --- .../steamwar/command/AbstractSWCommand.java | 160 +++++++++++++++++- .../steamwar/command/AbstractTypeMapper.java | 23 ++- .../command/CommandFrameworkException.java | 6 - src/de/steamwar/command/CommandMetaData.java | 31 +++- src/de/steamwar/command/CommandPart.java | 38 +++-- .../command/ImplicitTypeValidators.java | 102 ----------- .../steamwar/command/PreviousArguments.java | 39 +++++ src/de/steamwar/command/SWCommandUtils.java | 84 ++------- src/de/steamwar/command/SubCommand.java | 141 +++++++++------ .../command/BetterExceptionCommand.java | 4 +- testsrc/de/steamwar/command/CacheCommand.java | 4 +- .../steamwar/command/NullMapperCommand.java | 4 +- 12 files changed, 370 insertions(+), 266 deletions(-) delete mode 100644 src/de/steamwar/command/ImplicitTypeValidators.java create mode 100644 src/de/steamwar/command/PreviousArguments.java diff --git a/src/de/steamwar/command/AbstractSWCommand.java b/src/de/steamwar/command/AbstractSWCommand.java index 2793c3c..bf65ec8 100644 --- a/src/de/steamwar/command/AbstractSWCommand.java +++ b/src/de/steamwar/command/AbstractSWCommand.java @@ -20,6 +20,7 @@ package de.steamwar.command; import java.lang.annotation.*; +import java.lang.reflect.Array; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.*; @@ -295,10 +296,35 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER, ElementType.METHOD}) @CommandMetaData.Method(value = AbstractTypeMapper.class, maxParameterCount = 0) + @CommandMetaData.ImplicitTypeMapper(handler = Mapper.Handler.class) protected @interface Mapper { String value(); boolean local() default false; + + class Handler implements AbstractTypeMapper { + + private AbstractTypeMapper inner; + + public Handler(AbstractSWCommand.Mapper mapper, Map> localTypeMapper) { + inner = (AbstractTypeMapper) SWCommandUtils.getTypeMapper(mapper.value(), localTypeMapper); + } + + @Override + public Object map(T sender, PreviousArguments previousArguments, String s) { + return inner.map(sender, previousArguments, s); + } + + @Override + public boolean validate(T sender, Object value, MessageSender messageSender) { + return inner.validate(sender, value, messageSender); + } + + @Override + public Collection tabCompletes(T sender, PreviousArguments previousArguments, String s) { + return inner.tabCompletes(sender, previousArguments, s); + } + } } @Retention(RetentionPolicy.RUNTIME) @@ -322,10 +348,25 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER, ElementType.METHOD}) @CommandMetaData.Method(value = AbstractValidator.class, maxParameterCount = 0) + @CommandMetaData.ImplicitValidator(handler = Validator.Handler.class, order = 0) protected @interface Validator { String value() default ""; boolean local() default false; + + class Handler implements AbstractValidator { + + private AbstractValidator inner; + + public Handler(AbstractSWCommand.Validator validator, Class clazz, Map> localValidator) { + inner = (AbstractValidator) SWCommandUtils.getValidator(validator, clazz, localValidator); + } + + @Override + public boolean validate(T sender, Object value, MessageSender messageSender) { + return inner.validate(sender, value, messageSender); + } + } } @Retention(RetentionPolicy.RUNTIME) @@ -342,6 +383,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) @CommandMetaData.Parameter({String.class, int.class, Integer.class, long.class, Long.class, boolean.class, Boolean.class}) + @CommandMetaData.ImplicitTypeMapper(handler = StaticValue.Handler.class) protected @interface StaticValue { String[] value(); @@ -356,6 +398,53 @@ public abstract class AbstractSWCommand { boolean allowISE() default false; int[] falseValues() default { 0 }; + + class Handler implements AbstractTypeMapper { + + private AbstractTypeMapper inner; + + public Handler(StaticValue staticValue, Class clazz) { + if (clazz == String.class) { + inner = SWCommandUtils.createMapper(staticValue.value()); + return; + } + if (!staticValue.allowISE()) { + throw new IllegalArgumentException("The parameter type '" + clazz.getTypeName() + "' is not supported by the StaticValue annotation"); + } + if (clazz == boolean.class || clazz == Boolean.class) { + List tabCompletes = new ArrayList<>(Arrays.asList(staticValue.value())); + Set falseValues = new HashSet<>(); + for (int i : staticValue.falseValues()) falseValues.add(i); + inner = SWCommandUtils.createMapper(s -> { + int index = tabCompletes.indexOf(s); + return index == -1 ? null : !falseValues.contains(index); + }, (commandSender, s) -> tabCompletes); + } else if (clazz == int.class || clazz == Integer.class || clazz == long.class || clazz == Long.class) { + List tabCompletes = new ArrayList<>(Arrays.asList(staticValue.value())); + inner = SWCommandUtils.createMapper(s -> { + Number index = tabCompletes.indexOf(s); + return index.longValue() == -1 ? null : index; + }, (commandSender, s) -> tabCompletes); + } else { + throw new IllegalArgumentException("The parameter type '" + clazz.getTypeName() + "' is not supported by the StaticValue annotation"); + } + } + + @Override + public Object map(T sender, PreviousArguments previousArguments, String s) { + return inner.map(sender, previousArguments, s); + } + + @Override + public boolean validate(T sender, Object value, MessageSender messageSender) { + return inner.validate(sender, value, messageSender); + } + + @Override + public Collection tabCompletes(T sender, PreviousArguments previousArguments, String s) { + return inner.tabCompletes(sender, previousArguments, s); + } + } } @Retention(RetentionPolicy.RUNTIME) @@ -376,7 +465,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) - @CommandMetaData.ImplicitValidator(handler = ImplicitTypeValidators.ErrorMessageValidator.class, order = 1) + @CommandMetaData.ImplicitValidator(handler = ErrorMessage.Handler.class, order = 1) protected @interface ErrorMessage { /** * Error message to be displayed when the parameter is invalid. @@ -387,6 +476,25 @@ public abstract class AbstractSWCommand { * This is the short form for 'allowEmptyArrays'. */ boolean allowEAs() default true; + + class Handler implements AbstractValidator { + + private AbstractSWCommand.ErrorMessage errorMessage; + + public Handler(AbstractSWCommand.ErrorMessage errorMessage) { + this.errorMessage = errorMessage; + } + + @Override + public boolean validate(T sender, Object value, MessageSender messageSender) { + if (value == null) messageSender.send(errorMessage.value()); + if (!errorMessage.allowEAs() && value != null && value.getClass().isArray() && Array.getLength(value) == 0) { + messageSender.send(errorMessage.value()); + return false; + } + return value != null; + } + } } @Retention(RetentionPolicy.RUNTIME) @@ -406,7 +514,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) @CommandMetaData.Parameter({int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class}) - @CommandMetaData.ImplicitValidator(handler = ImplicitTypeValidators.MinValidator.class, order = 2) + @CommandMetaData.ImplicitValidator(handler = Min.Handler.class, order = 2) protected @interface Min { int intValue() default Integer.MIN_VALUE; long longValue() default Long.MIN_VALUE; @@ -414,12 +522,28 @@ public abstract class AbstractSWCommand { double doubleValue() default Double.MIN_VALUE; boolean inclusive() default true; + + class Handler implements AbstractValidator { + + private int value; + private Function comparator; + + public Handler(AbstractSWCommand.Min min, Class clazz) { + this.value = min.inclusive() ? 0 : 1; + this.comparator = createComparator("Min", clazz, min.intValue(), min.longValue(), min.floatValue(), min.doubleValue()); + } + + @Override + public boolean validate(T sender, Number value, MessageSender messageSender) { + return (comparator.apply(value).intValue()) >= this.value; + } + } } @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) @CommandMetaData.Parameter({int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class}) - @CommandMetaData.ImplicitValidator(handler = ImplicitTypeValidators.MaxValidator.class, order = 2) + @CommandMetaData.ImplicitValidator(handler = Max.Handler.class, order = 2) protected @interface Max { int intValue() default Integer.MAX_VALUE; long longValue() default Long.MAX_VALUE; @@ -427,5 +551,35 @@ public abstract class AbstractSWCommand { double doubleValue() default Double.MAX_VALUE; boolean inclusive() default true; + + class Handler implements AbstractValidator { + + private int value; + private Function comparator; + + public Handler(AbstractSWCommand.Max max, Class clazz) { + this.value = max.inclusive() ? 0 : -1; + this.comparator = createComparator("Max", clazz, max.intValue(), max.longValue(), max.floatValue(), max.doubleValue()); + } + + @Override + public boolean validate(T sender, Number value, MessageSender messageSender) { + return (comparator.apply(value).intValue()) <= this.value; + } + } + } + + private static Function createComparator(String type, Class clazz, int iValue, long lValue, float fValue, double dValue) { + if (clazz == int.class || clazz == Integer.class) { + return number -> Integer.compare(number.intValue(), iValue); + } else if (clazz == long.class || clazz == Long.class) { + return number -> Long.compare(number.longValue(), lValue); + } else if (clazz == float.class || clazz == Float.class) { + return number -> Float.compare(number.floatValue(), fValue); + } else if (clazz == double.class || clazz == Double.class) { + return number -> Double.compare(number.doubleValue(), dValue); + } else { + throw new IllegalArgumentException(type + " annotation is not supported for " + clazz); + } } } diff --git a/src/de/steamwar/command/AbstractTypeMapper.java b/src/de/steamwar/command/AbstractTypeMapper.java index 1c8550b..0363cd0 100644 --- a/src/de/steamwar/command/AbstractTypeMapper.java +++ b/src/de/steamwar/command/AbstractTypeMapper.java @@ -25,14 +25,29 @@ public interface AbstractTypeMapper extends AbstractValidator { /** * The CommandSender can be null! */ - // TODO: Change the 'previousArguments' to List or something like that - // TODO: Change T return value to Pair or something like that. This would make OptionalValue easier to implement as the Boolean would indicate if s should be consumed or not - T map(K sender, String[] previousArguments, String s); + @Deprecated + default T map(K sender, String[] previousArguments, String s) { + throw new IllegalArgumentException("This method is deprecated and should not be used anymore!"); + } + + /** + * The CommandSender can be null! + */ + default T map(K sender, PreviousArguments previousArguments, String s) { + return map(sender, previousArguments.userArgs, s); + } @Override default boolean validate(K sender, T value, MessageSender messageSender) { return true; } - Collection tabCompletes(K sender, String[] previousArguments, String s); + @Deprecated + default Collection tabCompletes(K sender, String[] previousArguments, String s) { + throw new IllegalArgumentException("This method is deprecated and should not be used anymore!"); + } + + default Collection tabCompletes(K sender, PreviousArguments previousArguments, String s) { + return tabCompletes(sender, previousArguments.userArgs, s); + } } diff --git a/src/de/steamwar/command/CommandFrameworkException.java b/src/de/steamwar/command/CommandFrameworkException.java index 53ee025..ea3d835 100644 --- a/src/de/steamwar/command/CommandFrameworkException.java +++ b/src/de/steamwar/command/CommandFrameworkException.java @@ -36,12 +36,6 @@ public class CommandFrameworkException extends RuntimeException { private String message; - static CommandFrameworkException commandGetExceptions(String type, Class clazzType, Executable executable, int index) { - return new CommandFrameworkException(throwable -> { - return CommandFrameworkException.class.getTypeName() + ": Error while getting " + type + " for " + clazzType.getTypeName() + " with parameter index " + index; - }, null, throwable -> Stream.empty(), executable.getDeclaringClass().getTypeName() + "." + executable.getName() + "(Unknown Source)"); - } - static CommandFrameworkException commandPartExceptions(String type, Throwable cause, String current, Class clazzType, Executable executable, int index) { return new CommandFrameworkException(e -> { return CommandFrameworkException.class.getTypeName() + ": Error while " + type + " (" + current + ") to type " + clazzType.getTypeName() + " with parameter index " + index; diff --git a/src/de/steamwar/command/CommandMetaData.java b/src/de/steamwar/command/CommandMetaData.java index c7f79c7..fdb1ea2 100644 --- a/src/de/steamwar/command/CommandMetaData.java +++ b/src/de/steamwar/command/CommandMetaData.java @@ -47,9 +47,34 @@ public @interface CommandMetaData { } /** - * This annotation can be used in conjunction with a class that implement {@link AbstractValidator} to - * create custom validator short hands for commands. The validator class itself should contain a constructor - * with two parameters the first is the annotation this annotation is used on and the second is of type {@link Class}. + * This annotation can be used in conjunction with a class that implements {@link AbstractTypeMapper} to + * create a custom type mapper for a parameter. The class must have one of two constructors with the following + * types: + *
    + *
  • Annotation this annotation annotates
  • + *
  • {@link Class}
  • + *
  • {@link AbstractTypeMapper}, optional, if not present only one per parameter
  • + *
  • {@link java.util.Map} with types {@link String} and {@link AbstractValidator}
  • + *
+ */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.ANNOTATION_TYPE) + @interface ImplicitTypeMapper { + /** + * The validator class that should be used. + */ + Class handler(); + } + + /** + * This annotation can be used in conjunction with a class that implements {@link AbstractValidator} to + * create a custom validator short hands for commands. The validator class must have one constructor with + * one of the following types: + *
    + *
  • Annotation this annotation annotates
  • + *
  • {@link Class}
  • + *
  • {@link java.util.Map} with types {@link String} and {@link AbstractValidator}
  • + *
*/ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) diff --git a/src/de/steamwar/command/CommandPart.java b/src/de/steamwar/command/CommandPart.java index 2969872..5a056d2 100644 --- a/src/de/steamwar/command/CommandPart.java +++ b/src/de/steamwar/command/CommandPart.java @@ -32,7 +32,8 @@ import java.util.function.Consumer; class CommandPart { - private static final String[] EMPTY_ARRAY = new String[0]; + private static final String[] EMPTY_STRING_ARRAY = new String[0]; + private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; @AllArgsConstructor private static class CheckArgumentResult { @@ -86,7 +87,7 @@ class CommandPart { if (varArgType != null) { Object array = Array.newInstance(varArgType, args.length - startIndex); for (int i = startIndex; i < args.length; i++) { - CheckArgumentResult validArgument = checkArgument(null, sender, args, i); + CheckArgumentResult validArgument = checkArgument(null, sender, args, current, i); if (!validArgument.success) throw new CommandParseException(); Array.set(array, i - startIndex, validArgument.value); } @@ -99,16 +100,16 @@ class CommandPart { return; } - CheckArgumentResult validArgument = checkArgument(errors, sender, args, startIndex); + CheckArgumentResult validArgument = checkArgument(errors, sender, args, current, startIndex); if (!validArgument.success && optional == null) { throw new CommandParseException(); } if (!validArgument.success) { if (!ignoreAsArgument) { if (!onlyUseIfNoneIsGiven) { - current.add(typeMapper.map(sender, EMPTY_ARRAY, optional)); + current.add(typeMapper.map(sender, new PreviousArguments(EMPTY_STRING_ARRAY, EMPTY_OBJECT_ARRAY), optional)); } else if (startIndex >= args.length) { - current.add(typeMapper.map(sender, EMPTY_ARRAY, optional)); + current.add(typeMapper.map(sender, new PreviousArguments(EMPTY_STRING_ARRAY, EMPTY_OBJECT_ARRAY), optional)); } else { throw new CommandParseException(); } @@ -126,13 +127,13 @@ class CommandPart { } } - public void generateTabComplete(List current, T sender, String[] args, int startIndex) { + public void generateTabComplete(List current, T sender, String[] args, List mappedArgs, int startIndex) { if (varArgType != null) { for (int i = startIndex; i < args.length - 1; i++) { - CheckArgumentResult validArgument = checkArgument((ignore) -> {}, sender, args, i); + CheckArgumentResult validArgument = checkArgument((ignore) -> {}, sender, args, mappedArgs, i); if (!validArgument.success) return; } - Collection strings = tabCompletes(sender, args, args.length - 1); + Collection strings = tabCompletes(sender, args, mappedArgs, args.length - 1); if (strings != null) { current.addAll(strings); } @@ -140,40 +141,43 @@ class CommandPart { } if (args.length - 1 > startIndex) { - CheckArgumentResult checkArgumentResult = checkArgument((ignore) -> {}, sender, args, startIndex); + CheckArgumentResult checkArgumentResult = checkArgument((ignore) -> {}, sender, args, mappedArgs, startIndex); if (checkArgumentResult.success && next != null) { - next.generateTabComplete(current, sender, args, startIndex + 1); + if (!ignoreAsArgument) { + mappedArgs.add(checkArgumentResult.value); + } + next.generateTabComplete(current, sender, args, mappedArgs, startIndex + 1); return; } if (optional != null && next != null) { - next.generateTabComplete(current, sender, args, startIndex); + next.generateTabComplete(current, sender, args, mappedArgs, startIndex); } return; } - Collection strings = tabCompletes(sender, args, startIndex); + Collection strings = tabCompletes(sender, args, mappedArgs, startIndex); if (strings != null) { current.addAll(strings); } if (optional != null && next != null) { - next.generateTabComplete(current, sender, args, startIndex); + next.generateTabComplete(current, sender, args, mappedArgs, startIndex); } } - private Collection tabCompletes(T sender, String[] args, int startIndex) { + private Collection tabCompletes(T sender, String[] args, List mappedArgs, int startIndex) { return TabCompletionCache.tabComplete(sender, typeMapper, command, () -> { try { - return typeMapper.tabCompletes(sender, Arrays.copyOf(args, startIndex), args[startIndex]); + return typeMapper.tabCompletes(sender, new PreviousArguments(Arrays.copyOf(args, startIndex), mappedArgs.toArray()), args[startIndex]); } catch (Throwable e) { throw CommandFrameworkException.commandPartExceptions("tabcompleting", e, args[startIndex], (varArgType != null ? varArgType : parameter.getType()), parameter.getDeclaringExecutable(), parameterIndex); } }); } - private CheckArgumentResult checkArgument(Consumer errors, T sender, String[] args, int index) { + private CheckArgumentResult checkArgument(Consumer errors, T sender, String[] args, List mappedArgs, int index) { Object value; try { - value = typeMapper.map(sender, Arrays.copyOf(args, index), args[index]); + value = typeMapper.map(sender, new PreviousArguments(Arrays.copyOf(args, index), mappedArgs.toArray()), args[index]); } catch (Exception e) { return new CheckArgumentResult(false, null); } diff --git a/src/de/steamwar/command/ImplicitTypeValidators.java b/src/de/steamwar/command/ImplicitTypeValidators.java deleted file mode 100644 index 7204ce5..0000000 --- a/src/de/steamwar/command/ImplicitTypeValidators.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * 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.command; - -import lombok.experimental.UtilityClass; - -import java.lang.reflect.Array; -import java.util.function.Function; - -@UtilityClass -public class ImplicitTypeValidators { - - public static class ErrorMessageValidator implements AbstractValidator { - - private AbstractSWCommand.ErrorMessage errorMessage; - - public ErrorMessageValidator(AbstractSWCommand.ErrorMessage errorMessage, Class type) { - this.errorMessage = errorMessage; - } - - @Override - public boolean validate(T sender, Object value, MessageSender messageSender) { - if (value == null) messageSender.send(errorMessage.value()); - if (!errorMessage.allowEAs() && value != null && value.getClass().isArray() && Array.getLength(value) == 0) { - messageSender.send(errorMessage.value()); - return false; - } - return value != null; - } - } - - public static class MinValidator implements AbstractValidator { - - private int value; - private Function comparator; - - public MinValidator(AbstractSWCommand.Min min, Class clazz) { - this.value = min.inclusive() ? 0 : 1; - - if (clazz == int.class || clazz == Integer.class) { - comparator = number -> Integer.compare(number.intValue(), min.intValue()); - } else if (clazz == long.class || clazz == Long.class) { - comparator = number -> Long.compare(number.longValue(), min.longValue()); - } else if (clazz == float.class || clazz == Float.class) { - comparator = number -> Float.compare(number.floatValue(), min.floatValue()); - } else if (clazz == double.class || clazz == Double.class) { - comparator = number -> Double.compare(number.doubleValue(), min.doubleValue()); - } else { - throw new IllegalArgumentException("Min annotation is not supported for " + clazz); - } - } - - @Override - public boolean validate(T sender, Number value, MessageSender messageSender) { - return (comparator.apply(value).intValue()) >= this.value; - } - } - - public static class MaxValidator implements AbstractValidator { - - private int value; - private Function comparator; - - public MaxValidator(AbstractSWCommand.Max max, Class clazz) { - this.value = max.inclusive() ? 0 : -1; - - if (clazz == int.class || clazz == Integer.class) { - comparator = number -> Integer.compare(number.intValue(), max.intValue()); - } else if (clazz == long.class || clazz == Long.class) { - comparator = number -> Long.compare(number.longValue(), max.longValue()); - } else if (clazz == float.class || clazz == Float.class) { - comparator = number -> Float.compare(number.floatValue(), max.floatValue()); - } else if (clazz == double.class || clazz == Double.class) { - comparator = number -> Double.compare(number.doubleValue(), max.doubleValue()); - } else { - throw new IllegalArgumentException("Max annotation is not supported for " + clazz); - } - } - - @Override - public boolean validate(T sender, Number value, MessageSender messageSender) { - return (comparator.apply(value).intValue()) <= this.value; - } - } -} diff --git a/src/de/steamwar/command/PreviousArguments.java b/src/de/steamwar/command/PreviousArguments.java new file mode 100644 index 0000000..24701c0 --- /dev/null +++ b/src/de/steamwar/command/PreviousArguments.java @@ -0,0 +1,39 @@ +/* + * 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.command; + +public class PreviousArguments { + + public final String[] userArgs; + public final Object[] mappedArgs; + + public PreviousArguments(String[] userArgs, Object[] mappedArgs) { + this.userArgs = userArgs; + this.mappedArgs = mappedArgs; + } + + public String getUserArg(int index) { + return userArgs[userArgs.length - index - 1]; + } + + public T getMappedArg(int index) { + return (T) mappedArgs[mappedArgs.length - index - 1]; + } +} diff --git a/src/de/steamwar/command/SWCommandUtils.java b/src/de/steamwar/command/SWCommandUtils.java index 2f16d7a..5bedbe6 100644 --- a/src/de/steamwar/command/SWCommandUtils.java +++ b/src/de/steamwar/command/SWCommandUtils.java @@ -82,49 +82,7 @@ public class SWCommandUtils { MAPPER_FUNCTIONS.put(alternativeClazz.getTypeName(), mapper); } - public static AbstractTypeMapper getTypeMapper(Parameter parameter, Map> localTypeMapper) { - Class clazz = parameter.getType(); - if (parameter.isVarArgs()) { - clazz = clazz.getComponentType(); - } - - AbstractSWCommand.ClassMapper classMapper = parameter.getAnnotation(AbstractSWCommand.ClassMapper.class); - AbstractSWCommand.Mapper mapper = parameter.getAnnotation(AbstractSWCommand.Mapper.class); - if (clazz.isEnum() && classMapper == null && mapper == null && !MAPPER_FUNCTIONS.containsKey(clazz.getTypeName()) && !localTypeMapper.containsKey(clazz.getTypeName())) { - return createEnumMapper((Class>) clazz); - } - - String name = clazz.getTypeName(); - if (classMapper != null) { - name = classMapper.value().getTypeName(); - } else if (mapper != null) { - name = mapper.value(); - } else { - AbstractSWCommand.StaticValue staticValue = parameter.getAnnotation(AbstractSWCommand.StaticValue.class); - if (staticValue != null) { - if (parameter.getType() == String.class) { - return createMapper(staticValue.value()); - } - if (staticValue.allowISE()) { - if ((parameter.getType() == boolean.class || parameter.getType() == Boolean.class)) { - List tabCompletes = new ArrayList<>(Arrays.asList(staticValue.value())); - Set falseValues = new HashSet<>(); - for (int i : staticValue.falseValues()) falseValues.add(i); - return createMapper(s -> { - int index = tabCompletes.indexOf(s); - return index == -1 ? null : !falseValues.contains(index); - }, (commandSender, s) -> tabCompletes); - } - if ((parameter.getType() == int.class || parameter.getType() == Integer.class || parameter.getType() == long.class || parameter.getType() == Long.class) && staticValue.value().length >= 2) { - List tabCompletes = new ArrayList<>(Arrays.asList(staticValue.value())); - return createMapper(s -> { - Number index = tabCompletes.indexOf(s); - return index.longValue() == -1 ? null : index; - }, (commandSender, s) -> tabCompletes); - } - } - } - } + public static AbstractTypeMapper getTypeMapper(String name, Map> localTypeMapper) { AbstractTypeMapper typeMapper = localTypeMapper.getOrDefault(name, (AbstractTypeMapper) MAPPER_FUNCTIONS.getOrDefault(name, null)); if (typeMapper == null) { throw new IllegalArgumentException("No mapper found for " + name); @@ -132,40 +90,24 @@ public class SWCommandUtils { return typeMapper; } - public static AbstractValidator getValidator(Parameter parameter, Map> localValidator) { + public static AbstractTypeMapper getTypeMapper(Parameter parameter, Map> localTypeMapper) { Class clazz = parameter.getType(); - - AbstractSWCommand.Validator validator = parameter.getAnnotation(AbstractSWCommand.Validator.class); - if (validator != null) { - if (validator.value() != null && !validator.value().isEmpty()) { - return getValidator(validator.value(), localValidator); - } - return getValidator(clazz.getTypeName(), localValidator); + if (parameter.isVarArgs()) { + clazz = clazz.getComponentType(); } - return null; + if (clazz.isEnum() && !MAPPER_FUNCTIONS.containsKey(clazz.getTypeName()) && !localTypeMapper.containsKey(clazz.getTypeName())) { + return createEnumMapper((Class>) clazz); + } + return getTypeMapper(clazz.getTypeName(), localTypeMapper); } - public static AbstractValidator getErrorMessage(Parameter parameter) { - AbstractSWCommand.ErrorMessage errorMessage = parameter.getAnnotation(AbstractSWCommand.ErrorMessage.class); - if (errorMessage != null) { - return (AbstractValidator) (sender, value, messageSender) -> { - if (value == null) messageSender.send(errorMessage.value()); - if (!errorMessage.allowEAs() && value != null && value.getClass().isArray() && Array.getLength(value) == 0) { - messageSender.send(errorMessage.value()); - return false; - } - return value != null; - }; - } - return null; - } - - private static AbstractValidator getValidator(String s, Map> localGuardChecker) { - AbstractValidator validator = localGuardChecker.getOrDefault(s, (AbstractValidator) VALIDATOR_FUNCTIONS.getOrDefault(s, null)); - if (validator == null) { + public static AbstractValidator getValidator(AbstractSWCommand.Validator validator, Class type, Map> localValidator) { + String s = validator.value() != null && !validator.value().isEmpty() ? validator.value() : type.getTypeName(); + AbstractValidator concreteValidator = localValidator.getOrDefault(s, (AbstractValidator) VALIDATOR_FUNCTIONS.getOrDefault(s, null)); + if (concreteValidator == null) { throw new IllegalArgumentException("No validator found for " + s); } - return validator; + return concreteValidator; } public static void addMapper(Class clazz, AbstractTypeMapper mapper) { diff --git a/src/de/steamwar/command/SubCommand.java b/src/de/steamwar/command/SubCommand.java index da3576e..961cf29 100644 --- a/src/de/steamwar/command/SubCommand.java +++ b/src/de/steamwar/command/SubCommand.java @@ -61,7 +61,10 @@ public class SubCommand { Parameter[] parameters = method.getParameters(); comparableValue = parameters[parameters.length - 1].isVarArgs() ? Integer.MAX_VALUE : -parameters.length; - validator = (AbstractValidator) SWCommandUtils.getValidator(parameters[0], localValidator); + AbstractSWCommand.Validator validator = parameters[0].getAnnotation(AbstractSWCommand.Validator.class); + if (validator != null) { + this.validator = (AbstractValidator) SWCommandUtils.getValidator(validator, parameters[0].getType(), localValidator); + } commandPart = generateCommandPart(abstractSWCommand, subCommand, parameters, localTypeMapper, localValidator); @@ -120,7 +123,7 @@ public class SubCommand { return null; } List list = new ArrayList<>(); - commandPart.generateTabComplete(list, sender, args, 0); + commandPart.generateTabComplete(list, sender, args, new ArrayList<>(), 0); return list; } @@ -141,8 +144,7 @@ public class SubCommand { } for (int i = 1; i < parameters.length; i++) { Parameter parameter = parameters[i]; - AbstractTypeMapper typeMapper = SWCommandUtils.getTypeMapper(parameter, localTypeMapper); - AbstractValidator validator = (AbstractValidator) SWCommandUtils.getValidator(parameter, localValidator); + AbstractTypeMapper typeMapper = handleImplicitTypeMapper(parameter, localTypeMapper); Class varArgType = parameter.isVarArgs() ? parameter.getType().getComponentType() : null; AbstractSWCommand.OptionalValue optionalValue = parameter.getAnnotation(AbstractSWCommand.OptionalValue.class); @@ -151,10 +153,7 @@ public class SubCommand { if (parameter.getAnnotation(AbstractSWCommand.Quotable.class) == null) { commandPart.addValidator((AbstractValidator) STRING_SPACE_FILTER); } - handleImplicitTypeValidator(parameter, commandPart, true); - commandPart.addValidator(validator); - commandPart.addValidator((AbstractValidator) SWCommandUtils.getErrorMessage(parameter)); - handleImplicitTypeValidator(parameter, commandPart, false); + handleImplicitTypeValidator(parameter, commandPart, localValidator); if (parameter.getAnnotation(AbstractSWCommand.AllowNull.class) == null) { commandPart.addValidator((AbstractValidator) NULL_FILTER); } @@ -169,29 +168,72 @@ public class SubCommand { return first; } - private static void handleImplicitTypeValidator(Parameter parameter, CommandPart commandPart, boolean beforeValidatorAnnotation) { + private static AbstractTypeMapper handleImplicitTypeMapper(Parameter parameter, Map> localTypeMapper) { + Class type = parameter.getType(); + if (parameter.isVarArgs()) { + type = type.getComponentType(); + } + + Annotation[] annotations = parameter.getAnnotations(); + Constructor sourceConstructor = null; + Annotation sourceAnnotation = null; + List> parentConstructors = new ArrayList<>(); + List parentAnnotations = new ArrayList<>(); + for (Annotation annotation : annotations) { + CommandMetaData.ImplicitTypeMapper implicitTypeMapper = annotation.annotationType().getAnnotation(CommandMetaData.ImplicitTypeMapper.class); + if (implicitTypeMapper == null) continue; + Class clazz = implicitTypeMapper.handler(); + if (!AbstractTypeMapper.class.isAssignableFrom(clazz)) continue; + Constructor[] constructors = clazz.getConstructors(); + if (constructors.length != 1) continue; + Constructor constructor = constructors[0]; + if (needsTypeMapper(constructor)) { + parentConstructors.add(constructor); + parentAnnotations.add(annotation); + } else { + if (sourceAnnotation != null) { + throw new IllegalArgumentException("Multiple source type mappers found for parameter " + parameter); + } + sourceConstructor = constructor; + sourceAnnotation = annotation; + } + } + + AbstractTypeMapper current; + if (sourceAnnotation != null) { + current = createInstance(sourceConstructor, sourceAnnotation, type, localTypeMapper); + } else { + current = (AbstractTypeMapper) SWCommandUtils.getTypeMapper(parameter, localTypeMapper); + } + for (int i = 0; i < parentConstructors.size(); i++) { + Constructor constructor = parentConstructors.get(i); + Annotation annotation = parentAnnotations.get(i); + current = createInstance(constructor, annotation, type, localTypeMapper, current); + } + return current; + } + + private static boolean needsTypeMapper(Constructor constructor) { + Class[] parameterTypes = constructor.getParameterTypes(); + for (Class parameterType : parameterTypes) { + if (AbstractTypeMapper.class.isAssignableFrom(parameterType)) { + return true; + } + } + return false; + } + + private static void handleImplicitTypeValidator(Parameter parameter, CommandPart commandPart, Map> localValidator) { Annotation[] annotations = parameter.getAnnotations(); Map>> validators = new HashMap<>(); for (Annotation annotation : annotations) { CommandMetaData.ImplicitValidator implicitValidator = annotation.annotationType().getAnnotation(CommandMetaData.ImplicitValidator.class); if (implicitValidator == null) continue; - if (beforeValidatorAnnotation && implicitValidator.order() >= 0) continue; - if (!beforeValidatorAnnotation && implicitValidator.order() <= 0) continue; Class clazz = implicitValidator.handler(); if (!AbstractValidator.class.isAssignableFrom(clazz)) continue; Constructor[] constructors = clazz.getConstructors(); if (constructors.length != 1) continue; - Constructor constructor = constructors[0]; - Class[] parameterTypes = constructor.getParameterTypes(); - if (parameterTypes.length != 2) continue; - AbstractValidator validator; - if (!annotation.annotationType().isAssignableFrom(parameterTypes[0])) continue; - if (!Class.class.isAssignableFrom(parameterTypes[1])) continue; - try { - validator = (AbstractValidator) constructor.newInstance(annotation, commandPart.getType()); - } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { - throw new RuntimeException(e); - } + AbstractValidator validator = createInstance(constructors[0], annotation, commandPart.getType(), localValidator); validators.computeIfAbsent(implicitValidator.order(), integer -> new ArrayList<>()).add(validator); } List keys = new ArrayList<>(validators.keySet()); @@ -204,42 +246,33 @@ public class SubCommand { } } + private static T createInstance(Constructor constructor, Object... parameter) { + Class[] types = constructor.getParameterTypes(); + List objects = new ArrayList<>(); + for (Class clazz : types) { + boolean found = false; + for (Object o : parameter) { + if (clazz.isAssignableFrom(o.getClass())) { + objects.add(o); + found = true; + break; + } + } + if (!found) { + throw new RuntimeException("Could not find type " + clazz + " for constructor " + constructor); + } + } + try { + return (T) constructor.newInstance(objects.toArray()); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + private static final AbstractValidator NULL_FILTER = (sender, value, messageSender) -> value != null; private static final AbstractValidator STRING_SPACE_FILTER = (sender, value, messageSender) -> { if (!(value instanceof String)) return true; return !((String) value).contains(" "); }; - - private static AbstractValidator createMinValidator(Class clazz, AbstractSWCommand.Min min) { - Function comparator; - if (clazz == int.class || clazz == Integer.class) { - comparator = number -> Integer.compare(number.intValue(), min.intValue()); - } else if (clazz == long.class || clazz == Long.class) { - comparator = number -> Long.compare(number.longValue(), min.longValue()); - } else if (clazz == float.class || clazz == Float.class) { - comparator = number -> Float.compare(number.floatValue(), min.floatValue()); - } else if (clazz == double.class || clazz == Double.class) { - comparator = number -> Double.compare(number.doubleValue(), min.doubleValue()); - } else { - throw new IllegalArgumentException("Min annotation is not supported for " + clazz); - } - return (sender, value, messageSender) -> comparator.apply((Number) value).intValue() >= (min.inclusive() ? 0 : 1); - } - - private static AbstractValidator createMaxValidator(Class clazz, AbstractSWCommand.Max max) { - Function comparator; - if (clazz == int.class || clazz == Integer.class) { - comparator = number -> Integer.compare(number.intValue(), max.intValue()); - } else if (clazz == long.class || clazz == Long.class) { - comparator = number -> Long.compare(number.longValue(), max.longValue()); - } else if (clazz == float.class || clazz == Float.class) { - comparator = number -> Float.compare(number.floatValue(), max.floatValue()); - } else if (clazz == double.class || clazz == Double.class) { - comparator = number -> Double.compare(number.doubleValue(), max.doubleValue()); - } else { - throw new IllegalArgumentException("Max annotation is not supported for " + clazz); - } - return (sender, value, messageSender) -> comparator.apply((Number) value).intValue() <= (max.inclusive() ? 0 : -1); - } } diff --git a/testsrc/de/steamwar/command/BetterExceptionCommand.java b/testsrc/de/steamwar/command/BetterExceptionCommand.java index d014186..feb47fc 100644 --- a/testsrc/de/steamwar/command/BetterExceptionCommand.java +++ b/testsrc/de/steamwar/command/BetterExceptionCommand.java @@ -39,7 +39,7 @@ public class BetterExceptionCommand extends TestSWCommand { public TestTypeMapper tabCompleteException() { return new TestTypeMapper() { @Override - public String map(String sender, String[] previousArguments, String s) { + public String map(String sender, PreviousArguments previousArguments, String s) { return null; } @@ -50,7 +50,7 @@ public class BetterExceptionCommand extends TestSWCommand { } @Override - public Collection tabCompletes(String sender, String[] previousArguments, String s) { + public Collection tabCompletes(String sender, PreviousArguments previousArguments, String s) { throw new SecurityException(); } }; diff --git a/testsrc/de/steamwar/command/CacheCommand.java b/testsrc/de/steamwar/command/CacheCommand.java index 1771bca..5057ffd 100644 --- a/testsrc/de/steamwar/command/CacheCommand.java +++ b/testsrc/de/steamwar/command/CacheCommand.java @@ -44,12 +44,12 @@ public class CacheCommand extends TestSWCommand { System.out.println("TypeMapper register"); return new TestTypeMapper() { @Override - public Integer map(String sender, String[] previousArguments, String s) { + public Integer map(String sender, PreviousArguments previousArguments, String s) { return Integer.parseInt(s); } @Override - public Collection tabCompletes(String sender, String[] previousArguments, String s) { + public Collection tabCompletes(String sender, PreviousArguments previousArguments, String s) { return Arrays.asList(count.getAndIncrement() + ""); } }; diff --git a/testsrc/de/steamwar/command/NullMapperCommand.java b/testsrc/de/steamwar/command/NullMapperCommand.java index e47eab7..30f4061 100644 --- a/testsrc/de/steamwar/command/NullMapperCommand.java +++ b/testsrc/de/steamwar/command/NullMapperCommand.java @@ -43,7 +43,7 @@ public class NullMapperCommand extends TestSWCommand { public TestTypeMapper typeMapper() { return new TestTypeMapper() { @Override - public String map(String sender, String[] previousArguments, String s) { + public String map(String sender, PreviousArguments previousArguments, String s) { if (s.equals("Hello World")) { return null; } @@ -51,7 +51,7 @@ public class NullMapperCommand extends TestSWCommand { } @Override - public Collection tabCompletes(String sender, String[] previousArguments, String s) { + public Collection tabCompletes(String sender, PreviousArguments previousArguments, String s) { return null; } }; From c94ee5c55f25caa053eae3a8b56fda7b4d707f14 Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Sun, 15 Jan 2023 17:55:17 +0100 Subject: [PATCH 44/85] Add Copyright --- .../steamwar/sql/EffectiveSchematicNode.java | 267 ------------------ src/de/steamwar/sql/SchematicNode.java | 184 ++++++++++-- 2 files changed, 163 insertions(+), 288 deletions(-) delete mode 100644 src/de/steamwar/sql/EffectiveSchematicNode.java diff --git a/src/de/steamwar/sql/EffectiveSchematicNode.java b/src/de/steamwar/sql/EffectiveSchematicNode.java deleted file mode 100644 index 587034e..0000000 --- a/src/de/steamwar/sql/EffectiveSchematicNode.java +++ /dev/null @@ -1,267 +0,0 @@ -/* - This file is a part of the SteamWar software. - - Copyright (C) 2023 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.*; -import java.util.stream.Collectors; - -import static de.steamwar.sql.SchematicNode.TAB_CACHE; - -public class EffectiveSchematicNode { - - private static final Table table = new Table<>(EffectiveSchematicNode.class, "EffectiveSchematicNode"); - - private static final SelectStatement all = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? ORDER BY NodeName"); - - private static final SelectStatement list_null = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND ParentNode is null ORDER BY NodeName"); - private static final SelectStatement list = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND ParentNode = ? ORDER BY NodeName"); - private static final SelectStatement byParentName = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND ParentNode = ? AND NodeName = ? ORDER BY NodeName"); - private static final SelectStatement byParentName_null = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND ParentNode is null AND NodeName = ? ORDER BY NodeName"); - private static final SelectStatement schematicAccessibleForUser = new SelectStatement<>(table, "SELECT COUNT(DISTINCT NodeId) FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND NodeId = ?"); - private static final SelectStatement accessibleByUserTypeParent = new SelectStatement<>(table, "WITH RECURSIVE RSN AS (SELECT NodeId, ParentNode FROM EffectiveSchematicNode WHERE NodeType = ? AND EffectiveOwner = ? UNION SELECT SN.NodeId, SN.ParentNode FROM RSN, EffectiveSchematicNode SN WHERE SN.NodeId = RSN.ParentNode AND EffectiveOwner = ?) SELECT SN.NodeId, SN.NodeOwner, ? AS EffectiveOwner, SN.NodeName, RSN.ParentNode, SN.LastUpdate, SN.NodeItem, SN.NodeType, SN.NodeRank FROM RSN INNER JOIN SchematicNode SN ON RSN.NodeId = SN.NodeId"); - private static final SelectStatement accessibleByUserTypeInParent = new SelectStatement<>(table, "WITH RECURSIVE RSN AS (SELECT NodeId, ParentNode FROM EffectiveSchematicNode WHERE NodeType = ? AND EffectiveOwner = ? UNION SELECT SN.NodeId, SN.ParentNode FROM RSN, EffectiveSchematicNode SN WHERE SN.NodeId = RSN.ParentNode AND EffectiveOwner = ?) SELECT SN.NodeId, SN.NodeOwner, ? AS EffectiveOwner, SN.NodeName, RSN.ParentNode, SN.LastUpdate, SN.NodeItem, SN.NodeType, SN.NodeRank FROM RSN INNER JOIN SchematicNode SN ON RSN.NodeId = SN.NodeId WHERE RSN.ParentNode = ?"); - private static final SelectStatement accessibleByUserTypeInParent_null = new SelectStatement<>(table, "WITH RECURSIVE RSN AS (SELECT NodeId, ParentNode FROM EffectiveSchematicNode WHERE NodeType = ? AND EffectiveOwner = ? UNION SELECT SN.NodeId, SN.ParentNode FROM RSN, EffectiveSchematicNode SN WHERE SN.NodeId = RSN.ParentNode AND EffectiveOwner = ?) SELECT SN.NodeId, SN.NodeOwner, ? AS EffectiveOwner, SN.NodeName, RSN.ParentNode, SN.LastUpdate, SN.NodeItem, SN.NodeType, SN.NodeRank FROM RSN INNER JOIN SchematicNode SN ON RSN.NodeId = SN.NodeId WHERE RSN.ParentNode is null"); - private static final SelectStatement accessibleByUserType = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND NodeType = ?"); - private static final SelectStatement byIdAndUser = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND NodeId = ?"); - private static final SelectStatement allParentsOfNode = new SelectStatement<>(table, "WITH RECURSIVE R AS (SELECT NodeId, ParentNode FROM EffectiveSchematicNode WHERE NodeId = ? AND EffectiveOwner = ? UNION SELECT E.NodeId, E.ParentNode FROM R, EffectiveSchematicNode E WHERE R.ParentNode = E.NodeId AND E.EffectiveOwner = ?) SELECT SN.NodeId, SN.NodeOwner, ? AS EffectiveOwner, SN.NodeName, R.ParentNode, SN.LastUpdate, SN.NodeItem, SN.NodeType, SN.NodeRank FROM R INNER JOIN SchematicNode SN ON SN.NodeId = R.NodeId"); - - public static List getAll(SteamwarUser user) { - return all.listSelect(user); - } - - public static Map> getAllMap(SteamwarUser user) { - return map(all.listSelect(user)); - } - - public static List list(SteamwarUser user, Integer schematicId) { - if(schematicId == null || schematicId == 0) { - return list_null.listSelect(user); - } else { - return list.listSelect(user, schematicId); - } - } - - public static Optional byParentName(SteamwarUser user, Integer schematicId, String name) { - if(schematicId == null || schematicId == 0) { - return Optional.ofNullable(byParentName_null.select(user, name)); - } else { - return Optional.ofNullable(byParentName.select(user, schematicId, name)); - } - } - - public static List accessibleByUserType(SteamwarUser user, SchematicType type) { - return accessibleByUserType.listSelect(type, user, user, user); - } - - public static Map> accessibleByUserTypeMap(SteamwarUser user, SchematicType type) { - return map(accessibleByUserTypeParent.listSelect(type, user, user, user)); - } - - public static boolean schematicAccessibleForUser(SteamwarUser user, Integer schematicId) { - return schematicAccessibleForUser.select(user, schematicId) != null; - } - - public static List accessibleByUserTypeParent(SteamwarUser user, SchematicType type, Integer parentId) { - if(parentId == null || parentId == 0) { - return accessibleByUserTypeInParent_null.listSelect(type, user, user, user); - } else { - return accessibleByUserTypeInParent.listSelect(type, user, user, user, parentId); - } - } - - public static Optional byIdAndUser(SteamwarUser user, Integer id) { - return Optional.ofNullable(byIdAndUser.select(user, id)); - } - - public static List parentsOfNode(SteamwarUser user, Integer id) { - return allParentsOfNode.listSelect(id, user, user, user).stream().filter(n -> n.getNodeId() != id).collect(Collectors.toList()); - } - - private static Map> map(List in) { - Map> map = new HashMap<>(); - for (EffectiveSchematicNode effectiveSchematicNode : in) { - map.computeIfAbsent(effectiveSchematicNode.getNodeParent().orElse(0), k -> new ArrayList<>()).add(effectiveSchematicNode); - } - return map; - } - - @Getter - @Field - private final int nodeId; - @Getter - @Field - private final int nodeOwner; - @Getter - @Field - private final int effectiveOwner; - @Getter - @Field - private final String nodeName; - @Field(nullable = true) - private final Integer parentNode; - @Getter - @Field - private final Timestamp lastUpdate; - @Field - private final String nodeItem; - @Field(nullable = true) - private final SchematicType nodeType; - @Field - private final int nodeRank; - - private String brCache = null; - - public EffectiveSchematicNode(int nodeId, int nodeOwner, int effectiveOwner, String nodeName, Integer nodeParent, Timestamp lastUpdate, String nodeItem, SchematicType nodeType, int nodeRank) { - this.nodeId = nodeId; - this.nodeOwner = nodeOwner; - this.effectiveOwner = effectiveOwner; - this.nodeName = nodeName; - this.parentNode = nodeParent; - this.lastUpdate = lastUpdate; - this.nodeItem = nodeItem; - this.nodeType = nodeType; - this.nodeRank = nodeRank; - } - - public boolean isDir() { - return nodeType == null; - } - - public Optional getNodeParent() { - return Optional.ofNullable(parentNode); - } - - public Optional getParentNode() { - return byIdAndUser(SteamwarUser.get(effectiveOwner), parentNode); - } - - public SchematicType getNodeType() { - if(isDir()) { - throw new IllegalStateException("This node is a directory"); - } - return nodeType; - } - - public int getNodeRank() { - if(isDir()) { - throw new IllegalStateException("This node is a directory"); - } - return nodeRank; - } - - public SchematicNode toSchematicNode() { - return SchematicNode.getSchematicNode(nodeId); - } - - public boolean isWritable() { - return effectiveOwner == nodeOwner; - } - - public String getNodeItem() { - if (nodeItem.isEmpty()) { - return isDir() ? "CHEST" : "CAULDRON_ITEM"; - } - return nodeItem; - } - - 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); - Optional pa = EffectiveSchematicNode.getNodeFromPath(user, preTab); - if (!pa.isPresent()) return Collections.emptyList(); - List nodes = EffectiveSchematicNode.list(user, pa.get().getNodeId()); - nodes.forEach(node -> list.add((sws ? "/" : "") + node.generateBreadcrumbs())); - } else { - List nodes = EffectiveSchematicNode.list(user, 0); - nodes.forEach(node -> list.add((sws ? "/" : "") + node.getNodeName() + (node.isDir() ? "/" : ""))); - } - list.remove("//copy"); - TAB_CACHE.computeIfAbsent(user.getId(), integer -> new HashMap<>()).putIfAbsent(cacheKey, list); - return list; - } - - public String generateBreadcrumbs() { - if(brCache == null) { - brCache = generateBreadcrumbs("/"); - } - return brCache; - } - - public String generateBreadcrumbs(String split) { - StringBuilder builder = new StringBuilder(getNodeName()); - Optional currentNode = Optional.of(this); - if(currentNode.map(EffectiveSchematicNode::isDir).orElse(false)) { - builder.append("/"); - } - while (currentNode.isPresent()) { - currentNode = currentNode.flatMap(EffectiveSchematicNode::getNodeParent).flatMap(integer -> byIdAndUser(SteamwarUser.get(effectiveOwner), integer)); - currentNode.ifPresent(node -> builder.insert(0, '/').insert(0, node.getNodeName())); - } - return builder.toString(); - } - - public static Optional getNodeFromPath(SteamwarUser user, String s) { - if (s.startsWith("/")) { - s = s.substring(1); - } - if (s.endsWith("/")) { - s = s.substring(0, s.length() - 1); - } - if (s.isEmpty()) { - return Optional.empty(); - } - if (s.contains("/")) { - String[] layers = s.split("/"); - Optional currentNode = EffectiveSchematicNode.byParentName(user, 0, layers[0]); - for (int i = 1; i < layers.length; i++) { - int finalI = i; - Optional node = currentNode.flatMap(effectiveSchematicNode -> EffectiveSchematicNode.byParentName(user, effectiveSchematicNode.getNodeId(), layers[finalI])); - if (!node.isPresent()) { - return Optional.empty(); - } else { - currentNode = node; - if (!currentNode.map(EffectiveSchematicNode::isDir).orElse(false) && i != layers.length - 1) { - return Optional.empty(); - } - } - } - return currentNode; - } else { - return EffectiveSchematicNode.byParentName(user, 0, s); - } - } -} diff --git a/src/de/steamwar/sql/SchematicNode.java b/src/de/steamwar/sql/SchematicNode.java index 0299e77..4c6d826 100644 --- a/src/de/steamwar/sql/SchematicNode.java +++ b/src/de/steamwar/sql/SchematicNode.java @@ -21,6 +21,7 @@ package de.steamwar.sql; import de.steamwar.sql.internal.*; import lombok.AccessLevel; +import lombok.Getter; import lombok.Setter; import java.io.ByteArrayOutputStream; @@ -28,7 +29,6 @@ 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; @@ -45,7 +45,7 @@ public class SchematicNode { 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 "; + return "SELECT " + Arrays.stream(fields).map(s -> itemPrefix + s).collect(Collectors.joining(", ")) + ", NodeOwner AS EffectiveOwner FROM SchematicNode "; } private static final Table table = new Table<>(SchematicNode.class); @@ -60,11 +60,19 @@ public class SchematicNode { 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 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 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"); + private static final SelectStatement all = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? ORDER BY NodeName"); + private static final SelectStatement list_null = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND ParentNode is null ORDER BY NodeName"); + private static final SelectStatement list = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND ParentNode = ? ORDER BY NodeName"); + private static final SelectStatement byParentName = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND ParentNode = ? AND NodeName = ? ORDER BY NodeName"); + private static final SelectStatement byParentName_null = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND ParentNode is null AND NodeName = ? ORDER BY NodeName"); + private static final SelectStatement schematicAccessibleForUser = new SelectStatement<>(table, "SELECT COUNT(DISTINCT NodeId) FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND NodeId = ?"); + private static final SelectStatement accessibleByUserTypeInParent = new SelectStatement<>(table, "WITH RECURSIVE RSN AS (SELECT NodeId, ParentNode FROM EffectiveSchematicNode WHERE NodeType = ? AND EffectiveOwner = ? UNION SELECT SN.NodeId, SN.ParentNode FROM RSN, EffectiveSchematicNode SN WHERE SN.NodeId = RSN.ParentNode AND EffectiveOwner = ?) SELECT SN.NodeId, SN.NodeOwner, ? AS EffectiveOwner, SN.NodeName, RSN.ParentNode, SN.LastUpdate, SN.NodeItem, SN.NodeType, SN.NodeRank FROM RSN INNER JOIN SchematicNode SN ON RSN.NodeId = SN.NodeId WHERE RSN.ParentNode = ?"); + private static final SelectStatement accessibleByUserTypeInParent_null = new SelectStatement<>(table, "WITH RECURSIVE RSN AS (SELECT NodeId, ParentNode FROM EffectiveSchematicNode WHERE NodeType = ? AND EffectiveOwner = ? UNION SELECT SN.NodeId, SN.ParentNode FROM RSN, EffectiveSchematicNode SN WHERE SN.NodeId = RSN.ParentNode AND EffectiveOwner = ?) SELECT SN.NodeId, SN.NodeOwner, ? AS EffectiveOwner, SN.NodeName, RSN.ParentNode, SN.LastUpdate, SN.NodeItem, SN.NodeType, SN.NodeRank FROM RSN INNER JOIN SchematicNode SN ON RSN.NodeId = SN.NodeId WHERE RSN.ParentNode is null"); + private static final SelectStatement accessibleByUserType = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND NodeType = ?"); + private static final SelectStatement byIdAndUser = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND NodeId = ?"); + private static final SelectStatement allParentsOfNode = new SelectStatement<>(table, "WITH RECURSIVE R AS (SELECT NodeId, ParentNode FROM EffectiveSchematicNode WHERE NodeId = ? AND EffectiveOwner = ? UNION SELECT E.NodeId, E.ParentNode FROM R, EffectiveSchematicNode E WHERE R.ParentNode = E.NodeId AND E.EffectiveOwner = ?) SELECT SN.NodeId, SN.NodeOwner, ? AS EffectiveOwner, SN.NodeName, R.ParentNode, SN.LastUpdate, SN.NodeItem, SN.NodeType, SN.NodeRank FROM R INNER JOIN SchematicNode SN ON SN.NodeId = R.NodeId"); static { NodeMember.init(); @@ -74,6 +82,9 @@ public class SchematicNode { private final int nodeId; @Field(keys = {"OwnerNameParent"}) private final int nodeOwner; + @Field + @Getter + private final int effectiveOwner; @Field(keys = {"OwnerNameParent"}) private String nodeName; @Field(keys = {"OwnerNameParent"}, nullable = true) @@ -94,9 +105,12 @@ public class SchematicNode { @Field(def = "1") private boolean nodeFormat; + private String brCache; + public SchematicNode( int nodeId, int nodeOwner, + int effectiveOwner, String nodeName, Integer parentNode, Timestamp lastUpdate, @@ -109,6 +123,7 @@ public class SchematicNode { ) { this.nodeId = nodeId; this.nodeOwner = nodeOwner; + this.effectiveOwner = effectiveOwner; this.nodeName = nodeName; this.parentNode = parentNode; this.nodeItem = nodeItem; @@ -120,6 +135,66 @@ public class SchematicNode { this.nodeFormat = nodeFormat; } + public static List getAll(SteamwarUser user) { + return all.listSelect(user); + } + + public static Map> getAllMap(SteamwarUser user) { + return map(getAll(user)); + } + + public static List list(SteamwarUser user, Integer schematicId) { + if(schematicId == null || schematicId == 0) { + return list_null.listSelect(user); + } else { + return list.listSelect(user, schematicId); + } + } + + public static SchematicNode byParentName(SteamwarUser user, Integer schematicId, String name) { + if(schematicId == null || schematicId == 0) { + return byParentName_null.select(user, name); + } else { + return byParentName.select(user, schematicId, name); + } + } + + public static List accessibleByUserType(SteamwarUser user, SchematicType type) { + return accessibleByUserType.listSelect(type, user, user, user); + } + + public static Map> accessibleByUserTypeMap(SteamwarUser user, SchematicType type) { + return map(accessibleByUserType(user, type)); + } + + public static boolean schematicAccessibleForUser(SteamwarUser user, Integer schematicId) { + return schematicAccessibleForUser.select(user, schematicId) != null; + } + + public static List accessibleByUserTypeParent(SteamwarUser user, SchematicType type, Integer parentId) { + if(parentId == null || parentId == 0) { + return accessibleByUserTypeInParent_null.listSelect(type, user, user, user); + } else { + return accessibleByUserTypeInParent.listSelect(type, user, user, user, parentId); + } + } + + public static SchematicNode byIdAndUser(SteamwarUser user, Integer id) { + return byIdAndUser.select(user, id); + } + + public static List parentsOfNode(SteamwarUser user, Integer id) { + return allParentsOfNode.listSelect(id, user, user, user).stream().filter(n -> n.getId() != id).collect(Collectors.toList()); + } + + private static Map> map(List in) { + Map> map = new HashMap<>(); + for (SchematicNode effectiveSchematicNode : in) { + map.computeIfAbsent(effectiveSchematicNode.getOptionalParent().orElse(0), k -> new ArrayList<>()).add(effectiveSchematicNode); + } + return map; + } + public static SchematicNode createSchematic(int owner, String name, Integer parent) { return createSchematicNode(owner, name, parent, SchematicType.Normal.toDB(), ""); } @@ -188,14 +263,12 @@ public class SchematicNode { return byId.select(id); } - @Deprecated public static List getAccessibleSchematicsOfTypeInParent(int owner, String schemType, Integer parent) { - return EffectiveSchematicNode.accessibleByUserTypeParent(SteamwarUser.get(owner), SchematicType.fromDB(schemType), parent).stream().map(EffectiveSchematicNode::toSchematicNode).collect(Collectors.toList()); + return accessibleByUserTypeParent(SteamwarUser.get(owner), SchematicType.fromDB(schemType), parent); } - @Deprecated public static List getAllAccessibleSchematicsOfType(int user, String schemType) { - return EffectiveSchematicNode.accessibleByUserType(SteamwarUser.get(user), SchematicType.fromDB(schemType)).stream().map(EffectiveSchematicNode::toSchematicNode).collect(Collectors.toList()); + return accessibleByUserType(SteamwarUser.get(user), SchematicType.fromDB(schemType)); } public static List getAllSchematicsOfType(int owner, String schemType) { @@ -227,12 +300,12 @@ public class SchematicNode { @Deprecated public static List getSchematicsAccessibleByUser(int user, Integer parent) { - return EffectiveSchematicNode.list(SteamwarUser.get(user), parent).stream().map(EffectiveSchematicNode::toSchematicNode).collect(Collectors.toList()); + return list(SteamwarUser.get(user), parent); } @Deprecated public static List getAllSchematicsAccessibleByUser(int user) { - return EffectiveSchematicNode.getAll(SteamwarUser.get(user)).stream().map(EffectiveSchematicNode::toSchematicNode).collect(Collectors.toList()); + return getAll(SteamwarUser.get(user)); } public static List getAllParentsOfNode(SchematicNode node) { @@ -243,9 +316,35 @@ public class SchematicNode { return allParentsOfNode.listSelect(node); } - @Deprecated public static SchematicNode getNodeFromPath(SteamwarUser user, String s) { - return EffectiveSchematicNode.getNodeFromPath(user, s).map(EffectiveSchematicNode::toSchematicNode).orElse(null); + if (s.startsWith("/")) { + s = s.substring(1); + } + if (s.endsWith("/")) { + s = s.substring(0, s.length() - 1); + } + if (s.isEmpty()) { + return null; + } + if (s.contains("/")) { + String[] layers = s.split("/"); + Optional currentNode = Optional.ofNullable(SchematicNode.byParentName(user, 0, layers[0])); + for (int i = 1; i < layers.length; i++) { + int finalI = i; + Optional node = currentNode.map(effectiveSchematicNode -> SchematicNode.byParentName(user, effectiveSchematicNode.getId(), layers[finalI])); + if (!node.isPresent()) { + return null; + } else { + currentNode = node; + if (!currentNode.map(SchematicNode::isDir).orElse(false) && i != layers.length - 1) { + return null; + } + } + } + return currentNode.orElse(null); + } else { + return SchematicNode.byParentName(user, 0, s); + } } public static List filterSchems(int user, Predicate filter) { @@ -262,11 +361,6 @@ public class SchematicNode { return finalList; } - @Deprecated - public static Integer countNodes() { - return -1; - } - public int getId() { return nodeId; } @@ -288,6 +382,10 @@ public class SchematicNode { return parentNode; } + public Optional getOptionalParent() { + return Optional.ofNullable(parentNode); + } + public void setParent(Integer parent) { this.parentNode = parent; updateDB(); @@ -424,12 +522,34 @@ public class SchematicNode { return ((SchematicNode) obj).getId() == nodeId; } + @Deprecated public String generateBreadcrumbs(SteamwarUser user) { - return EffectiveSchematicNode.byIdAndUser(user, nodeId).map(EffectiveSchematicNode::generateBreadcrumbs).orElse("/"); + return generateBreadcrumbs(); } + @Deprecated public String generateBreadcrumbs(String split, SteamwarUser user) { - return EffectiveSchematicNode.byIdAndUser(user, nodeId).map(node -> node.generateBreadcrumbs(split)).orElse("/"); + return generateBreadcrumbs(split); + } + + public String generateBreadcrumbs() { + if(brCache == null) { + brCache = generateBreadcrumbs("/"); + } + return brCache; + } + + public String generateBreadcrumbs(String split) { + StringBuilder builder = new StringBuilder(getName()); + Optional currentNode = Optional.of(this); + if(currentNode.map(SchematicNode::isDir).orElse(false)) { + builder.append(split); + } + while (currentNode.isPresent()) { + currentNode = currentNode.flatMap(SchematicNode::getOptionalParent).map(integer -> byIdAndUser(SteamwarUser.get(effectiveOwner), integer)); + currentNode.ifPresent(node -> builder.insert(0, split).insert(0, node.getName())); + } + return builder.toString(); } private static final List FORBIDDEN_NAMES = Collections.unmodifiableList(Arrays.asList("public")); @@ -458,7 +578,29 @@ public class SchematicNode { @Deprecated public static List getNodeTabcomplete(SteamwarUser user, String s) { - return EffectiveSchematicNode.getNodeTabcomplete(user, 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.list(user, pa.getId()); + nodes.forEach(node -> list.add((sws ? "/" : "") + node.generateBreadcrumbs())); + } else { + List nodes = SchematicNode.list(user, 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() { From d23f2563f69a973c35709a76c3b201704d6f6f39 Mon Sep 17 00:00:00 2001 From: yoyosource Date: Sun, 15 Jan 2023 18:29:14 +0100 Subject: [PATCH 45/85] Add test for new system --- .../command/PreviousArgumentCommand.java | 54 ++++++++++++++++++ .../command/PreviousArgumentCommandTest.java | 56 +++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 testsrc/de/steamwar/command/PreviousArgumentCommand.java create mode 100644 testsrc/de/steamwar/command/PreviousArgumentCommandTest.java diff --git a/testsrc/de/steamwar/command/PreviousArgumentCommand.java b/testsrc/de/steamwar/command/PreviousArgumentCommand.java new file mode 100644 index 0000000..454283c --- /dev/null +++ b/testsrc/de/steamwar/command/PreviousArgumentCommand.java @@ -0,0 +1,54 @@ +/* + * 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.command; + +import de.steamwar.command.dto.ExecutionIdentifier; +import de.steamwar.command.dto.TestSWCommand; +import de.steamwar.command.dto.TestTypeMapper; + +import java.util.Arrays; +import java.util.Collection; + +public class PreviousArgumentCommand extends TestSWCommand { + + public PreviousArgumentCommand() { + super("previous"); + } + + @Register + public void genericCommand(String s, int i, String l) { + throw new ExecutionIdentifier(l); + } + + @ClassMapper(value = String.class, local = true) + public TestTypeMapper typeMapper() { + return new TestTypeMapper() { + @Override + public String map(String sender, PreviousArguments previousArguments, String s) { + return "RunTypeMapper_" + previousArguments.getMappedArg(0) + "_" + previousArguments.getMappedArg(0).getClass().getSimpleName(); + } + + @Override + public Collection tabCompletes(String sender, PreviousArguments previousArguments, String s) { + return Arrays.asList(previousArguments.getMappedArg(0) + ""); + } + }; + } +} diff --git a/testsrc/de/steamwar/command/PreviousArgumentCommandTest.java b/testsrc/de/steamwar/command/PreviousArgumentCommandTest.java new file mode 100644 index 0000000..941b1c3 --- /dev/null +++ b/testsrc/de/steamwar/command/PreviousArgumentCommandTest.java @@ -0,0 +1,56 @@ +/* + * 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.command; + +import de.steamwar.command.dto.ExecutionIdentifier; +import org.junit.Test; + +import java.util.List; + +import static de.steamwar.AssertionUtils.assertCMDFramework; +import static de.steamwar.AssertionUtils.assertTabCompletes; + +public class PreviousArgumentCommandTest { + + @Test + public void testPrevArg1() { + PreviousArgumentCommand command = new PreviousArgumentCommand(); + List strings = command.tabComplete("", "", new String[]{"1", ""}); + assertTabCompletes(strings, "1"); + } + + @Test + public void testPrevArg2() { + PreviousArgumentCommand command = new PreviousArgumentCommand(); + List strings = command.tabComplete("", "", new String[]{"2", ""}); + assertTabCompletes(strings, "2"); + } + + @Test + public void testPrevArgExecute() { + PreviousArgumentCommand command = new PreviousArgumentCommand(); + try { + command.execute("", "", new String[]{"2", "2"}); + assert false; + } catch (Exception e) { + assertCMDFramework(e, ExecutionIdentifier.class, "RunTypeMapper_2_Integer"); + } + } +} From 9276f79d93c1f3028871aa57e6c1c80d31d1d475 Mon Sep 17 00:00:00 2001 From: Lixfel Date: Sun, 15 Jan 2023 20:07:01 +0100 Subject: [PATCH 46/85] Utilize null safe equals --- src/de/steamwar/sql/internal/SelectStatement.java | 2 +- src/de/steamwar/sql/internal/Statement.java | 3 +++ src/de/steamwar/sql/internal/Table.java | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/de/steamwar/sql/internal/SelectStatement.java b/src/de/steamwar/sql/internal/SelectStatement.java index e17dcb7..0b39160 100644 --- a/src/de/steamwar/sql/internal/SelectStatement.java +++ b/src/de/steamwar/sql/internal/SelectStatement.java @@ -31,7 +31,7 @@ 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 "))); + this(table, "SELECT " + Arrays.stream(table.fields).map(f -> f.identifier).collect(Collectors.joining(", ")) + " FROM " + table.name + " WHERE " + Arrays.stream(kfields).map(f -> f + Statement.NULL_SAFE_EQUALS + "?").collect(Collectors.joining(" AND "))); } public SelectStatement(Table table, String sql) { diff --git a/src/de/steamwar/sql/internal/Statement.java b/src/de/steamwar/sql/internal/Statement.java index 6c44332..ff39a09 100644 --- a/src/de/steamwar/sql/internal/Statement.java +++ b/src/de/steamwar/sql/internal/Statement.java @@ -41,6 +41,7 @@ public class Statement implements AutoCloseable { static final Consumer> schemaCreator; static final String ON_DUPLICATE_KEY; static final UnaryOperator upsertWrapper; + public static final String NULL_SAFE_EQUALS; private static final boolean MYSQL_MODE; private static final boolean PRODUCTION_DATABASE; @@ -73,6 +74,7 @@ public class Statement implements AutoCloseable { schemaCreator = table -> {}; ON_DUPLICATE_KEY = " ON DUPLICATE KEY UPDATE "; upsertWrapper = f -> f + " = VALUES(" + f + ")"; + NULL_SAFE_EQUALS = " <=> "; } else { Connection connection; @@ -89,6 +91,7 @@ public class Statement implements AutoCloseable { schemaCreator = Table::ensureExistanceInSqlite; ON_DUPLICATE_KEY = " ON CONFLICT DO UPDATE SET "; upsertWrapper = f -> f + " = " + f; + NULL_SAFE_EQUALS = " IS "; } } diff --git a/src/de/steamwar/sql/internal/Table.java b/src/de/steamwar/sql/internal/Table.java index cbad10a..3040642 100644 --- a/src/de/steamwar/sql/internal/Table.java +++ b/src/de/steamwar/sql/internal/Table.java @@ -75,7 +75,7 @@ public class Table { } 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 "))); + return new Statement("UPDATE " + name + " SET " + Arrays.stream(fields).map(f -> f + " = ?").collect(Collectors.joining(", ")) + " WHERE " + Arrays.stream(kfields).map(f -> f + Statement.NULL_SAFE_EQUALS + "?").collect(Collectors.joining(" AND "))); } public Statement insert(String name) { @@ -100,7 +100,7 @@ public class Table { } public Statement deleteFields(String... kfields) { - return new Statement("DELETE FROM " + name + " WHERE " + Arrays.stream(kfields).map(f -> f + " = ?").collect(Collectors.joining(" AND "))); + return new Statement("DELETE FROM " + name + " WHERE " + Arrays.stream(kfields).map(f -> f + Statement.NULL_SAFE_EQUALS + "?").collect(Collectors.joining(" AND "))); } void ensureExistanceInSqlite() { From 6a6483235c50c436a82d6471789dc2e5998cd4b1 Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Sun, 15 Jan 2023 20:11:41 +0100 Subject: [PATCH 47/85] Add Copyright --- src/de/steamwar/sql/SchematicNode.java | 50 ++++++++++---------------- 1 file changed, 18 insertions(+), 32 deletions(-) diff --git a/src/de/steamwar/sql/SchematicNode.java b/src/de/steamwar/sql/SchematicNode.java index 4c6d826..0dadeb0 100644 --- a/src/de/steamwar/sql/SchematicNode.java +++ b/src/de/steamwar/sql/SchematicNode.java @@ -56,20 +56,15 @@ public class SchematicNode { 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 byParent = new SelectStatement<>(table, nodeSelectCreator("") + "WHERE ParentNode <=> ? ORDER BY NodeName"); + private static final SelectStatement dirsByParent = new SelectStatement<>(table, nodeSelectCreator("") + "WHERE ParentNode" + Statement.NULL_SAFE_EQUALS + "? AND NodeType is NULL 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 all = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? ORDER BY NodeName"); - private static final SelectStatement list_null = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND ParentNode is null ORDER BY NodeName"); - private static final SelectStatement list = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND ParentNode = ? ORDER BY NodeName"); - private static final SelectStatement byParentName = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND ParentNode = ? AND NodeName = ? ORDER BY NodeName"); - private static final SelectStatement byParentName_null = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND ParentNode is null AND NodeName = ? ORDER BY NodeName"); + private static final SelectStatement list = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND ParentNode" + Statement.NULL_SAFE_EQUALS + "? ORDER BY NodeName"); + private static final SelectStatement byParentName = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND ParentNode" + Statement.NULL_SAFE_EQUALS + "? AND NodeName = ? ORDER BY NodeName"); private static final SelectStatement schematicAccessibleForUser = new SelectStatement<>(table, "SELECT COUNT(DISTINCT NodeId) FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND NodeId = ?"); - private static final SelectStatement accessibleByUserTypeInParent = new SelectStatement<>(table, "WITH RECURSIVE RSN AS (SELECT NodeId, ParentNode FROM EffectiveSchematicNode WHERE NodeType = ? AND EffectiveOwner = ? UNION SELECT SN.NodeId, SN.ParentNode FROM RSN, EffectiveSchematicNode SN WHERE SN.NodeId = RSN.ParentNode AND EffectiveOwner = ?) SELECT SN.NodeId, SN.NodeOwner, ? AS EffectiveOwner, SN.NodeName, RSN.ParentNode, SN.LastUpdate, SN.NodeItem, SN.NodeType, SN.NodeRank FROM RSN INNER JOIN SchematicNode SN ON RSN.NodeId = SN.NodeId WHERE RSN.ParentNode = ?"); - private static final SelectStatement accessibleByUserTypeInParent_null = new SelectStatement<>(table, "WITH RECURSIVE RSN AS (SELECT NodeId, ParentNode FROM EffectiveSchematicNode WHERE NodeType = ? AND EffectiveOwner = ? UNION SELECT SN.NodeId, SN.ParentNode FROM RSN, EffectiveSchematicNode SN WHERE SN.NodeId = RSN.ParentNode AND EffectiveOwner = ?) SELECT SN.NodeId, SN.NodeOwner, ? AS EffectiveOwner, SN.NodeName, RSN.ParentNode, SN.LastUpdate, SN.NodeItem, SN.NodeType, SN.NodeRank FROM RSN INNER JOIN SchematicNode SN ON RSN.NodeId = SN.NodeId WHERE RSN.ParentNode is null"); + private static final SelectStatement accessibleByUserTypeInParent = new SelectStatement<>(table, "WITH RECURSIVE RSN AS (SELECT NodeId, ParentNode FROM EffectiveSchematicNode WHERE NodeType = ? AND EffectiveOwner = ? UNION SELECT SN.NodeId, SN.ParentNode FROM RSN, EffectiveSchematicNode SN WHERE SN.NodeId = RSN.ParentNode AND EffectiveOwner = ?) SELECT SN.NodeId, SN.NodeOwner, ? AS EffectiveOwner, SN.NodeName, RSN.ParentNode, SN.LastUpdate, SN.NodeItem, SN.NodeType, SN.NodeRank FROM RSN INNER JOIN SchematicNode SN ON RSN.NodeId = SN.NodeId WHERE RSN.ParentNode" + Statement.NULL_SAFE_EQUALS + "?"); private static final SelectStatement accessibleByUserType = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND NodeType = ?"); private static final SelectStatement byIdAndUser = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND NodeId = ?"); private static final SelectStatement allParentsOfNode = new SelectStatement<>(table, "WITH RECURSIVE R AS (SELECT NodeId, ParentNode FROM EffectiveSchematicNode WHERE NodeId = ? AND EffectiveOwner = ? UNION SELECT E.NodeId, E.ParentNode FROM R, EffectiveSchematicNode E WHERE R.ParentNode = E.NodeId AND E.EffectiveOwner = ?) SELECT SN.NodeId, SN.NodeOwner, ? AS EffectiveOwner, SN.NodeName, R.ParentNode, SN.LastUpdate, SN.NodeItem, SN.NodeType, SN.NodeRank FROM R INNER JOIN SchematicNode SN ON SN.NodeId = R.NodeId"); @@ -135,6 +130,13 @@ public class SchematicNode { this.nodeFormat = nodeFormat; } + private static Integer nullOrNotNul(Integer i) { + if(i == null || i == 0) { + return null; + } + return i; + } + public static List getAll(SteamwarUser user) { return all.listSelect(user); } @@ -144,19 +146,11 @@ public class SchematicNode { } public static List list(SteamwarUser user, Integer schematicId) { - if(schematicId == null || schematicId == 0) { - return list_null.listSelect(user); - } else { - return list.listSelect(user, schematicId); - } + return list.listSelect(user, nullOrNotNul(schematicId)); } public static SchematicNode byParentName(SteamwarUser user, Integer schematicId, String name) { - if(schematicId == null || schematicId == 0) { - return byParentName_null.select(user, name); - } else { - return byParentName.select(user, schematicId, name); - } + return byParentName.select(user, nullOrNotNul(schematicId), name); } public static List accessibleByUserType(SteamwarUser user, SchematicType type) { @@ -172,11 +166,7 @@ public class SchematicNode { } public static List accessibleByUserTypeParent(SteamwarUser user, SchematicType type, Integer parentId) { - if(parentId == null || parentId == 0) { - return accessibleByUserTypeInParent_null.listSelect(type, user, user, user); - } else { - return accessibleByUserTypeInParent.listSelect(type, user, user, user, parentId); - } + return accessibleByUserTypeInParent.listSelect(type, user, user, user, nullOrNotNul(parentId)); } public static SchematicNode byIdAndUser(SteamwarUser user, Integer id) { @@ -227,18 +217,16 @@ public class SchematicNode { public static List getSchematicNodeInNode(Integer parent) { if(parent == null || parent == 0) { rootWarning(); - return byParent_null.listSelect(); } - return byParent.listSelect(parent); + return byParent.listSelect(nullOrNotNul(parent)); } public static List getSchematicDirectoryInNode(Integer parent) { if(parent == null || parent == 0) { rootWarning(); - return dirsByParent_null.listSelect(); } - return dirsByParent.listSelect(parent); + return dirsByParent.listSelect(nullOrNotNul(parent)); } @Deprecated @@ -254,9 +242,8 @@ public class SchematicNode { public static SchematicNode getSchematicNode(String name, Integer parent) { if(parent == null || parent == 0) { rootWarning(); - return byParentName_null.select(name); } - return byParentName.select(name, parent); + return byParentName.select(name, nullOrNotNul(parent)); } public static SchematicNode getSchematicNode(int id) { @@ -576,7 +563,6 @@ public class SchematicNode { return false; } - @Deprecated public static List getNodeTabcomplete(SteamwarUser user, String s) { boolean sws = s.startsWith("/"); if (sws) { From ad43512d30efa05f9905296ff739df61b80431a2 Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Sun, 15 Jan 2023 20:18:53 +0100 Subject: [PATCH 48/85] Add Copyright --- src/de/steamwar/sql/SchematicNode.java | 38 ++++---------------------- 1 file changed, 6 insertions(+), 32 deletions(-) diff --git a/src/de/steamwar/sql/SchematicNode.java b/src/de/steamwar/sql/SchematicNode.java index 0dadeb0..594b20a 100644 --- a/src/de/steamwar/sql/SchematicNode.java +++ b/src/de/steamwar/sql/SchematicNode.java @@ -55,7 +55,6 @@ public class SchematicNode { 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 dirsByParent = new SelectStatement<>(table, nodeSelectCreator("") + "WHERE ParentNode" + Statement.NULL_SAFE_EQUALS + "? AND NodeType is NULL ORDER BY NodeName"); private static final SelectStatement byOwnerType = new SelectStatement<>(table, nodeSelectCreator("") + "WHERE NodeOwner = ? AND NodeType = ? ORDER BY NodeName"); @@ -130,13 +129,6 @@ public class SchematicNode { this.nodeFormat = nodeFormat; } - private static Integer nullOrNotNul(Integer i) { - if(i == null || i == 0) { - return null; - } - return i; - } - public static List getAll(SteamwarUser user) { return all.listSelect(user); } @@ -146,11 +138,11 @@ public class SchematicNode { } public static List list(SteamwarUser user, Integer schematicId) { - return list.listSelect(user, nullOrNotNul(schematicId)); + return list.listSelect(user, schematicId); } public static SchematicNode byParentName(SteamwarUser user, Integer schematicId, String name) { - return byParentName.select(user, nullOrNotNul(schematicId), name); + return byParentName.select(user, schematicId, name); } public static List accessibleByUserType(SteamwarUser user, SchematicType type) { @@ -166,7 +158,7 @@ public class SchematicNode { } public static List accessibleByUserTypeParent(SteamwarUser user, SchematicType type, Integer parentId) { - return accessibleByUserTypeInParent.listSelect(type, user, user, user, nullOrNotNul(parentId)); + return accessibleByUserTypeInParent.listSelect(type, user, user, user, parentId); } public static SchematicNode byIdAndUser(SteamwarUser user, Integer id) { @@ -205,8 +197,6 @@ public class SchematicNode { } 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); } @@ -215,18 +205,11 @@ public class SchematicNode { } public static List getSchematicNodeInNode(Integer parent) { - if(parent == null || parent == 0) { - rootWarning(); - } - - return byParent.listSelect(nullOrNotNul(parent)); + return byParent.listSelect(parent); } public static List getSchematicDirectoryInNode(Integer parent) { - if(parent == null || parent == 0) { - rootWarning(); - } - return dirsByParent.listSelect(nullOrNotNul(parent)); + return dirsByParent.listSelect(parent); } @Deprecated @@ -240,10 +223,7 @@ public class SchematicNode { } public static SchematicNode getSchematicNode(String name, Integer parent) { - if(parent == null || parent == 0) { - rootWarning(); - } - return byParentName.select(name, nullOrNotNul(parent)); + return byParentName.select(name, parent); } public static SchematicNode getSchematicNode(int id) { @@ -588,10 +568,4 @@ public class SchematicNode { 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()); - } } From d56150a8b105f5ad63a8c3a21b6833f388a83225 Mon Sep 17 00:00:00 2001 From: yoyosource Date: Sun, 15 Jan 2023 20:45:29 +0100 Subject: [PATCH 49/85] Fix one weird behaviour --- .../steamwar/command/PreviousArguments.java | 23 +++++++++++++++++++ src/de/steamwar/command/SWCommandUtils.java | 5 ++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/de/steamwar/command/PreviousArguments.java b/src/de/steamwar/command/PreviousArguments.java index 24701c0..e9851c0 100644 --- a/src/de/steamwar/command/PreviousArguments.java +++ b/src/de/steamwar/command/PreviousArguments.java @@ -19,6 +19,10 @@ package de.steamwar.command; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + public class PreviousArguments { public final String[] userArgs; @@ -36,4 +40,23 @@ public class PreviousArguments { public T getMappedArg(int index) { return (T) mappedArgs[mappedArgs.length - index - 1]; } + + public Optional getFirst(Class clazz) { + for (Object o : mappedArgs) { + if (clazz.isInstance(o)) { + return Optional.of((T) o); + } + } + return Optional.empty(); + } + + public List getAll(Class clazz) { + List list = new ArrayList<>(); + for (Object o : mappedArgs) { + if (clazz.isInstance(o)) { + list.add((T) o); + } + } + return list; + } } diff --git a/src/de/steamwar/command/SWCommandUtils.java b/src/de/steamwar/command/SWCommandUtils.java index 5bedbe6..32b7fcc 100644 --- a/src/de/steamwar/command/SWCommandUtils.java +++ b/src/de/steamwar/command/SWCommandUtils.java @@ -23,7 +23,6 @@ import lombok.Getter; import lombok.experimental.UtilityClass; import java.lang.annotation.Annotation; -import java.lang.reflect.Array; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.*; @@ -42,12 +41,12 @@ public class SWCommandUtils { private SWTypeMapperCreator swTypeMapperCreator = (mapper, tabCompleter) -> new AbstractTypeMapper() { @Override - public Object map(Object sender, String[] previousArguments, String s) { + public Object map(Object sender, PreviousArguments previousArguments, String s) { return mapper.apply(s); } @Override - public Collection tabCompletes(Object sender, String[] previousArguments, String s) { + public Collection tabCompletes(Object sender, PreviousArguments previousArguments, String s) { return ((BiFunction>) tabCompleter).apply(sender, s); } }; From a3f13737e4031628b5f29ddc43250dbbc233f13e Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Sun, 15 Jan 2023 20:46:11 +0100 Subject: [PATCH 50/85] Add Copyright --- src/de/steamwar/sql/NodeMember.java | 2 +- src/de/steamwar/sql/SchematicNode.java | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/de/steamwar/sql/NodeMember.java b/src/de/steamwar/sql/NodeMember.java index 19417da..a952028 100644 --- a/src/de/steamwar/sql/NodeMember.java +++ b/src/de/steamwar/sql/NodeMember.java @@ -85,7 +85,7 @@ public class NodeMember { } public void setParentId(Integer parentId) { - this.parentId = Optional.ofNullable(parentId).orElse(0) == 0 ? null : parentId; + this.parentId = parentId == null || parentId == 0 ? null : parentId; updateParent.update(this.parentId, nodeId, userId); } } diff --git a/src/de/steamwar/sql/SchematicNode.java b/src/de/steamwar/sql/SchematicNode.java index 594b20a..9142154 100644 --- a/src/de/steamwar/sql/SchematicNode.java +++ b/src/de/steamwar/sql/SchematicNode.java @@ -24,8 +24,6 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; import java.sql.Timestamp; import java.time.Instant; import java.util.*; @@ -38,7 +36,7 @@ public class SchematicNode { 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)); } - protected static final Map>> TAB_CACHE = new HashMap<>(); + private static final Map>> TAB_CACHE = new HashMap<>(); public static void clear() { TAB_CACHE.clear(); } From 2e39d677d2c9b6518886db620e571f564009ca14 Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Sun, 15 Jan 2023 21:10:35 +0100 Subject: [PATCH 51/85] Add Copyright --- src/de/steamwar/sql/SchematicNode.java | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/de/steamwar/sql/SchematicNode.java b/src/de/steamwar/sql/SchematicNode.java index 9142154..b0064c6 100644 --- a/src/de/steamwar/sql/SchematicNode.java +++ b/src/de/steamwar/sql/SchematicNode.java @@ -41,10 +41,7 @@ public class SchematicNode { 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(", ")) + ", NodeOwner AS EffectiveOwner FROM SchematicNode "; - } + private static final String nodeSelector = "SELECT NodeId, NodeOwner, NodeOwner AS EffectiveOwner, NodeName, ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, ReplaceColor, AllowReplay, NodeFormat FROM SchematicNode "; private static final Table table = new Table<>(SchematicNode.class); private static final Statement create = table.insertFields(true, "NodeOwner", "NodeName", "ParentNode", "NodeItem", "NodeType"); @@ -53,10 +50,10 @@ public class SchematicNode { private static final SelectStatement byId = table.select(Table.PRIMARY); private static final SelectStatement byOwnerNameParent = table.select("OwnerNameParent"); - private static final SelectStatement byParent = new SelectStatement<>(table, nodeSelectCreator("") + "WHERE ParentNode <=> ? ORDER BY NodeName"); - private static final SelectStatement dirsByParent = new SelectStatement<>(table, nodeSelectCreator("") + "WHERE ParentNode" + Statement.NULL_SAFE_EQUALS + "? AND NodeType is NULL 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 byParent = new SelectStatement<>(table, nodeSelector + "WHERE ParentNode" + Statement.NULL_SAFE_EQUALS + "? ORDER BY NodeName"); + private static final SelectStatement dirsByParent = new SelectStatement<>(table, nodeSelector + "WHERE ParentNode" + Statement.NULL_SAFE_EQUALS + "? AND NodeType is NULL ORDER BY NodeName"); + private static final SelectStatement byOwnerType = new SelectStatement<>(table, nodeSelector + "WHERE NodeOwner = ? AND NodeType = ? ORDER BY NodeName"); + private static final SelectStatement byType = new SelectStatement<>(table, nodeSelector + "WHERE NodeType = ? ORDER BY NodeName"); private static final SelectStatement all = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? ORDER BY NodeName"); private static final SelectStatement list = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND ParentNode" + Statement.NULL_SAFE_EQUALS + "? ORDER BY NodeName"); private static final SelectStatement byParentName = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND ParentNode" + Statement.NULL_SAFE_EQUALS + "? AND NodeName = ? ORDER BY NodeName"); From d30650bad30d53b5fb6c4e5c9499b71c577cf849 Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Mon, 16 Jan 2023 16:33:17 +0100 Subject: [PATCH 52/85] Fixes "0 wird nicht benutzt" --- src/de/steamwar/sql/NodeMember.java | 2 +- src/de/steamwar/sql/SchematicNode.java | 22 ++++++++++------------ 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/de/steamwar/sql/NodeMember.java b/src/de/steamwar/sql/NodeMember.java index a952028..e25764e 100644 --- a/src/de/steamwar/sql/NodeMember.java +++ b/src/de/steamwar/sql/NodeMember.java @@ -85,7 +85,7 @@ public class NodeMember { } public void setParentId(Integer parentId) { - this.parentId = parentId == null || parentId == 0 ? null : parentId; + this.parentId = parentId; updateParent.update(this.parentId, nodeId, userId); } } diff --git a/src/de/steamwar/sql/SchematicNode.java b/src/de/steamwar/sql/SchematicNode.java index b0064c6..ca03c0a 100644 --- a/src/de/steamwar/sql/SchematicNode.java +++ b/src/de/steamwar/sql/SchematicNode.java @@ -48,7 +48,7 @@ public class SchematicNode { private static final Statement update = table.update(Table.PRIMARY, "NodeName", "ParentNode", "NodeItem", "NodeType", "NodeRank", "ReplaceColor", "AllowReplay", "NodeFormat"); private static final Statement delete = table.delete(Table.PRIMARY); - private static final SelectStatement byId = table.select(Table.PRIMARY); + private static final SelectStatement byId = new SelectStatement<>(table, nodeSelector + "WHERE NodeId = ?"); private static final SelectStatement byOwnerNameParent = table.select("OwnerNameParent"); private static final SelectStatement byParent = new SelectStatement<>(table, nodeSelector + "WHERE ParentNode" + Statement.NULL_SAFE_EQUALS + "? ORDER BY NodeName"); private static final SelectStatement dirsByParent = new SelectStatement<>(table, nodeSelector + "WHERE ParentNode" + Statement.NULL_SAFE_EQUALS + "? AND NodeType is NULL ORDER BY NodeName"); @@ -58,10 +58,10 @@ public class SchematicNode { private static final SelectStatement list = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND ParentNode" + Statement.NULL_SAFE_EQUALS + "? ORDER BY NodeName"); private static final SelectStatement byParentName = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND ParentNode" + Statement.NULL_SAFE_EQUALS + "? AND NodeName = ? ORDER BY NodeName"); private static final SelectStatement schematicAccessibleForUser = new SelectStatement<>(table, "SELECT COUNT(DISTINCT NodeId) FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND NodeId = ?"); - private static final SelectStatement accessibleByUserTypeInParent = new SelectStatement<>(table, "WITH RECURSIVE RSN AS (SELECT NodeId, ParentNode FROM EffectiveSchematicNode WHERE NodeType = ? AND EffectiveOwner = ? UNION SELECT SN.NodeId, SN.ParentNode FROM RSN, EffectiveSchematicNode SN WHERE SN.NodeId = RSN.ParentNode AND EffectiveOwner = ?) SELECT SN.NodeId, SN.NodeOwner, ? AS EffectiveOwner, SN.NodeName, RSN.ParentNode, SN.LastUpdate, SN.NodeItem, SN.NodeType, SN.NodeRank FROM RSN INNER JOIN SchematicNode SN ON RSN.NodeId = SN.NodeId WHERE RSN.ParentNode" + Statement.NULL_SAFE_EQUALS + "?"); + private static final SelectStatement accessibleByUserTypeInParent = new SelectStatement<>(table, "WITH RECURSIVE RSN AS (SELECT NodeId, ParentNode FROM EffectiveSchematicNode WHERE NodeType = ? AND EffectiveOwner = ? UNION SELECT SN.NodeId, SN.ParentNode FROM RSN, EffectiveSchematicNode SN WHERE SN.NodeId = RSN.ParentNode AND EffectiveOwner = ?) SELECT SN.NodeId, SN.NodeOwner, ? AS EffectiveOwner, SN.NodeName, RSN.ParentNode, SN.LastUpdate, SN.NodeItem, SN.NodeType, SN.NodeRank, SN.ReplaceColor, SN.AllowReplay, SN.NodeFormat FROM RSN INNER JOIN SchematicNode SN ON RSN.NodeId = SN.NodeId WHERE RSN.ParentNode" + Statement.NULL_SAFE_EQUALS + "?"); private static final SelectStatement accessibleByUserType = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND NodeType = ?"); private static final SelectStatement byIdAndUser = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND NodeId = ?"); - private static final SelectStatement allParentsOfNode = new SelectStatement<>(table, "WITH RECURSIVE R AS (SELECT NodeId, ParentNode FROM EffectiveSchematicNode WHERE NodeId = ? AND EffectiveOwner = ? UNION SELECT E.NodeId, E.ParentNode FROM R, EffectiveSchematicNode E WHERE R.ParentNode = E.NodeId AND E.EffectiveOwner = ?) SELECT SN.NodeId, SN.NodeOwner, ? AS EffectiveOwner, SN.NodeName, R.ParentNode, SN.LastUpdate, SN.NodeItem, SN.NodeType, SN.NodeRank FROM R INNER JOIN SchematicNode SN ON SN.NodeId = R.NodeId"); + private static final SelectStatement allParentsOfNode = new SelectStatement<>(table, "WITH RECURSIVE R AS (SELECT NodeId, ParentNode FROM EffectiveSchematicNode WHERE NodeId = ? AND EffectiveOwner = ? UNION SELECT E.NodeId, E.ParentNode FROM R, EffectiveSchematicNode E WHERE R.ParentNode = E.NodeId AND E.EffectiveOwner = ?) SELECT SN.NodeId, SN.NodeOwner, ? AS EffectiveOwner, SN.NodeName, R.ParentNode, SN.LastUpdate, SN.NodeItem, SN.NodeType, SN.NodeRank, SN.ReplaceColor, SN.AllowReplay, SN.NodeFormat FROM R INNER JOIN SchematicNode SN ON SN.NodeId = R.NodeId"); static { NodeMember.init(); @@ -71,7 +71,7 @@ public class SchematicNode { private final int nodeId; @Field(keys = {"OwnerNameParent"}) private final int nodeOwner; - @Field + @Field(def = "0") @Getter private final int effectiveOwner; @Field(keys = {"OwnerNameParent"}) @@ -161,7 +161,7 @@ public class SchematicNode { } public static List parentsOfNode(SteamwarUser user, Integer id) { - return allParentsOfNode.listSelect(id, user, user, user).stream().filter(n -> n.getId() != id).collect(Collectors.toList()); + return allParentsOfNode.listSelect(id, user, user, user); } private static Map> map(List in) { @@ -290,7 +290,7 @@ public class SchematicNode { } if (s.contains("/")) { String[] layers = s.split("/"); - Optional currentNode = Optional.ofNullable(SchematicNode.byParentName(user, 0, layers[0])); + Optional currentNode = Optional.ofNullable(SchematicNode.byParentName(user, null, layers[0])); for (int i = 1; i < layers.length; i++) { int finalI = i; Optional node = currentNode.map(effectiveSchematicNode -> SchematicNode.byParentName(user, effectiveSchematicNode.getId(), layers[finalI])); @@ -305,7 +305,7 @@ public class SchematicNode { } return currentNode.orElse(null); } else { - return SchematicNode.byParentName(user, 0, s); + return SchematicNode.byParentName(user, null, s); } } @@ -484,14 +484,12 @@ public class SchematicNode { return ((SchematicNode) obj).getId() == nodeId; } - @Deprecated public String generateBreadcrumbs(SteamwarUser user) { - return generateBreadcrumbs(); + return byIdAndUser(user, nodeId).generateBreadcrumbs(); } - @Deprecated public String generateBreadcrumbs(String split, SteamwarUser user) { - return generateBreadcrumbs(split); + return byIdAndUser(user, nodeId).generateBreadcrumbs(split); } public String generateBreadcrumbs() { @@ -556,7 +554,7 @@ public class SchematicNode { List nodes = SchematicNode.list(user, pa.getId()); nodes.forEach(node -> list.add((sws ? "/" : "") + node.generateBreadcrumbs())); } else { - List nodes = SchematicNode.list(user, 0); + List nodes = SchematicNode.list(user, null); nodes.forEach(node -> list.add((sws ? "/" : "") + node.getName() + (node.isDir() ? "/" : ""))); } list.remove("//copy"); From 785f31a728726265d7075fb6d92e9c794cc66b54 Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Mon, 16 Jan 2023 16:40:01 +0100 Subject: [PATCH 53/85] Fixes "0 wird nicht benutzt" --- src/de/steamwar/sql/SchematicNode.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/de/steamwar/sql/SchematicNode.java b/src/de/steamwar/sql/SchematicNode.java index ca03c0a..5a8258f 100644 --- a/src/de/steamwar/sql/SchematicNode.java +++ b/src/de/steamwar/sql/SchematicNode.java @@ -49,7 +49,7 @@ public class SchematicNode { private static final Statement delete = table.delete(Table.PRIMARY); private static final SelectStatement byId = new SelectStatement<>(table, nodeSelector + "WHERE NodeId = ?"); - private static final SelectStatement byOwnerNameParent = table.select("OwnerNameParent"); + private static final SelectStatement byOwnerNameParent = new SelectStatement<>(table, nodeSelector + "WHERE NodeOwner = ? AND NodeName = ? AND ParentNode = ?"); private static final SelectStatement byParent = new SelectStatement<>(table, nodeSelector + "WHERE ParentNode" + Statement.NULL_SAFE_EQUALS + "? ORDER BY NodeName"); private static final SelectStatement dirsByParent = new SelectStatement<>(table, nodeSelector + "WHERE ParentNode" + Statement.NULL_SAFE_EQUALS + "? AND NodeType is NULL ORDER BY NodeName"); private static final SelectStatement byOwnerType = new SelectStatement<>(table, nodeSelector + "WHERE NodeOwner = ? AND NodeType = ? ORDER BY NodeName"); From 4422c7c0ba55eb2a25c142531b5c59a09cf3f4c1 Mon Sep 17 00:00:00 2001 From: Lixfel Date: Tue, 17 Jan 2023 18:20:24 +0100 Subject: [PATCH 54/85] Hotfix statement --- src/de/steamwar/sql/SchematicNode.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/de/steamwar/sql/SchematicNode.java b/src/de/steamwar/sql/SchematicNode.java index 5a8258f..2709399 100644 --- a/src/de/steamwar/sql/SchematicNode.java +++ b/src/de/steamwar/sql/SchematicNode.java @@ -28,7 +28,6 @@ import java.sql.Timestamp; import java.time.Instant; import java.util.*; import java.util.function.Predicate; -import java.util.stream.Collectors; public class SchematicNode { @@ -141,7 +140,7 @@ public class SchematicNode { } public static List accessibleByUserType(SteamwarUser user, SchematicType type) { - return accessibleByUserType.listSelect(type, user, user, user); + return accessibleByUserType.listSelect(type, user); } public static Map> accessibleByUserTypeMap(SteamwarUser user, SchematicType type) { From 1cf75f37422f695822a5e5aa5fd9ad85eaee92f9 Mon Sep 17 00:00:00 2001 From: yoyosource Date: Tue, 17 Jan 2023 18:27:29 +0100 Subject: [PATCH 55/85] Hotfix AbstractSWCommand --- src/de/steamwar/command/AbstractSWCommand.java | 2 +- src/de/steamwar/sql/SchematicNode.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/de/steamwar/command/AbstractSWCommand.java b/src/de/steamwar/command/AbstractSWCommand.java index bf65ec8..ef0ccf1 100644 --- a/src/de/steamwar/command/AbstractSWCommand.java +++ b/src/de/steamwar/command/AbstractSWCommand.java @@ -95,7 +95,7 @@ public abstract class AbstractSWCommand { list.add(arg); } if (quote) { - commandSystemWarning(() -> "Missing closing quote in command"); + builder.append("\""); } if (builder.length() > 0) { list.add(builder.toString()); diff --git a/src/de/steamwar/sql/SchematicNode.java b/src/de/steamwar/sql/SchematicNode.java index 2709399..ed9ca39 100644 --- a/src/de/steamwar/sql/SchematicNode.java +++ b/src/de/steamwar/sql/SchematicNode.java @@ -140,7 +140,7 @@ public class SchematicNode { } public static List accessibleByUserType(SteamwarUser user, SchematicType type) { - return accessibleByUserType.listSelect(type, user); + return accessibleByUserType.listSelect(user, type); } public static Map> accessibleByUserTypeMap(SteamwarUser user, SchematicType type) { From ee35d659924c67c042eb3b9a04c2bc79f20cca1a Mon Sep 17 00:00:00 2001 From: Lixfel Date: Tue, 17 Jan 2023 19:26:19 +0100 Subject: [PATCH 56/85] Revert to null unsafe default --- src/de/steamwar/sql/internal/SelectStatement.java | 2 +- src/de/steamwar/sql/internal/Table.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/de/steamwar/sql/internal/SelectStatement.java b/src/de/steamwar/sql/internal/SelectStatement.java index 0b39160..e17dcb7 100644 --- a/src/de/steamwar/sql/internal/SelectStatement.java +++ b/src/de/steamwar/sql/internal/SelectStatement.java @@ -31,7 +31,7 @@ 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 + Statement.NULL_SAFE_EQUALS + "?").collect(Collectors.joining(" AND "))); + 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) { diff --git a/src/de/steamwar/sql/internal/Table.java b/src/de/steamwar/sql/internal/Table.java index 3040642..cbad10a 100644 --- a/src/de/steamwar/sql/internal/Table.java +++ b/src/de/steamwar/sql/internal/Table.java @@ -75,7 +75,7 @@ public class Table { } 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 + Statement.NULL_SAFE_EQUALS + "?").collect(Collectors.joining(" AND "))); + 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) { @@ -100,7 +100,7 @@ public class Table { } public Statement deleteFields(String... kfields) { - return new Statement("DELETE FROM " + name + " WHERE " + Arrays.stream(kfields).map(f -> f + Statement.NULL_SAFE_EQUALS + "?").collect(Collectors.joining(" AND "))); + return new Statement("DELETE FROM " + name + " WHERE " + Arrays.stream(kfields).map(f -> f + " = ?").collect(Collectors.joining(" AND "))); } void ensureExistanceInSqlite() { From d6b3dbc446895dad8706405f306d1f68ec2128d6 Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Tue, 17 Jan 2023 19:38:50 +0100 Subject: [PATCH 57/85] NodeMember Fix #1 --- src/de/steamwar/sql/SchematicNode.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/de/steamwar/sql/SchematicNode.java b/src/de/steamwar/sql/SchematicNode.java index ed9ca39..e57290f 100644 --- a/src/de/steamwar/sql/SchematicNode.java +++ b/src/de/steamwar/sql/SchematicNode.java @@ -54,7 +54,7 @@ public class SchematicNode { private static final SelectStatement byOwnerType = new SelectStatement<>(table, nodeSelector + "WHERE NodeOwner = ? AND NodeType = ? ORDER BY NodeName"); private static final SelectStatement byType = new SelectStatement<>(table, nodeSelector + "WHERE NodeType = ? ORDER BY NodeName"); private static final SelectStatement all = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? ORDER BY NodeName"); - private static final SelectStatement list = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND ParentNode" + Statement.NULL_SAFE_EQUALS + "? ORDER BY NodeName"); + private static final SelectStatement list = new SelectStatement<>(table, "SELECT SchematicNode.NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, NM.ParentId AS ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, ReplaceColor, AllowReplay, NodeFormat FROM SchematicNode INNER JOIN NodeMember NM on SchematicNode.NodeId = NM.NodeId WHERE NM.ParentId <=> ? AND NM.UserId = ? UNION ALL SELECT SchematicNode.NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, ReplaceColor, AllowReplay, NodeFormat FROM SchematicNode WHERE ParentNode <=> ? AND NodeOwner = ?"); private static final SelectStatement byParentName = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND ParentNode" + Statement.NULL_SAFE_EQUALS + "? AND NodeName = ? ORDER BY NodeName"); private static final SelectStatement schematicAccessibleForUser = new SelectStatement<>(table, "SELECT COUNT(DISTINCT NodeId) FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND NodeId = ?"); private static final SelectStatement accessibleByUserTypeInParent = new SelectStatement<>(table, "WITH RECURSIVE RSN AS (SELECT NodeId, ParentNode FROM EffectiveSchematicNode WHERE NodeType = ? AND EffectiveOwner = ? UNION SELECT SN.NodeId, SN.ParentNode FROM RSN, EffectiveSchematicNode SN WHERE SN.NodeId = RSN.ParentNode AND EffectiveOwner = ?) SELECT SN.NodeId, SN.NodeOwner, ? AS EffectiveOwner, SN.NodeName, RSN.ParentNode, SN.LastUpdate, SN.NodeItem, SN.NodeType, SN.NodeRank, SN.ReplaceColor, SN.AllowReplay, SN.NodeFormat FROM RSN INNER JOIN SchematicNode SN ON RSN.NodeId = SN.NodeId WHERE RSN.ParentNode" + Statement.NULL_SAFE_EQUALS + "?"); @@ -132,7 +132,7 @@ public class SchematicNode { } public static List list(SteamwarUser user, Integer schematicId) { - return list.listSelect(user, schematicId); + return list.listSelect(user, schematicId, user, user, schematicId, user); } public static SchematicNode byParentName(SteamwarUser user, Integer schematicId, String name) { @@ -501,11 +501,12 @@ public class SchematicNode { public String generateBreadcrumbs(String split) { StringBuilder builder = new StringBuilder(getName()); Optional currentNode = Optional.of(this); + List parents = SchematicNode.parentsOfNode(SteamwarUser.get(effectiveOwner), nodeId); if(currentNode.map(SchematicNode::isDir).orElse(false)) { builder.append(split); } while (currentNode.isPresent()) { - currentNode = currentNode.flatMap(SchematicNode::getOptionalParent).map(integer -> byIdAndUser(SteamwarUser.get(effectiveOwner), integer)); + currentNode = currentNode.flatMap(SchematicNode::getOptionalParent).flatMap(integer -> parents.stream().filter(schematicNode -> schematicNode.getId() == integer).findFirst()); currentNode.ifPresent(node -> builder.insert(0, split).insert(0, node.getName())); } return builder.toString(); From ef26175fc3186064910c740ebf35604e39389542 Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Tue, 17 Jan 2023 19:47:47 +0100 Subject: [PATCH 58/85] NodeMember Fix #1 --- src/de/steamwar/sql/SchematicNode.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/de/steamwar/sql/SchematicNode.java b/src/de/steamwar/sql/SchematicNode.java index e57290f..df7d4b4 100644 --- a/src/de/steamwar/sql/SchematicNode.java +++ b/src/de/steamwar/sql/SchematicNode.java @@ -54,7 +54,7 @@ public class SchematicNode { private static final SelectStatement byOwnerType = new SelectStatement<>(table, nodeSelector + "WHERE NodeOwner = ? AND NodeType = ? ORDER BY NodeName"); private static final SelectStatement byType = new SelectStatement<>(table, nodeSelector + "WHERE NodeType = ? ORDER BY NodeName"); private static final SelectStatement all = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? ORDER BY NodeName"); - private static final SelectStatement list = new SelectStatement<>(table, "SELECT SchematicNode.NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, NM.ParentId AS ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, ReplaceColor, AllowReplay, NodeFormat FROM SchematicNode INNER JOIN NodeMember NM on SchematicNode.NodeId = NM.NodeId WHERE NM.ParentId <=> ? AND NM.UserId = ? UNION ALL SELECT SchematicNode.NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, ReplaceColor, AllowReplay, NodeFormat FROM SchematicNode WHERE ParentNode <=> ? AND NodeOwner = ?"); + private static final SelectStatement list = new SelectStatement<>(table, "SELECT SchematicNode.NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, NM.ParentId AS ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, ReplaceColor, AllowReplay, NodeFormat FROM SchematicNode INNER JOIN NodeMember NM on SchematicNode.NodeId = NM.NodeId WHERE NM.ParentId " + Statement.NULL_SAFE_EQUALS + "? AND NM.UserId = ? UNION ALL SELECT SchematicNode.NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, ReplaceColor, AllowReplay, NodeFormat FROM SchematicNode WHERE ParentNode " + Statement.NULL_SAFE_EQUALS + "? AND NodeOwner = ?"); private static final SelectStatement byParentName = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND ParentNode" + Statement.NULL_SAFE_EQUALS + "? AND NodeName = ? ORDER BY NodeName"); private static final SelectStatement schematicAccessibleForUser = new SelectStatement<>(table, "SELECT COUNT(DISTINCT NodeId) FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND NodeId = ?"); private static final SelectStatement accessibleByUserTypeInParent = new SelectStatement<>(table, "WITH RECURSIVE RSN AS (SELECT NodeId, ParentNode FROM EffectiveSchematicNode WHERE NodeType = ? AND EffectiveOwner = ? UNION SELECT SN.NodeId, SN.ParentNode FROM RSN, EffectiveSchematicNode SN WHERE SN.NodeId = RSN.ParentNode AND EffectiveOwner = ?) SELECT SN.NodeId, SN.NodeOwner, ? AS EffectiveOwner, SN.NodeName, RSN.ParentNode, SN.LastUpdate, SN.NodeItem, SN.NodeType, SN.NodeRank, SN.ReplaceColor, SN.AllowReplay, SN.NodeFormat FROM RSN INNER JOIN SchematicNode SN ON RSN.NodeId = SN.NodeId WHERE RSN.ParentNode" + Statement.NULL_SAFE_EQUALS + "?"); From 4e65bfe0985e8f538ab3e2d66bf92dae918787ae Mon Sep 17 00:00:00 2001 From: Lixfel Date: Tue, 17 Jan 2023 19:59:49 +0100 Subject: [PATCH 59/85] Fix ordering --- src/de/steamwar/sql/SchematicNode.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/de/steamwar/sql/SchematicNode.java b/src/de/steamwar/sql/SchematicNode.java index df7d4b4..2220146 100644 --- a/src/de/steamwar/sql/SchematicNode.java +++ b/src/de/steamwar/sql/SchematicNode.java @@ -54,7 +54,7 @@ public class SchematicNode { private static final SelectStatement byOwnerType = new SelectStatement<>(table, nodeSelector + "WHERE NodeOwner = ? AND NodeType = ? ORDER BY NodeName"); private static final SelectStatement byType = new SelectStatement<>(table, nodeSelector + "WHERE NodeType = ? ORDER BY NodeName"); private static final SelectStatement all = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? ORDER BY NodeName"); - private static final SelectStatement list = new SelectStatement<>(table, "SELECT SchematicNode.NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, NM.ParentId AS ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, ReplaceColor, AllowReplay, NodeFormat FROM SchematicNode INNER JOIN NodeMember NM on SchematicNode.NodeId = NM.NodeId WHERE NM.ParentId " + Statement.NULL_SAFE_EQUALS + "? AND NM.UserId = ? UNION ALL SELECT SchematicNode.NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, ReplaceColor, AllowReplay, NodeFormat FROM SchematicNode WHERE ParentNode " + Statement.NULL_SAFE_EQUALS + "? AND NodeOwner = ?"); + private static final SelectStatement list = new SelectStatement<>(table, "SELECT SchematicNode.NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, NM.ParentId AS ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, ReplaceColor, AllowReplay, NodeFormat FROM SchematicNode INNER JOIN NodeMember NM on SchematicNode.NodeId = NM.NodeId WHERE NM.ParentId " + Statement.NULL_SAFE_EQUALS + "? AND NM.UserId = ? ORDER BY NodeName UNION ALL SELECT SchematicNode.NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, ReplaceColor, AllowReplay, NodeFormat FROM SchematicNode WHERE ParentNode " + Statement.NULL_SAFE_EQUALS + "? AND NodeOwner = ? ORDER BY NodeName"); private static final SelectStatement byParentName = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND ParentNode" + Statement.NULL_SAFE_EQUALS + "? AND NodeName = ? ORDER BY NodeName"); private static final SelectStatement schematicAccessibleForUser = new SelectStatement<>(table, "SELECT COUNT(DISTINCT NodeId) FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND NodeId = ?"); private static final SelectStatement accessibleByUserTypeInParent = new SelectStatement<>(table, "WITH RECURSIVE RSN AS (SELECT NodeId, ParentNode FROM EffectiveSchematicNode WHERE NodeType = ? AND EffectiveOwner = ? UNION SELECT SN.NodeId, SN.ParentNode FROM RSN, EffectiveSchematicNode SN WHERE SN.NodeId = RSN.ParentNode AND EffectiveOwner = ?) SELECT SN.NodeId, SN.NodeOwner, ? AS EffectiveOwner, SN.NodeName, RSN.ParentNode, SN.LastUpdate, SN.NodeItem, SN.NodeType, SN.NodeRank, SN.ReplaceColor, SN.AllowReplay, SN.NodeFormat FROM RSN INNER JOIN SchematicNode SN ON RSN.NodeId = SN.NodeId WHERE RSN.ParentNode" + Statement.NULL_SAFE_EQUALS + "?"); From 1a5455af93af567807f5c321594b61e2668cfb86 Mon Sep 17 00:00:00 2001 From: Lixfel Date: Tue, 17 Jan 2023 20:00:37 +0100 Subject: [PATCH 60/85] HOTFIX --- src/de/steamwar/sql/SchematicNode.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/de/steamwar/sql/SchematicNode.java b/src/de/steamwar/sql/SchematicNode.java index 2220146..d909baa 100644 --- a/src/de/steamwar/sql/SchematicNode.java +++ b/src/de/steamwar/sql/SchematicNode.java @@ -54,7 +54,7 @@ public class SchematicNode { private static final SelectStatement byOwnerType = new SelectStatement<>(table, nodeSelector + "WHERE NodeOwner = ? AND NodeType = ? ORDER BY NodeName"); private static final SelectStatement byType = new SelectStatement<>(table, nodeSelector + "WHERE NodeType = ? ORDER BY NodeName"); private static final SelectStatement all = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? ORDER BY NodeName"); - private static final SelectStatement list = new SelectStatement<>(table, "SELECT SchematicNode.NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, NM.ParentId AS ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, ReplaceColor, AllowReplay, NodeFormat FROM SchematicNode INNER JOIN NodeMember NM on SchematicNode.NodeId = NM.NodeId WHERE NM.ParentId " + Statement.NULL_SAFE_EQUALS + "? AND NM.UserId = ? ORDER BY NodeName UNION ALL SELECT SchematicNode.NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, ReplaceColor, AllowReplay, NodeFormat FROM SchematicNode WHERE ParentNode " + Statement.NULL_SAFE_EQUALS + "? AND NodeOwner = ? ORDER BY NodeName"); + private static final SelectStatement list = new SelectStatement<>(table, "SELECT SchematicNode.NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, NM.ParentId AS ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, ReplaceColor, AllowReplay, NodeFormat FROM SchematicNode INNER JOIN NodeMember NM on SchematicNode.NodeId = NM.NodeId WHERE NM.ParentId " + Statement.NULL_SAFE_EQUALS + "? AND NM.UserId = ? UNION ALL SELECT SchematicNode.NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, ReplaceColor, AllowReplay, NodeFormat FROM SchematicNode WHERE ParentNode " + Statement.NULL_SAFE_EQUALS + "? AND NodeOwner = ? ORDER BY NodeName"); private static final SelectStatement byParentName = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND ParentNode" + Statement.NULL_SAFE_EQUALS + "? AND NodeName = ? ORDER BY NodeName"); private static final SelectStatement schematicAccessibleForUser = new SelectStatement<>(table, "SELECT COUNT(DISTINCT NodeId) FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND NodeId = ?"); private static final SelectStatement accessibleByUserTypeInParent = new SelectStatement<>(table, "WITH RECURSIVE RSN AS (SELECT NodeId, ParentNode FROM EffectiveSchematicNode WHERE NodeType = ? AND EffectiveOwner = ? UNION SELECT SN.NodeId, SN.ParentNode FROM RSN, EffectiveSchematicNode SN WHERE SN.NodeId = RSN.ParentNode AND EffectiveOwner = ?) SELECT SN.NodeId, SN.NodeOwner, ? AS EffectiveOwner, SN.NodeName, RSN.ParentNode, SN.LastUpdate, SN.NodeItem, SN.NodeType, SN.NodeRank, SN.ReplaceColor, SN.AllowReplay, SN.NodeFormat FROM RSN INNER JOIN SchematicNode SN ON RSN.NodeId = SN.NodeId WHERE RSN.ParentNode" + Statement.NULL_SAFE_EQUALS + "?"); From b6be12db92643d1053488fbf711221078368a768 Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Tue, 17 Jan 2023 20:49:46 +0100 Subject: [PATCH 61/85] Fix: Optimize generateBreadcrumbs --- src/de/steamwar/sql/SchematicNode.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/de/steamwar/sql/SchematicNode.java b/src/de/steamwar/sql/SchematicNode.java index df7d4b4..06f1eb0 100644 --- a/src/de/steamwar/sql/SchematicNode.java +++ b/src/de/steamwar/sql/SchematicNode.java @@ -501,12 +501,20 @@ public class SchematicNode { public String generateBreadcrumbs(String split) { StringBuilder builder = new StringBuilder(getName()); Optional currentNode = Optional.of(this); - List parents = SchematicNode.parentsOfNode(SteamwarUser.get(effectiveOwner), nodeId); if(currentNode.map(SchematicNode::isDir).orElse(false)) { builder.append(split); } while (currentNode.isPresent()) { - currentNode = currentNode.flatMap(SchematicNode::getOptionalParent).flatMap(integer -> parents.stream().filter(schematicNode -> schematicNode.getId() == integer).findFirst()); + currentNode = currentNode.flatMap(SchematicNode::getOptionalParent).map(integer -> { + NodeMember member = NodeMember.getNodeMember(integer, nodeOwner); + if (member != null) { + integer = member.getParent().orElse(null); + if(integer == null) { + return null; + } + } + return SchematicNode.getSchematicNode(integer); + }); currentNode.ifPresent(node -> builder.insert(0, split).insert(0, node.getName())); } return builder.toString(); @@ -552,7 +560,8 @@ public class SchematicNode { SchematicNode pa = SchematicNode.getNodeFromPath(user, preTab); if (pa == null) return Collections.emptyList(); List nodes = SchematicNode.list(user, pa.getId()); - nodes.forEach(node -> list.add((sws ? "/" : "") + node.generateBreadcrumbs())); + String br = pa.generateBreadcrumbs(); + nodes.forEach(node -> list.add((sws ? "/" : "") + br + node.getName())); } else { List nodes = SchematicNode.list(user, null); nodes.forEach(node -> list.add((sws ? "/" : "") + node.getName() + (node.isDir() ? "/" : ""))); From d959d8426b62497952ac691ab0649930abd5c0c4 Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Tue, 17 Jan 2023 21:06:51 +0100 Subject: [PATCH 62/85] Fix: Optimize getPath and fix generateBreadCrumbs --- src/de/steamwar/sql/SchematicNode.java | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/de/steamwar/sql/SchematicNode.java b/src/de/steamwar/sql/SchematicNode.java index a284f17..9110b18 100644 --- a/src/de/steamwar/sql/SchematicNode.java +++ b/src/de/steamwar/sql/SchematicNode.java @@ -55,7 +55,7 @@ public class SchematicNode { private static final SelectStatement byType = new SelectStatement<>(table, nodeSelector + "WHERE NodeType = ? ORDER BY NodeName"); private static final SelectStatement all = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? ORDER BY NodeName"); private static final SelectStatement list = new SelectStatement<>(table, "SELECT SchematicNode.NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, NM.ParentId AS ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, ReplaceColor, AllowReplay, NodeFormat FROM SchematicNode INNER JOIN NodeMember NM on SchematicNode.NodeId = NM.NodeId WHERE NM.ParentId " + Statement.NULL_SAFE_EQUALS + "? AND NM.UserId = ? UNION ALL SELECT SchematicNode.NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, ReplaceColor, AllowReplay, NodeFormat FROM SchematicNode WHERE ParentNode " + Statement.NULL_SAFE_EQUALS + "? AND NodeOwner = ? ORDER BY NodeName"); - private static final SelectStatement byParentName = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND ParentNode" + Statement.NULL_SAFE_EQUALS + "? AND NodeName = ? ORDER BY NodeName"); + private static final SelectStatement byParentName = new SelectStatement<>(table, "SELECT SchematicNode.NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, NM.ParentId AS ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, ReplaceColor, AllowReplay, NodeFormat FROM SchematicNode INNER JOIN NodeMember NM on SchematicNode.NodeId = NM.NodeId WHERE NM.ParentId " + Statement.NULL_SAFE_EQUALS + "? AND NM.UserId = ? AND SchematicNode.NodeName = ? UNION ALL SELECT SchematicNode.NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, ReplaceColor, AllowReplay, NodeFormat FROM SchematicNode WHERE ParentNode " + Statement.NULL_SAFE_EQUALS + "? AND NodeOwner = ? AND NodeName = ? ORDER BY NodeName"); private static final SelectStatement schematicAccessibleForUser = new SelectStatement<>(table, "SELECT COUNT(DISTINCT NodeId) FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND NodeId = ?"); private static final SelectStatement accessibleByUserTypeInParent = new SelectStatement<>(table, "WITH RECURSIVE RSN AS (SELECT NodeId, ParentNode FROM EffectiveSchematicNode WHERE NodeType = ? AND EffectiveOwner = ? UNION SELECT SN.NodeId, SN.ParentNode FROM RSN, EffectiveSchematicNode SN WHERE SN.NodeId = RSN.ParentNode AND EffectiveOwner = ?) SELECT SN.NodeId, SN.NodeOwner, ? AS EffectiveOwner, SN.NodeName, RSN.ParentNode, SN.LastUpdate, SN.NodeItem, SN.NodeType, SN.NodeRank, SN.ReplaceColor, SN.AllowReplay, SN.NodeFormat FROM RSN INNER JOIN SchematicNode SN ON RSN.NodeId = SN.NodeId WHERE RSN.ParentNode" + Statement.NULL_SAFE_EQUALS + "?"); private static final SelectStatement accessibleByUserType = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND NodeType = ?"); @@ -136,7 +136,7 @@ public class SchematicNode { } public static SchematicNode byParentName(SteamwarUser user, Integer schematicId, String name) { - return byParentName.select(user, schematicId, name); + return byParentName.select(user, schematicId, user, name, user, schematicId, user, name); } public static List accessibleByUserType(SteamwarUser user, SchematicType type) { @@ -505,16 +505,7 @@ public class SchematicNode { builder.append(split); } while (currentNode.isPresent()) { - currentNode = currentNode.flatMap(SchematicNode::getOptionalParent).map(integer -> { - NodeMember member = NodeMember.getNodeMember(integer, nodeOwner); - if (member != null) { - integer = member.getParent().orElse(null); - if(integer == null) { - return null; - } - } - return SchematicNode.getSchematicNode(integer); - }); + currentNode = currentNode.flatMap( schematicNode -> Optional.of(NodeMember.getNodeMember(schematicNode.getId(), nodeOwner)).map(NodeMember::getParent).orElse(schematicNode.getOptionalParent())).map(SchematicNode::getSchematicNode); currentNode.ifPresent(node -> builder.insert(0, split).insert(0, node.getName())); } return builder.toString(); From e880c2944bdec7519497cfc0e50cc40e9da219bc Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Tue, 17 Jan 2023 21:12:11 +0100 Subject: [PATCH 63/85] Fix: Optimize getPath and fix generateBreadCrumbs --- src/de/steamwar/sql/SchematicNode.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/de/steamwar/sql/SchematicNode.java b/src/de/steamwar/sql/SchematicNode.java index 9110b18..ded3a17 100644 --- a/src/de/steamwar/sql/SchematicNode.java +++ b/src/de/steamwar/sql/SchematicNode.java @@ -505,7 +505,7 @@ public class SchematicNode { builder.append(split); } while (currentNode.isPresent()) { - currentNode = currentNode.flatMap( schematicNode -> Optional.of(NodeMember.getNodeMember(schematicNode.getId(), nodeOwner)).map(NodeMember::getParent).orElse(schematicNode.getOptionalParent())).map(SchematicNode::getSchematicNode); + currentNode = currentNode.flatMap(schematicNode -> Optional.ofNullable(NodeMember.getNodeMember(schematicNode.getId(), nodeOwner)).map(NodeMember::getParent).orElse(schematicNode.getOptionalParent())).map(SchematicNode::getSchematicNode); currentNode.ifPresent(node -> builder.insert(0, split).insert(0, node.getName())); } return builder.toString(); From 47cf5d9e91fb659864e1288a043b96b14ab56170 Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Tue, 17 Jan 2023 21:42:57 +0100 Subject: [PATCH 64/85] Fix: Not Own Schematics not schowing --- src/de/steamwar/sql/SchematicNode.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/de/steamwar/sql/SchematicNode.java b/src/de/steamwar/sql/SchematicNode.java index ded3a17..8fbcf6b 100644 --- a/src/de/steamwar/sql/SchematicNode.java +++ b/src/de/steamwar/sql/SchematicNode.java @@ -54,8 +54,8 @@ public class SchematicNode { private static final SelectStatement byOwnerType = new SelectStatement<>(table, nodeSelector + "WHERE NodeOwner = ? AND NodeType = ? ORDER BY NodeName"); private static final SelectStatement byType = new SelectStatement<>(table, nodeSelector + "WHERE NodeType = ? ORDER BY NodeName"); private static final SelectStatement all = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? ORDER BY NodeName"); - private static final SelectStatement list = new SelectStatement<>(table, "SELECT SchematicNode.NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, NM.ParentId AS ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, ReplaceColor, AllowReplay, NodeFormat FROM SchematicNode INNER JOIN NodeMember NM on SchematicNode.NodeId = NM.NodeId WHERE NM.ParentId " + Statement.NULL_SAFE_EQUALS + "? AND NM.UserId = ? UNION ALL SELECT SchematicNode.NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, ReplaceColor, AllowReplay, NodeFormat FROM SchematicNode WHERE ParentNode " + Statement.NULL_SAFE_EQUALS + "? AND NodeOwner = ? ORDER BY NodeName"); - private static final SelectStatement byParentName = new SelectStatement<>(table, "SELECT SchematicNode.NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, NM.ParentId AS ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, ReplaceColor, AllowReplay, NodeFormat FROM SchematicNode INNER JOIN NodeMember NM on SchematicNode.NodeId = NM.NodeId WHERE NM.ParentId " + Statement.NULL_SAFE_EQUALS + "? AND NM.UserId = ? AND SchematicNode.NodeName = ? UNION ALL SELECT SchematicNode.NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, ReplaceColor, AllowReplay, NodeFormat FROM SchematicNode WHERE ParentNode " + Statement.NULL_SAFE_EQUALS + "? AND NodeOwner = ? AND NodeName = ? ORDER BY NodeName"); + private static final SelectStatement list = new SelectStatement<>(table, "SELECT SchematicNode.NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, NM.ParentId AS ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, ReplaceColor, AllowReplay, NodeFormat FROM SchematicNode INNER JOIN NodeMember NM on SchematicNode.NodeId = NM.NodeId WHERE NM.ParentId " + Statement.NULL_SAFE_EQUALS + "? AND NM.UserId = ? UNION ALL SELECT SchematicNode.NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, ReplaceColor, AllowReplay, NodeFormat FROM SchematicNode WHERE (? IS NULL AND ParentNode IS NULL AND NodeOwner = ?) OR (? IS NOT NULL AND ParentNode = ?) ORDER BY NodeName"); + private static final SelectStatement byParentName = new SelectStatement<>(table, "SELECT SchematicNode.NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, NM.ParentId AS ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, ReplaceColor, AllowReplay, NodeFormat FROM SchematicNode INNER JOIN NodeMember NM on SchematicNode.NodeId = NM.NodeId WHERE NM.ParentId " + Statement.NULL_SAFE_EQUALS + "? AND NM.UserId = ? AND SchematicNode.NodeName = ? UNION ALL SELECT SchematicNode.NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, ReplaceColor, AllowReplay, NodeFormat FROM SchematicNode WHERE (? IS NULL AND ParentNode IS NULL AND NodeOwner = ?) OR (? IS NOT NULL AND ParentNode = ?) AND NodeName = ?"); private static final SelectStatement schematicAccessibleForUser = new SelectStatement<>(table, "SELECT COUNT(DISTINCT NodeId) FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND NodeId = ?"); private static final SelectStatement accessibleByUserTypeInParent = new SelectStatement<>(table, "WITH RECURSIVE RSN AS (SELECT NodeId, ParentNode FROM EffectiveSchematicNode WHERE NodeType = ? AND EffectiveOwner = ? UNION SELECT SN.NodeId, SN.ParentNode FROM RSN, EffectiveSchematicNode SN WHERE SN.NodeId = RSN.ParentNode AND EffectiveOwner = ?) SELECT SN.NodeId, SN.NodeOwner, ? AS EffectiveOwner, SN.NodeName, RSN.ParentNode, SN.LastUpdate, SN.NodeItem, SN.NodeType, SN.NodeRank, SN.ReplaceColor, SN.AllowReplay, SN.NodeFormat FROM RSN INNER JOIN SchematicNode SN ON RSN.NodeId = SN.NodeId WHERE RSN.ParentNode" + Statement.NULL_SAFE_EQUALS + "?"); private static final SelectStatement accessibleByUserType = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND NodeType = ?"); @@ -132,11 +132,11 @@ public class SchematicNode { } public static List list(SteamwarUser user, Integer schematicId) { - return list.listSelect(user, schematicId, user, user, schematicId, user); + return list.listSelect(user, schematicId, user, user, schematicId, user, schematicId, schematicId); } public static SchematicNode byParentName(SteamwarUser user, Integer schematicId, String name) { - return byParentName.select(user, schematicId, user, name, user, schematicId, user, name); + return byParentName.select(user, schematicId, user, user, schematicId, user, schematicId, schematicId, name); } public static List accessibleByUserType(SteamwarUser user, SchematicType type) { @@ -552,7 +552,7 @@ public class SchematicNode { if (pa == null) return Collections.emptyList(); List nodes = SchematicNode.list(user, pa.getId()); String br = pa.generateBreadcrumbs(); - nodes.forEach(node -> list.add((sws ? "/" : "") + br + node.getName())); + nodes.forEach(node -> list.add((sws ? "/" : "") + br + node.getName() + (node.isDir() ? "/" : ""))); } else { List nodes = SchematicNode.list(user, null); nodes.forEach(node -> list.add((sws ? "/" : "") + node.getName() + (node.isDir() ? "/" : ""))); From 1a8808fecdefbb875161ec8bb00e24143336ae2c Mon Sep 17 00:00:00 2001 From: Lixfel Date: Tue, 17 Jan 2023 21:48:59 +0100 Subject: [PATCH 65/85] HOTFIX --- src/de/steamwar/sql/SchematicNode.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/de/steamwar/sql/SchematicNode.java b/src/de/steamwar/sql/SchematicNode.java index 8fbcf6b..d65af22 100644 --- a/src/de/steamwar/sql/SchematicNode.java +++ b/src/de/steamwar/sql/SchematicNode.java @@ -136,7 +136,7 @@ public class SchematicNode { } public static SchematicNode byParentName(SteamwarUser user, Integer schematicId, String name) { - return byParentName.select(user, schematicId, user, user, schematicId, user, schematicId, schematicId, name); + return byParentName.select(user, schematicId, user, name, user, schematicId, user, schematicId, schematicId, name); } public static List accessibleByUserType(SteamwarUser user, SchematicType type) { From ee6412c34e2b7671e024875e5ccea4296a5fe3bc Mon Sep 17 00:00:00 2001 From: Lixfel Date: Tue, 17 Jan 2023 21:59:04 +0100 Subject: [PATCH 66/85] HOTFIX --- src/de/steamwar/sql/SchematicNode.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/de/steamwar/sql/SchematicNode.java b/src/de/steamwar/sql/SchematicNode.java index d65af22..c348460 100644 --- a/src/de/steamwar/sql/SchematicNode.java +++ b/src/de/steamwar/sql/SchematicNode.java @@ -55,7 +55,7 @@ public class SchematicNode { private static final SelectStatement byType = new SelectStatement<>(table, nodeSelector + "WHERE NodeType = ? ORDER BY NodeName"); private static final SelectStatement all = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? ORDER BY NodeName"); private static final SelectStatement list = new SelectStatement<>(table, "SELECT SchematicNode.NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, NM.ParentId AS ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, ReplaceColor, AllowReplay, NodeFormat FROM SchematicNode INNER JOIN NodeMember NM on SchematicNode.NodeId = NM.NodeId WHERE NM.ParentId " + Statement.NULL_SAFE_EQUALS + "? AND NM.UserId = ? UNION ALL SELECT SchematicNode.NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, ReplaceColor, AllowReplay, NodeFormat FROM SchematicNode WHERE (? IS NULL AND ParentNode IS NULL AND NodeOwner = ?) OR (? IS NOT NULL AND ParentNode = ?) ORDER BY NodeName"); - private static final SelectStatement byParentName = new SelectStatement<>(table, "SELECT SchematicNode.NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, NM.ParentId AS ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, ReplaceColor, AllowReplay, NodeFormat FROM SchematicNode INNER JOIN NodeMember NM on SchematicNode.NodeId = NM.NodeId WHERE NM.ParentId " + Statement.NULL_SAFE_EQUALS + "? AND NM.UserId = ? AND SchematicNode.NodeName = ? UNION ALL SELECT SchematicNode.NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, ReplaceColor, AllowReplay, NodeFormat FROM SchematicNode WHERE (? IS NULL AND ParentNode IS NULL AND NodeOwner = ?) OR (? IS NOT NULL AND ParentNode = ?) AND NodeName = ?"); + private static final SelectStatement byParentName = new SelectStatement<>(table, "SELECT SchematicNode.NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, NM.ParentId AS ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, ReplaceColor, AllowReplay, NodeFormat FROM SchematicNode INNER JOIN NodeMember NM on SchematicNode.NodeId = NM.NodeId WHERE NM.ParentId " + Statement.NULL_SAFE_EQUALS + "? AND NM.UserId = ? AND SchematicNode.NodeName = ? UNION ALL SELECT SchematicNode.NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, ReplaceColor, AllowReplay, NodeFormat FROM SchematicNode WHERE ((? IS NULL AND ParentNode IS NULL AND NodeOwner = ?) OR (? IS NOT NULL AND ParentNode = ?)) AND NodeName = ?"); private static final SelectStatement schematicAccessibleForUser = new SelectStatement<>(table, "SELECT COUNT(DISTINCT NodeId) FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND NodeId = ?"); private static final SelectStatement accessibleByUserTypeInParent = new SelectStatement<>(table, "WITH RECURSIVE RSN AS (SELECT NodeId, ParentNode FROM EffectiveSchematicNode WHERE NodeType = ? AND EffectiveOwner = ? UNION SELECT SN.NodeId, SN.ParentNode FROM RSN, EffectiveSchematicNode SN WHERE SN.NodeId = RSN.ParentNode AND EffectiveOwner = ?) SELECT SN.NodeId, SN.NodeOwner, ? AS EffectiveOwner, SN.NodeName, RSN.ParentNode, SN.LastUpdate, SN.NodeItem, SN.NodeType, SN.NodeRank, SN.ReplaceColor, SN.AllowReplay, SN.NodeFormat FROM RSN INNER JOIN SchematicNode SN ON RSN.NodeId = SN.NodeId WHERE RSN.ParentNode" + Statement.NULL_SAFE_EQUALS + "?"); private static final SelectStatement accessibleByUserType = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND NodeType = ?"); From 04577cc076ee5d53dc1eea4fc338df8fbe9eb722 Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Tue, 17 Jan 2023 22:08:51 +0100 Subject: [PATCH 67/85] Fix: Performance of byIdAndUser --- src/de/steamwar/sql/SchematicNode.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/de/steamwar/sql/SchematicNode.java b/src/de/steamwar/sql/SchematicNode.java index d65af22..e465105 100644 --- a/src/de/steamwar/sql/SchematicNode.java +++ b/src/de/steamwar/sql/SchematicNode.java @@ -54,12 +54,12 @@ public class SchematicNode { private static final SelectStatement byOwnerType = new SelectStatement<>(table, nodeSelector + "WHERE NodeOwner = ? AND NodeType = ? ORDER BY NodeName"); private static final SelectStatement byType = new SelectStatement<>(table, nodeSelector + "WHERE NodeType = ? ORDER BY NodeName"); private static final SelectStatement all = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? ORDER BY NodeName"); - private static final SelectStatement list = new SelectStatement<>(table, "SELECT SchematicNode.NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, NM.ParentId AS ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, ReplaceColor, AllowReplay, NodeFormat FROM SchematicNode INNER JOIN NodeMember NM on SchematicNode.NodeId = NM.NodeId WHERE NM.ParentId " + Statement.NULL_SAFE_EQUALS + "? AND NM.UserId = ? UNION ALL SELECT SchematicNode.NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, ReplaceColor, AllowReplay, NodeFormat FROM SchematicNode WHERE (? IS NULL AND ParentNode IS NULL AND NodeOwner = ?) OR (? IS NOT NULL AND ParentNode = ?) ORDER BY NodeName"); - private static final SelectStatement byParentName = new SelectStatement<>(table, "SELECT SchematicNode.NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, NM.ParentId AS ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, ReplaceColor, AllowReplay, NodeFormat FROM SchematicNode INNER JOIN NodeMember NM on SchematicNode.NodeId = NM.NodeId WHERE NM.ParentId " + Statement.NULL_SAFE_EQUALS + "? AND NM.UserId = ? AND SchematicNode.NodeName = ? UNION ALL SELECT SchematicNode.NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, ReplaceColor, AllowReplay, NodeFormat FROM SchematicNode WHERE (? IS NULL AND ParentNode IS NULL AND NodeOwner = ?) OR (? IS NOT NULL AND ParentNode = ?) AND NodeName = ?"); + private static final SelectStatement list = new SelectStatement<>(table, "SELECT SchematicNode.NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, NM.ParentId AS ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, ReplaceColor, AllowReplay, NodeFormat FROM SchematicNode INNER JOIN NodeMember NM on SchematicNode.NodeId = NM.NodeId WHERE NM.ParentId " + Statement.NULL_SAFE_EQUALS + "? AND NM.UserId = ? UNION ALL SELECT SchematicNode.NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, ReplaceColor, AllowReplay, NodeFormat FROM SchematicNode WHERE (? IS NULL AND ParentNode IS NULL AND NodeOwner = ?) OR (? IS NOT NULL AND ParentNode = ?) ORDER BY NodeName"); + private static final SelectStatement byParentName = new SelectStatement<>(table, "SELECT SchematicNode.NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, NM.ParentId AS ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, ReplaceColor, AllowReplay, NodeFormat FROM SchematicNode INNER JOIN NodeMember NM on SchematicNode.NodeId = NM.NodeId WHERE NM.ParentId " + Statement.NULL_SAFE_EQUALS + "? AND NM.UserId = ? AND SchematicNode.NodeName = ? UNION ALL SELECT SchematicNode.NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, ReplaceColor, AllowReplay, NodeFormat FROM SchematicNode WHERE ((? IS NULL AND ParentNode IS NULL AND NodeOwner = ?) OR (? IS NOT NULL AND ParentNode = ?)) AND NodeName = ?"); private static final SelectStatement schematicAccessibleForUser = new SelectStatement<>(table, "SELECT COUNT(DISTINCT NodeId) FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND NodeId = ?"); private static final SelectStatement accessibleByUserTypeInParent = new SelectStatement<>(table, "WITH RECURSIVE RSN AS (SELECT NodeId, ParentNode FROM EffectiveSchematicNode WHERE NodeType = ? AND EffectiveOwner = ? UNION SELECT SN.NodeId, SN.ParentNode FROM RSN, EffectiveSchematicNode SN WHERE SN.NodeId = RSN.ParentNode AND EffectiveOwner = ?) SELECT SN.NodeId, SN.NodeOwner, ? AS EffectiveOwner, SN.NodeName, RSN.ParentNode, SN.LastUpdate, SN.NodeItem, SN.NodeType, SN.NodeRank, SN.ReplaceColor, SN.AllowReplay, SN.NodeFormat FROM RSN INNER JOIN SchematicNode SN ON RSN.NodeId = SN.NodeId WHERE RSN.ParentNode" + Statement.NULL_SAFE_EQUALS + "?"); private static final SelectStatement accessibleByUserType = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND NodeType = ?"); - private static final SelectStatement byIdAndUser = new SelectStatement<>(table, "SELECT * FROM EffectiveSchematicNode WHERE EffectiveOwner = ? AND NodeId = ?"); + private static final SelectStatement byIdAndUser = new SelectStatement<>(table, "SELECT NodeId, NodeOwner, ? AS EffectiveOwner, NodeName, ParentNode, LastUpdate, NodeItem, NodeType, NodeRank, ReplaceColor, AllowReplay, NodeFormat FROM SchematicNode WHERE NodeId = ?"); private static final SelectStatement allParentsOfNode = new SelectStatement<>(table, "WITH RECURSIVE R AS (SELECT NodeId, ParentNode FROM EffectiveSchematicNode WHERE NodeId = ? AND EffectiveOwner = ? UNION SELECT E.NodeId, E.ParentNode FROM R, EffectiveSchematicNode E WHERE R.ParentNode = E.NodeId AND E.EffectiveOwner = ?) SELECT SN.NodeId, SN.NodeOwner, ? AS EffectiveOwner, SN.NodeName, R.ParentNode, SN.LastUpdate, SN.NodeItem, SN.NodeType, SN.NodeRank, SN.ReplaceColor, SN.AllowReplay, SN.NodeFormat FROM R INNER JOIN SchematicNode SN ON SN.NodeId = R.NodeId"); static { From 58d6506a44e4988955a4784ae8174200d0973883 Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Tue, 17 Jan 2023 22:52:47 +0100 Subject: [PATCH 68/85] Fix: Wrong Owner in Breadcrumbs --- src/de/steamwar/sql/SchematicNode.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/de/steamwar/sql/SchematicNode.java b/src/de/steamwar/sql/SchematicNode.java index 88dbaa4..110d3d9 100644 --- a/src/de/steamwar/sql/SchematicNode.java +++ b/src/de/steamwar/sql/SchematicNode.java @@ -505,7 +505,7 @@ public class SchematicNode { builder.append(split); } while (currentNode.isPresent()) { - currentNode = currentNode.flatMap(schematicNode -> Optional.ofNullable(NodeMember.getNodeMember(schematicNode.getId(), nodeOwner)).map(NodeMember::getParent).orElse(schematicNode.getOptionalParent())).map(SchematicNode::getSchematicNode); + currentNode = currentNode.flatMap(schematicNode -> Optional.ofNullable(NodeMember.getNodeMember(schematicNode.getId(), effectiveOwner)).map(NodeMember::getParent).orElse(schematicNode.getOptionalParent())).map(SchematicNode::getSchematicNode); currentNode.ifPresent(node -> builder.insert(0, split).insert(0, node.getName())); } return builder.toString(); From d12fbbd339f6f34108301aa08d1377eceea250a5 Mon Sep 17 00:00:00 2001 From: Lixfel Date: Fri, 20 Jan 2023 11:08:31 +0100 Subject: [PATCH 69/85] Potential SQL connection churn fix Signed-off-by: Lixfel --- src/de/steamwar/sql/internal/Statement.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/de/steamwar/sql/internal/Statement.java b/src/de/steamwar/sql/internal/Statement.java index f78f0d4..e842d5f 100644 --- a/src/de/steamwar/sql/internal/Statement.java +++ b/src/de/steamwar/sql/internal/Statement.java @@ -234,7 +234,7 @@ public class Statement implements AutoCloseable { private static Connection aquireConnection() { synchronized (connections) { - if(connections.isEmpty() && connectionBudget == 0) + while(connections.isEmpty() && connectionBudget == 0) waitOnConnections(); if(!connections.isEmpty()) { From a738dfa1c5540afa8ef8db04f81cd7e6d06a19ce Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Fri, 20 Jan 2023 19:38:51 +0100 Subject: [PATCH 70/85] Fix: Multiple //copy Schematics --- src/de/steamwar/sql/SchematicNode.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/de/steamwar/sql/SchematicNode.java b/src/de/steamwar/sql/SchematicNode.java index 110d3d9..0e64522 100644 --- a/src/de/steamwar/sql/SchematicNode.java +++ b/src/de/steamwar/sql/SchematicNode.java @@ -48,7 +48,7 @@ public class SchematicNode { private static final Statement delete = table.delete(Table.PRIMARY); private static final SelectStatement byId = new SelectStatement<>(table, nodeSelector + "WHERE NodeId = ?"); - private static final SelectStatement byOwnerNameParent = new SelectStatement<>(table, nodeSelector + "WHERE NodeOwner = ? AND NodeName = ? AND ParentNode = ?"); + private static final SelectStatement byOwnerNameParent = new SelectStatement<>(table, nodeSelector + "WHERE NodeOwner = ? AND NodeName = ? AND ParentNode " + Statement.NULL_SAFE_EQUALS + "?"); private static final SelectStatement byParent = new SelectStatement<>(table, nodeSelector + "WHERE ParentNode" + Statement.NULL_SAFE_EQUALS + "? ORDER BY NodeName"); private static final SelectStatement dirsByParent = new SelectStatement<>(table, nodeSelector + "WHERE ParentNode" + Statement.NULL_SAFE_EQUALS + "? AND NodeType is NULL ORDER BY NodeName"); private static final SelectStatement byOwnerType = new SelectStatement<>(table, nodeSelector + "WHERE NodeOwner = ? AND NodeType = ? ORDER BY NodeName"); From 9992dfaf181b77526472616d6e7124bd3960509d Mon Sep 17 00:00:00 2001 From: yoyosource Date: Sat, 21 Jan 2023 17:06:56 +0100 Subject: [PATCH 71/85] Add better caching key normalization --- src/de/steamwar/command/AbstractTypeMapper.java | 9 +++++++++ src/de/steamwar/command/CommandPart.java | 2 +- src/de/steamwar/command/TabCompletionCache.java | 7 +++++-- testsrc/de/steamwar/command/CacheCommand.java | 6 +++++- testsrc/de/steamwar/command/CacheCommandTest.java | 8 ++++++++ 5 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/de/steamwar/command/AbstractTypeMapper.java b/src/de/steamwar/command/AbstractTypeMapper.java index 0363cd0..ab9d116 100644 --- a/src/de/steamwar/command/AbstractTypeMapper.java +++ b/src/de/steamwar/command/AbstractTypeMapper.java @@ -50,4 +50,13 @@ public interface AbstractTypeMapper extends AbstractValidator { default Collection tabCompletes(K sender, PreviousArguments previousArguments, String s) { return tabCompletes(sender, previousArguments.userArgs, s); } + + /** + * Normalize the cache key by sender and user provided argument.
+ * Note: The CommandSender can be null!
+ * Returning null and the empty string are equivalent. + */ + default String normalize(K sender, String s) { + return s; + } } diff --git a/src/de/steamwar/command/CommandPart.java b/src/de/steamwar/command/CommandPart.java index 5a056d2..926ad2a 100644 --- a/src/de/steamwar/command/CommandPart.java +++ b/src/de/steamwar/command/CommandPart.java @@ -165,7 +165,7 @@ class CommandPart { } private Collection tabCompletes(T sender, String[] args, List mappedArgs, int startIndex) { - return TabCompletionCache.tabComplete(sender, typeMapper, command, () -> { + return TabCompletionCache.tabComplete(sender, args[startIndex], (AbstractTypeMapper) typeMapper, command, () -> { try { return typeMapper.tabCompletes(sender, new PreviousArguments(Arrays.copyOf(args, startIndex), mappedArgs.toArray()), args[startIndex]); } catch (Throwable e) { diff --git a/src/de/steamwar/command/TabCompletionCache.java b/src/de/steamwar/command/TabCompletionCache.java index 6f2d0ff..4e50a18 100644 --- a/src/de/steamwar/command/TabCompletionCache.java +++ b/src/de/steamwar/command/TabCompletionCache.java @@ -51,6 +51,7 @@ public class TabCompletionCache { @AllArgsConstructor private static class Key { private Object sender; + private String arg; private AbstractTypeMapper typeMapper; } @@ -61,9 +62,11 @@ public class TabCompletionCache { private Collection tabCompletions; } - Collection tabComplete(Object sender, AbstractTypeMapper typeMapper, AbstractSWCommand command, Supplier> tabCompleteSupplier) { + Collection tabComplete(Object sender, String arg, AbstractTypeMapper typeMapper, AbstractSWCommand command, Supplier> tabCompleteSupplier) { if (!cached.contains(typeMapper)) return tabCompleteSupplier.get(); - Key key = global.contains(typeMapper) ? new Key(null, typeMapper) : new Key(sender, typeMapper); + String normalizedArg = typeMapper.normalize(sender, arg); + if (normalizedArg == null) normalizedArg = ""; + Key key = global.contains(typeMapper) ? new Key(null, normalizedArg, typeMapper) : new Key(sender, normalizedArg, typeMapper); TabCompletions tabCompletions = tabCompletionCache.computeIfAbsent(key, ignore -> { return new TabCompletions(command, System.currentTimeMillis(), tabCompleteSupplier.get()); }); diff --git a/testsrc/de/steamwar/command/CacheCommand.java b/testsrc/de/steamwar/command/CacheCommand.java index 5057ffd..ee7a4cc 100644 --- a/testsrc/de/steamwar/command/CacheCommand.java +++ b/testsrc/de/steamwar/command/CacheCommand.java @@ -41,7 +41,6 @@ public class CacheCommand extends TestSWCommand { @Cached @Mapper(value = "int", local = true) public AbstractTypeMapper typeMapper() { - System.out.println("TypeMapper register"); return new TestTypeMapper() { @Override public Integer map(String sender, PreviousArguments previousArguments, String s) { @@ -52,6 +51,11 @@ public class CacheCommand extends TestSWCommand { public Collection tabCompletes(String sender, PreviousArguments previousArguments, String s) { return Arrays.asList(count.getAndIncrement() + ""); } + + @Override + public String normalize(String sender, String s) { + return ""; + } }; } } diff --git a/testsrc/de/steamwar/command/CacheCommandTest.java b/testsrc/de/steamwar/command/CacheCommandTest.java index 122528f..4a6e183 100644 --- a/testsrc/de/steamwar/command/CacheCommandTest.java +++ b/testsrc/de/steamwar/command/CacheCommandTest.java @@ -36,6 +36,14 @@ public class CacheCommandTest { assertThat(tabCompletions1, is(equalTo(tabCompletions2))); } + @Test + public void testCachingWithDifferentMessages() { + CacheCommand cmd = new CacheCommand(); + List tabCompletions1 = cmd.tabComplete("test", "", new String[]{""}); + List tabCompletions2 = cmd.tabComplete("test", "", new String[]{"0"}); + assertThat(tabCompletions1, is(equalTo(tabCompletions2))); + } + @Test public void testCachingWithDifferentSenders() { CacheCommand cmd = new CacheCommand(); From 8100f9c044c8d067380f3d51479032974c00f565 Mon Sep 17 00:00:00 2001 From: yoyosource Date: Sat, 21 Jan 2023 17:07:48 +0100 Subject: [PATCH 72/85] Retain current behaviour --- src/de/steamwar/command/AbstractTypeMapper.java | 2 +- testsrc/de/steamwar/command/CacheCommand.java | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/de/steamwar/command/AbstractTypeMapper.java b/src/de/steamwar/command/AbstractTypeMapper.java index ab9d116..040f303 100644 --- a/src/de/steamwar/command/AbstractTypeMapper.java +++ b/src/de/steamwar/command/AbstractTypeMapper.java @@ -57,6 +57,6 @@ public interface AbstractTypeMapper extends AbstractValidator { * Returning null and the empty string are equivalent. */ default String normalize(K sender, String s) { - return s; + return null; } } diff --git a/testsrc/de/steamwar/command/CacheCommand.java b/testsrc/de/steamwar/command/CacheCommand.java index ee7a4cc..7cfdb72 100644 --- a/testsrc/de/steamwar/command/CacheCommand.java +++ b/testsrc/de/steamwar/command/CacheCommand.java @@ -51,11 +51,6 @@ public class CacheCommand extends TestSWCommand { public Collection tabCompletes(String sender, PreviousArguments previousArguments, String s) { return Arrays.asList(count.getAndIncrement() + ""); } - - @Override - public String normalize(String sender, String s) { - return ""; - } }; } } From b4df616bc462540fd669e313b32facc42be9f4cb Mon Sep 17 00:00:00 2001 From: yoyosource Date: Sat, 21 Jan 2023 21:46:48 +0100 Subject: [PATCH 73/85] Fix some tab completion cache stuff --- src/de/steamwar/command/CommandPart.java | 2 +- src/de/steamwar/command/TabCompletionCache.java | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/de/steamwar/command/CommandPart.java b/src/de/steamwar/command/CommandPart.java index 926ad2a..b902892 100644 --- a/src/de/steamwar/command/CommandPart.java +++ b/src/de/steamwar/command/CommandPart.java @@ -165,7 +165,7 @@ class CommandPart { } private Collection tabCompletes(T sender, String[] args, List mappedArgs, int startIndex) { - return TabCompletionCache.tabComplete(sender, args[startIndex], (AbstractTypeMapper) typeMapper, command, () -> { + return TabCompletionCache.tabComplete(sender, args[startIndex], (AbstractTypeMapper) typeMapper, () -> { try { return typeMapper.tabCompletes(sender, new PreviousArguments(Arrays.copyOf(args, startIndex), mappedArgs.toArray()), args[startIndex]); } catch (Throwable e) { diff --git a/src/de/steamwar/command/TabCompletionCache.java b/src/de/steamwar/command/TabCompletionCache.java index 4e50a18..551ce19 100644 --- a/src/de/steamwar/command/TabCompletionCache.java +++ b/src/de/steamwar/command/TabCompletionCache.java @@ -57,22 +57,23 @@ public class TabCompletionCache { @AllArgsConstructor private static class TabCompletions { - private AbstractSWCommand command; private long timestamp; private Collection tabCompletions; } - Collection tabComplete(Object sender, String arg, AbstractTypeMapper typeMapper, AbstractSWCommand command, Supplier> tabCompleteSupplier) { + Collection tabComplete(Object sender, String arg, AbstractTypeMapper typeMapper, Supplier> tabCompleteSupplier) { if (!cached.contains(typeMapper)) return tabCompleteSupplier.get(); + String normalizedArg = typeMapper.normalize(sender, arg); if (normalizedArg == null) normalizedArg = ""; - Key key = global.contains(typeMapper) ? new Key(null, normalizedArg, typeMapper) : new Key(sender, normalizedArg, typeMapper); + Key key = new Key(global.contains(typeMapper) ? null : sender, normalizedArg, typeMapper); + TabCompletions tabCompletions = tabCompletionCache.computeIfAbsent(key, ignore -> { - return new TabCompletions(command, System.currentTimeMillis(), tabCompleteSupplier.get()); + return new TabCompletions(System.currentTimeMillis(), tabCompleteSupplier.get()); }); - if (tabCompletions.command != command || System.currentTimeMillis() - tabCompletions.timestamp > cacheDuration.get(typeMapper)) { - tabCompletions = new TabCompletions(command, System.currentTimeMillis(), tabCompleteSupplier.get()); - tabCompletionCache.put(key, tabCompletions); + + if (System.currentTimeMillis() - tabCompletions.timestamp > cacheDuration.get(typeMapper)) { + tabCompletions.tabCompletions = tabCompleteSupplier.get(); } tabCompletions.timestamp = System.currentTimeMillis(); return tabCompletions.tabCompletions; From 2cbd7b8117216dd9306c71edcfe900c762e7643e Mon Sep 17 00:00:00 2001 From: yoyosource Date: Sat, 21 Jan 2023 22:12:56 +0100 Subject: [PATCH 74/85] Add inverting of validator, removes more boilerplate --- src/de/steamwar/command/AbstractSWCommand.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/de/steamwar/command/AbstractSWCommand.java b/src/de/steamwar/command/AbstractSWCommand.java index ef0ccf1..86a00f5 100644 --- a/src/de/steamwar/command/AbstractSWCommand.java +++ b/src/de/steamwar/command/AbstractSWCommand.java @@ -354,17 +354,21 @@ public abstract class AbstractSWCommand { boolean local() default false; + boolean invert() default false; + class Handler implements AbstractValidator { private AbstractValidator inner; + private boolean invert; public Handler(AbstractSWCommand.Validator validator, Class clazz, Map> localValidator) { inner = (AbstractValidator) SWCommandUtils.getValidator(validator, clazz, localValidator); + invert = validator.invert(); } @Override public boolean validate(T sender, Object value, MessageSender messageSender) { - return inner.validate(sender, value, messageSender); + return inner.validate(sender, value, messageSender) ^ invert; } } } From 6cbee16c19bd60e3fa11c5bc24c83d5c27d156b0 Mon Sep 17 00:00:00 2001 From: yoyosource Date: Sun, 22 Jan 2023 13:45:22 +0100 Subject: [PATCH 75/85] Remove Quotable because it was a dumb idea to add Add AbstractSWCommand.Length and AbstractSWCommand.ArrayLength --- .../steamwar/command/AbstractSWCommand.java | 177 +++++++++++------- src/de/steamwar/command/CommandMetaData.java | 8 +- src/de/steamwar/command/SWCommandUtils.java | 10 +- src/de/steamwar/command/SubCommand.java | 8 - .../de/steamwar/command/ArgumentCommand.java | 2 +- .../steamwar/command/ArgumentCommandTest.java | 23 --- 6 files changed, 117 insertions(+), 111 deletions(-) diff --git a/src/de/steamwar/command/AbstractSWCommand.java b/src/de/steamwar/command/AbstractSWCommand.java index 86a00f5..9b0264f 100644 --- a/src/de/steamwar/command/AbstractSWCommand.java +++ b/src/de/steamwar/command/AbstractSWCommand.java @@ -21,12 +21,14 @@ package de.steamwar.command; import java.lang.annotation.*; import java.lang.reflect.Array; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; import java.util.function.Function; +import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -67,48 +69,11 @@ public abstract class AbstractSWCommand { protected void sendMessage(T sender, String message, Object[] args) {} - private String[] quote(String[] args) { - List list = new ArrayList<>(); - StringBuilder builder = new StringBuilder(); - boolean quote = false; - for (String arg : args) { - if (arg.startsWith("\"") && arg.endsWith("\"")) { - list.add(arg); - continue; - } - if (arg.startsWith("\"")) { - quote = true; - builder.append(arg); - continue; - } - if (arg.endsWith("\"")) { - quote = false; - builder.append(" ").append(arg); - list.add(builder.toString()); - builder = new StringBuilder(); - continue; - } - if (quote) { - builder.append(" ").append(arg); - continue; - } - list.add(arg); - } - if (quote) { - builder.append("\""); - } - if (builder.length() > 0) { - list.add(builder.toString()); - } - return list.toArray(new String[0]); - } - protected final void execute(T sender, String alias, String[] args) { initialize(); - String[] finalArgs = quote(args); List errors = new ArrayList<>(); try { - if (!commandList.stream().anyMatch(s -> s.invoke(errors::add, sender, alias, finalArgs))) { + if (!commandList.stream().anyMatch(s -> s.invoke(errors::add, sender, alias, args))) { errors.forEach(Runnable::run); } } catch (CommandFrameworkException e) { @@ -119,11 +84,10 @@ public abstract class AbstractSWCommand { protected final List tabComplete(T sender, String alias, String[] args) throws IllegalArgumentException { initialize(); - String[] finalArgs = quote(args); String string = args[args.length - 1].toLowerCase(); return commandList.stream() .filter(s -> !s.noTabComplete) - .map(s -> s.tabComplete(sender, finalArgs)) + .map(s -> s.tabComplete(sender, args)) .filter(Objects::nonNull) .flatMap(Collection::stream) .filter(s -> !s.isEmpty()) @@ -185,9 +149,16 @@ public abstract class AbstractSWCommand { private boolean validateMethod(Method method) { if (!checkType(method.getAnnotations(), method.getReturnType(), annotation -> { CommandMetaData.Method methodMetaData = annotation.annotationType().getAnnotation(CommandMetaData.Method.class); - if (methodMetaData == null) return null; - if (method.getParameterCount() > methodMetaData.maxParameterCount() || method.getParameterCount() < methodMetaData.minParameterCount()) return new Class[0]; - return methodMetaData.value(); + if (methodMetaData == null) return aClass -> true; + if (method.getParameterCount() > methodMetaData.maxParameterCount() || method.getParameterCount() < methodMetaData.minParameterCount()) return aClass -> false; + return aClass -> { + Class[] types = methodMetaData.value(); + if (types == null) return true; + for (Class type : types) { + if (type.isAssignableFrom(aClass)) return true; + } + return false; + }; }, "The method '" + method + "'")) return false; boolean valid = true; for (Parameter parameter : method.getParameters()) { @@ -195,26 +166,32 @@ public abstract class AbstractSWCommand { if (parameter.isVarArgs()) type = type.getComponentType(); if (!checkType(parameter.getAnnotations(), type, annotation -> { CommandMetaData.Parameter parameterMetaData = annotation.annotationType().getAnnotation(CommandMetaData.Parameter.class); - if (parameterMetaData == null) return null; - return parameterMetaData.value(); + if (parameterMetaData == null) return aClass -> true; + Class handler = parameterMetaData.handler(); + if (Predicate.class.isAssignableFrom(handler)) { + try { + return (Predicate>) handler.getConstructor().newInstance(); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + } + } + return aClass -> { + Class[] types = parameterMetaData.value(); + if (types == null) return true; + for (Class current : types) { + if (current.isAssignableFrom(aClass)) return true; + } + return false; + }; }, "The parameter '" + parameter + "'")) valid = false; } return valid; } - private boolean checkType(Annotation[] annotations, Class clazz, Function[]> toApplicableTypes, String warning) { + private boolean checkType(Annotation[] annotations, Class clazz, Function>> toApplicableTypes, String warning) { boolean valid = true; for (Annotation annotation : annotations) { - Class[] types = toApplicableTypes.apply(annotation); - if (types == null) continue; - boolean applicable = false; - for (Class type : types) { - if (type.isAssignableFrom(clazz)) { - applicable = true; - break; - } - } - if (!applicable) { + Predicate> predicate = toApplicableTypes.apply(annotation); + if (!predicate.test(clazz)) { commandSystemWarning(() -> warning + " is using an unsupported annotation of type '" + annotation.annotationType().getName() + "'"); valid = false; } @@ -506,15 +483,6 @@ public abstract class AbstractSWCommand { protected @interface AllowNull { } - /** - * This annotation is used to mark a String to be quotable with multiple words. - */ - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.PARAMETER}) - @CommandMetaData.Parameter({String.class}) - protected @interface Quotable { - } - @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) @CommandMetaData.Parameter({int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class}) @@ -586,4 +554,83 @@ public abstract class AbstractSWCommand { throw new IllegalArgumentException(type + " annotation is not supported for " + clazz); } } + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.PARAMETER}) + @CommandMetaData.ImplicitTypeMapper(handler = Length.Handler.class) + protected @interface Length { + int min() default 0; + int max() default Integer.MAX_VALUE; + + class Handler implements AbstractTypeMapper { + + private int min; + private int max; + private AbstractTypeMapper inner; + + public Handler(Length length, AbstractTypeMapper inner) { + this.min = length.min(); + this.max = length.max(); + this.inner = inner; + } + + @Override + public Object map(T sender, PreviousArguments previousArguments, String s) { + if (s.length() < min || s.length() > max) return null; + return inner.map(sender, previousArguments, s); + } + + @Override + public Collection tabCompletes(T sender, PreviousArguments previousArguments, String s) { + List tabCompletes = inner.tabCompletes(sender, previousArguments, s) + .stream() + .filter(str -> str.length() >= min) + .map(str -> str.substring(0, Math.min(str.length(), max))) + .collect(Collectors.toList()); + if (s.length() < min) { + tabCompletes.add(0, s); + } + return tabCompletes; + } + + @Override + public String normalize(T sender, String s) { + return inner.normalize(sender, s); + } + } + } + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.PARAMETER}) + @CommandMetaData.Parameter(handler = ArrayLength.Type.class) + @CommandMetaData.ImplicitValidator(handler = ArrayLength.Handler.class, order = 2) + protected @interface ArrayLength { + int min() default 0; + int max() default Integer.MAX_VALUE; + + class Type implements Predicate> { + @Override + public boolean test(Class clazz) { + return clazz.isArray(); + } + } + + class Handler implements AbstractValidator { + + private int min; + private int max; + + public Handler(ArrayLength arrayLength) { + this.min = arrayLength.min(); + this.max = arrayLength.max(); + } + + @Override + public boolean validate(T sender, Object value, MessageSender messageSender) { + if (value == null) return false; + int length = Array.getLength(value); + return length >= min && length <= max; + } + } + } } diff --git a/src/de/steamwar/command/CommandMetaData.java b/src/de/steamwar/command/CommandMetaData.java index fdb1ea2..33909ac 100644 --- a/src/de/steamwar/command/CommandMetaData.java +++ b/src/de/steamwar/command/CommandMetaData.java @@ -19,10 +19,7 @@ package de.steamwar.command; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; +import java.lang.annotation.*; public @interface CommandMetaData { @@ -43,7 +40,8 @@ public @interface CommandMetaData { @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) @interface Parameter { - Class[] value(); + Class[] value() default {}; + Class handler() default void.class; } /** diff --git a/src/de/steamwar/command/SWCommandUtils.java b/src/de/steamwar/command/SWCommandUtils.java index 32b7fcc..c06d584 100644 --- a/src/de/steamwar/command/SWCommandUtils.java +++ b/src/de/steamwar/command/SWCommandUtils.java @@ -61,15 +61,7 @@ public class SWCommandUtils { addMapper(double.class, Double.class, createMapper(numberMapper(Double::parseDouble), numberCompleter(Double::parseDouble, true))); addMapper(int.class, Integer.class, createMapper(numberMapper(Integer::parseInt), numberCompleter(Integer::parseInt, false))); addMapper(long.class, Long.class, createMapper(numberMapper(Long::parseLong), numberCompleter(Long::parseLong, false))); - MAPPER_FUNCTIONS.put(String.class.getTypeName(), createMapper(s -> { - if (s.startsWith("\"") && s.endsWith("\"")) { - return s.substring(1, s.length() - 1); - } - if (s.startsWith("'") && s.endsWith("'")) { - return s.substring(1, s.length() - 1); - } - return s; - }, Collections::singletonList)); + MAPPER_FUNCTIONS.put(String.class.getTypeName(), createMapper(s -> s, Collections::singletonList)); } public static , K, V> void init(SWTypeMapperCreator swTypeMapperCreator) { diff --git a/src/de/steamwar/command/SubCommand.java b/src/de/steamwar/command/SubCommand.java index 961cf29..d74423b 100644 --- a/src/de/steamwar/command/SubCommand.java +++ b/src/de/steamwar/command/SubCommand.java @@ -150,9 +150,6 @@ public class SubCommand { CommandPart commandPart = new CommandPart<>(command, typeMapper, varArgType, optionalValue != null ? optionalValue.value() : null, parameter, i); commandPart.setOnlyUseIfNoneIsGiven(optionalValue != null && optionalValue.onlyUINIG()); - if (parameter.getAnnotation(AbstractSWCommand.Quotable.class) == null) { - commandPart.addValidator((AbstractValidator) STRING_SPACE_FILTER); - } handleImplicitTypeValidator(parameter, commandPart, localValidator); if (parameter.getAnnotation(AbstractSWCommand.AllowNull.class) == null) { commandPart.addValidator((AbstractValidator) NULL_FILTER); @@ -270,9 +267,4 @@ public class SubCommand { } private static final AbstractValidator NULL_FILTER = (sender, value, messageSender) -> value != null; - - private static final AbstractValidator STRING_SPACE_FILTER = (sender, value, messageSender) -> { - if (!(value instanceof String)) return true; - return !((String) value).contains(" "); - }; } diff --git a/testsrc/de/steamwar/command/ArgumentCommand.java b/testsrc/de/steamwar/command/ArgumentCommand.java index ad1c1ec..a107270 100644 --- a/testsrc/de/steamwar/command/ArgumentCommand.java +++ b/testsrc/de/steamwar/command/ArgumentCommand.java @@ -54,7 +54,7 @@ public class ArgumentCommand extends TestSWCommand { } @Register - public void argument(String sender, @Quotable String arg) { + public void argument(String sender, String arg) { throw new ExecutionIdentifier("RunArgument with String"); } } diff --git a/testsrc/de/steamwar/command/ArgumentCommandTest.java b/testsrc/de/steamwar/command/ArgumentCommandTest.java index fd4fb5f..0af9327 100644 --- a/testsrc/de/steamwar/command/ArgumentCommandTest.java +++ b/testsrc/de/steamwar/command/ArgumentCommandTest.java @@ -94,29 +94,6 @@ public class ArgumentCommandTest { } } - @Test - public void testString() { - ArgumentCommand cmd = new ArgumentCommand(); - try { - cmd.execute("test", "", new String[]{"Hello World"}); - assert false; - } catch (Exception e) { - assertCMDFramework(e, ExecutionIdentifier.class, "RunArgument with String"); - } - try { - cmd.execute("test", "", new String[]{"\"Hello World\""}); - assert false; - } catch (Exception e) { - assertCMDFramework(e, ExecutionIdentifier.class, "RunArgument with String"); - } - try { - cmd.execute("test", "", new String[]{"\"Hello", "World\""}); - assert false; - } catch (Exception e) { - assertCMDFramework(e, ExecutionIdentifier.class, "RunArgument with String"); - } - } - @Test public void testTabComplete() { ArgumentCommand cmd = new ArgumentCommand(); From c9d915b01e7ab4d065cc08c6e5e297d1c6181d0b Mon Sep 17 00:00:00 2001 From: yoyosource Date: Sun, 22 Jan 2023 15:41:05 +0100 Subject: [PATCH 76/85] Fix stuff and update tests --- .../steamwar/command/AbstractSWCommand.java | 53 +++++++++++++------ src/de/steamwar/command/CommandPart.java | 6 ++- .../de/steamwar/command/ArgumentCommand.java | 15 ++++++ .../steamwar/command/ArgumentCommandTest.java | 17 +++++- 4 files changed, 72 insertions(+), 19 deletions(-) diff --git a/src/de/steamwar/command/AbstractSWCommand.java b/src/de/steamwar/command/AbstractSWCommand.java index 9b0264f..892d804 100644 --- a/src/de/steamwar/command/AbstractSWCommand.java +++ b/src/de/steamwar/command/AbstractSWCommand.java @@ -67,7 +67,8 @@ public abstract class AbstractSWCommand { System.out.println(message.get()); } - protected void sendMessage(T sender, String message, Object[] args) {} + protected void sendMessage(T sender, String message, Object[] args) { + } protected final void execute(T sender, String alias, String[] args) { initialize(); @@ -91,7 +92,7 @@ public abstract class AbstractSWCommand { .filter(Objects::nonNull) .flatMap(Collection::stream) .filter(s -> !s.isEmpty()) - .filter(s -> s.toLowerCase().startsWith(string)) + .filter(s -> s.toLowerCase().startsWith(string) || string.startsWith(s.toLowerCase())) .distinct() .collect(Collectors.toList()); } @@ -150,7 +151,8 @@ public abstract class AbstractSWCommand { if (!checkType(method.getAnnotations(), method.getReturnType(), annotation -> { CommandMetaData.Method methodMetaData = annotation.annotationType().getAnnotation(CommandMetaData.Method.class); if (methodMetaData == null) return aClass -> true; - if (method.getParameterCount() > methodMetaData.maxParameterCount() || method.getParameterCount() < methodMetaData.minParameterCount()) return aClass -> false; + if (method.getParameterCount() > methodMetaData.maxParameterCount() || method.getParameterCount() < methodMetaData.minParameterCount()) + return aClass -> false; return aClass -> { Class[] types = methodMetaData.value(); if (types == null) return true; @@ -171,7 +173,8 @@ public abstract class AbstractSWCommand { if (Predicate.class.isAssignableFrom(handler)) { try { return (Predicate>) handler.getConstructor().newInstance(); - } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | + NoSuchMethodException e) { } } return aClass -> { @@ -318,7 +321,9 @@ public abstract class AbstractSWCommand { @CommandMetaData.Method(value = AbstractTypeMapper.class, maxParameterCount = 0) protected @interface Cached { long cacheDuration() default 5; + TimeUnit timeUnit() default TimeUnit.SECONDS; + boolean global() default false; } @@ -378,7 +383,7 @@ public abstract class AbstractSWCommand { */ boolean allowISE() default false; - int[] falseValues() default { 0 }; + int[] falseValues() default {0}; class Handler implements AbstractTypeMapper { @@ -416,11 +421,6 @@ public abstract class AbstractSWCommand { return inner.map(sender, previousArguments, s); } - @Override - public boolean validate(T sender, Object value, MessageSender messageSender) { - return inner.validate(sender, value, messageSender); - } - @Override public Collection tabCompletes(T sender, PreviousArguments previousArguments, String s) { return inner.tabCompletes(sender, previousArguments, s); @@ -446,7 +446,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) - @CommandMetaData.ImplicitValidator(handler = ErrorMessage.Handler.class, order = 1) + @CommandMetaData.ImplicitValidator(handler = ErrorMessage.Handler.class, order = 2) protected @interface ErrorMessage { /** * Error message to be displayed when the parameter is invalid. @@ -486,11 +486,14 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) @CommandMetaData.Parameter({int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class}) - @CommandMetaData.ImplicitValidator(handler = Min.Handler.class, order = 2) + @CommandMetaData.ImplicitValidator(handler = Min.Handler.class, order = 3) protected @interface Min { int intValue() default Integer.MIN_VALUE; + long longValue() default Long.MIN_VALUE; + float floatValue() default Float.MIN_VALUE; + double doubleValue() default Double.MIN_VALUE; boolean inclusive() default true; @@ -507,6 +510,7 @@ public abstract class AbstractSWCommand { @Override public boolean validate(T sender, Number value, MessageSender messageSender) { + if (value == null) return true; return (comparator.apply(value).intValue()) >= this.value; } } @@ -515,11 +519,14 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) @CommandMetaData.Parameter({int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class}) - @CommandMetaData.ImplicitValidator(handler = Max.Handler.class, order = 2) + @CommandMetaData.ImplicitValidator(handler = Max.Handler.class, order = 3) protected @interface Max { int intValue() default Integer.MAX_VALUE; + long longValue() default Long.MAX_VALUE; + float floatValue() default Float.MAX_VALUE; + double doubleValue() default Double.MAX_VALUE; boolean inclusive() default true; @@ -536,6 +543,7 @@ public abstract class AbstractSWCommand { @Override public boolean validate(T sender, Number value, MessageSender messageSender) { + if (value == null) return true; return (comparator.apply(value).intValue()) <= this.value; } } @@ -560,6 +568,7 @@ public abstract class AbstractSWCommand { @CommandMetaData.ImplicitTypeMapper(handler = Length.Handler.class) protected @interface Length { int min() default 0; + int max() default Integer.MAX_VALUE; class Handler implements AbstractTypeMapper { @@ -603,11 +612,14 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) @CommandMetaData.Parameter(handler = ArrayLength.Type.class) - @CommandMetaData.ImplicitValidator(handler = ArrayLength.Handler.class, order = 2) + @CommandMetaData.ImplicitValidator(handler = ArrayLength.Handler.class, order = 1) protected @interface ArrayLength { int min() default 0; + int max() default Integer.MAX_VALUE; + String errorMessage() default ""; + class Type implements Predicate> { @Override public boolean test(Class clazz) { @@ -619,17 +631,26 @@ public abstract class AbstractSWCommand { private int min; private int max; + private String errorMessage; public Handler(ArrayLength arrayLength) { this.min = arrayLength.min(); this.max = arrayLength.max(); + this.errorMessage = arrayLength.errorMessage(); + if (this.errorMessage.isEmpty()) { + this.errorMessage = null; + } } @Override public boolean validate(T sender, Object value, MessageSender messageSender) { - if (value == null) return false; + if (value == null) return true; int length = Array.getLength(value); - return length >= min && length <= max; + boolean valid = length >= min && length <= max; + if (errorMessage != null) { + messageSender.send(!valid, errorMessage); + } + return valid; } } } diff --git a/src/de/steamwar/command/CommandPart.java b/src/de/steamwar/command/CommandPart.java index b902892..8a45791 100644 --- a/src/de/steamwar/command/CommandPart.java +++ b/src/de/steamwar/command/CommandPart.java @@ -181,6 +181,7 @@ class CommandPart { } catch (Exception e) { return new CheckArgumentResult(false, null); } + boolean success = true; for (AbstractValidator validator : validators) { try { if (!validator.validate(sender, value, (s, objects) -> { @@ -188,13 +189,14 @@ class CommandPart { command.sendMessage(sender, s, objects); }); })) { - return new CheckArgumentResult(false, null); + success = false; + value = null; } } catch (Throwable e) { throw CommandFrameworkException.commandPartExceptions("validating", e, args[index], (varArgType != null ? varArgType : parameter.getType()), parameter.getDeclaringExecutable(), parameterIndex); } } - return new CheckArgumentResult(true, value); + return new CheckArgumentResult(success, value); } public Class getType() { diff --git a/testsrc/de/steamwar/command/ArgumentCommand.java b/testsrc/de/steamwar/command/ArgumentCommand.java index a107270..48f9b40 100644 --- a/testsrc/de/steamwar/command/ArgumentCommand.java +++ b/testsrc/de/steamwar/command/ArgumentCommand.java @@ -57,4 +57,19 @@ public class ArgumentCommand extends TestSWCommand { public void argument(String sender, String arg) { throw new ExecutionIdentifier("RunArgument with String"); } + + @Register + public void minLengthArgument(String sender, @Length(min = 3) @StaticValue({"he", "hello"}) String arg) { + throw new ExecutionIdentifier("RunLengthArgument with String"); + } + + @Register + public void minAndMaxLengthArgument(String sender, @Length(min = 3, max = 3) @StaticValue({"wo", "world"}) String arg) { + throw new ExecutionIdentifier("RunLengthArgument with String"); + } + + @Register + public void arrayLengthArgument(String sender, @ArrayLength(min = 2) int... args) { + throw new ExecutionIdentifier("RunArrayLengthArgument with Integer"); + } } diff --git a/testsrc/de/steamwar/command/ArgumentCommandTest.java b/testsrc/de/steamwar/command/ArgumentCommandTest.java index 0af9327..5450c6e 100644 --- a/testsrc/de/steamwar/command/ArgumentCommandTest.java +++ b/testsrc/de/steamwar/command/ArgumentCommandTest.java @@ -98,7 +98,7 @@ public class ArgumentCommandTest { public void testTabComplete() { ArgumentCommand cmd = new ArgumentCommand(); List strings = cmd.tabComplete("test", "", new String[]{""}); - assertTabCompletes(strings, "true", "false"); + assertTabCompletes(strings, "true", "false", "hello", "wor"); } @Test @@ -106,5 +106,20 @@ public class ArgumentCommandTest { ArgumentCommand cmd = new ArgumentCommand(); List strings = cmd.tabComplete("test", "", new String[]{"t"}); assertTabCompletes(strings, "true", "t"); + + strings = cmd.tabComplete("test", "", new String[]{"h"}); + assertTabCompletes(strings, "h", "hello"); + + strings = cmd.tabComplete("test", "", new String[]{"hel"}); + assertTabCompletes(strings, "hel", "hello"); + + strings = cmd.tabComplete("test", "", new String[]{"w"}); + assertTabCompletes(strings, "w", "wor"); + + strings = cmd.tabComplete("test", "", new String[]{"wor"}); + assertTabCompletes(strings, "wor"); + + strings = cmd.tabComplete("test", "", new String[]{"worl"}); + assertTabCompletes(strings, "wor", "worl"); } } From e31534632e1d6b9278cc03de2691d21d9342cccd Mon Sep 17 00:00:00 2001 From: yoyosource Date: Sun, 22 Jan 2023 16:04:35 +0100 Subject: [PATCH 77/85] Fix stuff and update tests --- .../steamwar/command/AbstractSWCommand.java | 55 +++++++------------ .../de/steamwar/command/ArgumentCommand.java | 5 -- 2 files changed, 20 insertions(+), 40 deletions(-) diff --git a/src/de/steamwar/command/AbstractSWCommand.java b/src/de/steamwar/command/AbstractSWCommand.java index 892d804..5cdb213 100644 --- a/src/de/steamwar/command/AbstractSWCommand.java +++ b/src/de/steamwar/command/AbstractSWCommand.java @@ -26,10 +26,7 @@ import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.*; import java.util.concurrent.TimeUnit; -import java.util.function.BiConsumer; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.function.Supplier; +import java.util.function.*; import java.util.stream.Collectors; public abstract class AbstractSWCommand { @@ -148,12 +145,12 @@ public abstract class AbstractSWCommand { } private boolean validateMethod(Method method) { - if (!checkType(method.getAnnotations(), method.getReturnType(), annotation -> { + if (!checkType(method.getAnnotations(), method.getReturnType(), false, annotation -> { CommandMetaData.Method methodMetaData = annotation.annotationType().getAnnotation(CommandMetaData.Method.class); - if (methodMetaData == null) return aClass -> true; + if (methodMetaData == null) return (aClass, varArg) -> true; if (method.getParameterCount() > methodMetaData.maxParameterCount() || method.getParameterCount() < methodMetaData.minParameterCount()) - return aClass -> false; - return aClass -> { + return (aClass, varArg) -> false; + return (aClass, varArg) -> { Class[] types = methodMetaData.value(); if (types == null) return true; for (Class type : types) { @@ -164,20 +161,19 @@ public abstract class AbstractSWCommand { }, "The method '" + method + "'")) return false; boolean valid = true; for (Parameter parameter : method.getParameters()) { - Class type = parameter.getType(); - if (parameter.isVarArgs()) type = type.getComponentType(); - if (!checkType(parameter.getAnnotations(), type, annotation -> { + if (!checkType(parameter.getAnnotations(), parameter.getType(), parameter.isVarArgs(), annotation -> { CommandMetaData.Parameter parameterMetaData = annotation.annotationType().getAnnotation(CommandMetaData.Parameter.class); - if (parameterMetaData == null) return aClass -> true; + if (parameterMetaData == null) return (aClass, varArg) -> true; Class handler = parameterMetaData.handler(); - if (Predicate.class.isAssignableFrom(handler)) { + if (BiPredicate.class.isAssignableFrom(handler)) { try { - return (Predicate>) handler.getConstructor().newInstance(); + return (BiPredicate, Boolean>) handler.getConstructor().newInstance(); } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { } } - return aClass -> { + return (aClass, varArg) -> { + if (varArg) aClass = aClass.getComponentType(); Class[] types = parameterMetaData.value(); if (types == null) return true; for (Class current : types) { @@ -190,11 +186,11 @@ public abstract class AbstractSWCommand { return valid; } - private boolean checkType(Annotation[] annotations, Class clazz, Function>> toApplicableTypes, String warning) { + private boolean checkType(Annotation[] annotations, Class clazz, boolean varArg, Function, Boolean>> toApplicableTypes, String warning) { boolean valid = true; for (Annotation annotation : annotations) { - Predicate> predicate = toApplicableTypes.apply(annotation); - if (!predicate.test(clazz)) { + BiPredicate, Boolean> predicate = toApplicableTypes.apply(annotation); + if (!predicate.test(clazz, varArg)) { commandSystemWarning(() -> warning + " is using an unsupported annotation of type '" + annotation.annotationType().getName() + "'"); valid = false; } @@ -446,7 +442,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) - @CommandMetaData.ImplicitValidator(handler = ErrorMessage.Handler.class, order = 2) + @CommandMetaData.ImplicitValidator(handler = ErrorMessage.Handler.class, order = Integer.MAX_VALUE) protected @interface ErrorMessage { /** * Error message to be displayed when the parameter is invalid. @@ -486,7 +482,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) @CommandMetaData.Parameter({int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class}) - @CommandMetaData.ImplicitValidator(handler = Min.Handler.class, order = 3) + @CommandMetaData.ImplicitValidator(handler = Min.Handler.class, order = 2) protected @interface Min { int intValue() default Integer.MIN_VALUE; @@ -519,7 +515,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) @CommandMetaData.Parameter({int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class}) - @CommandMetaData.ImplicitValidator(handler = Max.Handler.class, order = 3) + @CommandMetaData.ImplicitValidator(handler = Max.Handler.class, order = 2) protected @interface Max { int intValue() default Integer.MAX_VALUE; @@ -618,11 +614,9 @@ public abstract class AbstractSWCommand { int max() default Integer.MAX_VALUE; - String errorMessage() default ""; - - class Type implements Predicate> { + class Type implements BiPredicate, Boolean> { @Override - public boolean test(Class clazz) { + public boolean test(Class clazz, Boolean isVarArgs) { return clazz.isArray(); } } @@ -631,26 +625,17 @@ public abstract class AbstractSWCommand { private int min; private int max; - private String errorMessage; public Handler(ArrayLength arrayLength) { this.min = arrayLength.min(); this.max = arrayLength.max(); - this.errorMessage = arrayLength.errorMessage(); - if (this.errorMessage.isEmpty()) { - this.errorMessage = null; - } } @Override public boolean validate(T sender, Object value, MessageSender messageSender) { if (value == null) return true; int length = Array.getLength(value); - boolean valid = length >= min && length <= max; - if (errorMessage != null) { - messageSender.send(!valid, errorMessage); - } - return valid; + return length >= min && length <= max; } } } diff --git a/testsrc/de/steamwar/command/ArgumentCommand.java b/testsrc/de/steamwar/command/ArgumentCommand.java index 48f9b40..451d6e9 100644 --- a/testsrc/de/steamwar/command/ArgumentCommand.java +++ b/testsrc/de/steamwar/command/ArgumentCommand.java @@ -67,9 +67,4 @@ public class ArgumentCommand extends TestSWCommand { public void minAndMaxLengthArgument(String sender, @Length(min = 3, max = 3) @StaticValue({"wo", "world"}) String arg) { throw new ExecutionIdentifier("RunLengthArgument with String"); } - - @Register - public void arrayLengthArgument(String sender, @ArrayLength(min = 2) int... args) { - throw new ExecutionIdentifier("RunArrayLengthArgument with Integer"); - } } From 4e6f73c63728ca5ef6bcb68db88dadc29feb46b0 Mon Sep 17 00:00:00 2001 From: yoyosource Date: Sun, 22 Jan 2023 22:04:00 +0100 Subject: [PATCH 78/85] Add ArrayLength visual update for tabcompletions --- .../steamwar/command/AbstractSWCommand.java | 40 +++++++++++++++++-- src/de/steamwar/command/CommandPart.java | 7 +++- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/src/de/steamwar/command/AbstractSWCommand.java b/src/de/steamwar/command/AbstractSWCommand.java index 5cdb213..6bc3177 100644 --- a/src/de/steamwar/command/AbstractSWCommand.java +++ b/src/de/steamwar/command/AbstractSWCommand.java @@ -26,7 +26,10 @@ import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.*; import java.util.concurrent.TimeUnit; -import java.util.function.*; +import java.util.function.BiConsumer; +import java.util.function.BiPredicate; +import java.util.function.Function; +import java.util.function.Supplier; import java.util.stream.Collectors; public abstract class AbstractSWCommand { @@ -608,7 +611,8 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) @CommandMetaData.Parameter(handler = ArrayLength.Type.class) - @CommandMetaData.ImplicitValidator(handler = ArrayLength.Handler.class, order = 1) + @CommandMetaData.ImplicitTypeMapper(handler = ArrayLength.HandlerTypeMapper.class) + @CommandMetaData.ImplicitValidator(handler = ArrayLength.HandlerValidator.class, order = 1) protected @interface ArrayLength { int min() default 0; @@ -621,12 +625,40 @@ public abstract class AbstractSWCommand { } } - class Handler implements AbstractValidator { + class HandlerTypeMapper implements AbstractTypeMapper { + + private int max; + private AbstractTypeMapper inner; + + public HandlerTypeMapper(ArrayLength arrayLength, AbstractTypeMapper inner) { + this.max = arrayLength.max(); + this.inner = inner; + } + + @Override + public Object map(T sender, PreviousArguments previousArguments, String s) { + return inner.map(sender, previousArguments, s); + } + + @Override + public Collection tabCompletes(T sender, PreviousArguments previousArguments, String s) { + Object[] mapped = previousArguments.getMappedArg(0); + if (mapped.length >= max) return Collections.emptyList(); + return inner.tabCompletes(sender, previousArguments, s); + } + + @Override + public String normalize(T sender, String s) { + return inner.normalize(sender, s); + } + } + + class HandlerValidator implements AbstractValidator { private int min; private int max; - public Handler(ArrayLength arrayLength) { + public HandlerValidator(ArrayLength arrayLength) { this.min = arrayLength.min(); this.max = arrayLength.max(); } diff --git a/src/de/steamwar/command/CommandPart.java b/src/de/steamwar/command/CommandPart.java index 8a45791..971775a 100644 --- a/src/de/steamwar/command/CommandPart.java +++ b/src/de/steamwar/command/CommandPart.java @@ -129,11 +129,16 @@ class CommandPart { public void generateTabComplete(List current, T sender, String[] args, List mappedArgs, int startIndex) { if (varArgType != null) { + List currentArgs = new ArrayList<>(mappedArgs); + List varArgs = new ArrayList<>(); for (int i = startIndex; i < args.length - 1; i++) { CheckArgumentResult validArgument = checkArgument((ignore) -> {}, sender, args, mappedArgs, i); if (!validArgument.success) return; + varArgs.add(validArgument.value); } - Collection strings = tabCompletes(sender, args, mappedArgs, args.length - 1); + + currentArgs.add(varArgs.toArray()); + Collection strings = tabCompletes(sender, args, currentArgs, args.length - 1); if (strings != null) { current.addAll(strings); } From 49e4bf64b7ba0d7c412919ac1573b07c7263a6b0 Mon Sep 17 00:00:00 2001 From: yoyosource Date: Mon, 23 Jan 2023 14:06:40 +0100 Subject: [PATCH 79/85] Add ArrayLength test --- testsrc/de/steamwar/command/ArgumentCommand.java | 4 ++++ testsrc/de/steamwar/command/ArgumentCommandTest.java | 3 +++ 2 files changed, 7 insertions(+) diff --git a/testsrc/de/steamwar/command/ArgumentCommand.java b/testsrc/de/steamwar/command/ArgumentCommand.java index 451d6e9..41f59c7 100644 --- a/testsrc/de/steamwar/command/ArgumentCommand.java +++ b/testsrc/de/steamwar/command/ArgumentCommand.java @@ -67,4 +67,8 @@ public class ArgumentCommand extends TestSWCommand { public void minAndMaxLengthArgument(String sender, @Length(min = 3, max = 3) @StaticValue({"wo", "world"}) String arg) { throw new ExecutionIdentifier("RunLengthArgument with String"); } + + public void arrayLengthArgument(String sender, @ArrayLength(max = 3) @StaticValue({"one", "two", "three"}) String... args) { + throw new ExecutionIdentifier("RunArrayLengthArgument with String"); + } } diff --git a/testsrc/de/steamwar/command/ArgumentCommandTest.java b/testsrc/de/steamwar/command/ArgumentCommandTest.java index 5450c6e..b46c184 100644 --- a/testsrc/de/steamwar/command/ArgumentCommandTest.java +++ b/testsrc/de/steamwar/command/ArgumentCommandTest.java @@ -121,5 +121,8 @@ public class ArgumentCommandTest { strings = cmd.tabComplete("test", "", new String[]{"worl"}); assertTabCompletes(strings, "wor", "worl"); + + strings = cmd.tabComplete("test", "", new String[]{"one", "two", "three", "one"}); + assertTabCompletes(strings); } } From ef79a2e7db64e3694d1f913b7e1b5a256dd25c38 Mon Sep 17 00:00:00 2001 From: yoyosource Date: Wed, 25 Jan 2023 20:41:16 +0100 Subject: [PATCH 80/85] Add some unfinished new sorting --- .../steamwar/command/AbstractSWCommand.java | 7 +- src/de/steamwar/command/SubCommand.java | 20 ++++-- .../command/StaticValueCommandTest.java | 14 ++++ .../command/SubCMDSortingCommand.java | 54 ++++++++++++++ .../command/SubCMDSortingCommandTest.java | 72 +++++++++++++++++++ 5 files changed, 157 insertions(+), 10 deletions(-) create mode 100644 testsrc/de/steamwar/command/SubCMDSortingCommand.java create mode 100644 testsrc/de/steamwar/command/SubCMDSortingCommandTest.java diff --git a/src/de/steamwar/command/AbstractSWCommand.java b/src/de/steamwar/command/AbstractSWCommand.java index 6bc3177..1ad1969 100644 --- a/src/de/steamwar/command/AbstractSWCommand.java +++ b/src/de/steamwar/command/AbstractSWCommand.java @@ -139,11 +139,8 @@ public abstract class AbstractSWCommand { }); } - this.commandList.sort((o1, o2) -> { - int compare = Integer.compare(-o1.subCommand.length, -o2.subCommand.length); - if (compare == 0) return Integer.compare(o1.comparableValue, o2.comparableValue); - return compare; - }); + Collections.sort(commandList); + System.out.println(commandList.stream().map(o -> o.method).collect(Collectors.toList())); initialized = true; } diff --git a/src/de/steamwar/command/SubCommand.java b/src/de/steamwar/command/SubCommand.java index d74423b..86543d5 100644 --- a/src/de/steamwar/command/SubCommand.java +++ b/src/de/steamwar/command/SubCommand.java @@ -32,7 +32,7 @@ import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; -public class SubCommand { +public class SubCommand implements Comparable> { private AbstractSWCommand abstractSWCommand; Method method; @@ -42,7 +42,8 @@ public class SubCommand { private Function senderFunction; AbstractValidator validator; boolean noTabComplete; - int comparableValue; + + private Parameter[] parameters; private CommandPart commandPart; @@ -58,9 +59,7 @@ public class SubCommand { this.description = description; this.noTabComplete = noTabComplete; - Parameter[] parameters = method.getParameters(); - comparableValue = parameters[parameters.length - 1].isVarArgs() ? Integer.MAX_VALUE : -parameters.length; - + parameters = method.getParameters(); AbstractSWCommand.Validator validator = parameters[0].getAnnotation(AbstractSWCommand.Validator.class); if (validator != null) { this.validator = (AbstractValidator) SWCommandUtils.getValidator(validator, parameters[0].getType(), localValidator); @@ -72,6 +71,17 @@ public class SubCommand { senderFunction = t -> parameters[0].getType().cast(t); } + @Override + public int compareTo(SubCommand o) { + int tLength = parameters.length + subCommand.length; + int oLength = o.parameters.length + o.subCommand.length; + + if (parameters[parameters.length - 1].isVarArgs()) tLength *= -1; + if (o.parameters[o.parameters.length - 1].isVarArgs()) oLength *= -1; + + return -Integer.compare(tLength, oLength); + } + boolean invoke(Consumer errors, T sender, String alias, String[] args) { try { if (!senderPredicate.test(sender)) { diff --git a/testsrc/de/steamwar/command/StaticValueCommandTest.java b/testsrc/de/steamwar/command/StaticValueCommandTest.java index 665250a..75a7dfd 100644 --- a/testsrc/de/steamwar/command/StaticValueCommandTest.java +++ b/testsrc/de/steamwar/command/StaticValueCommandTest.java @@ -41,11 +41,13 @@ public class StaticValueCommandTest { StaticValueCommand cmd = new StaticValueCommand(); try { cmd.execute("", "", new String[] {"hello"}); + assert false; } catch (Exception e) { assertCMDFramework(e, ExecutionIdentifier.class, "RunStaticValue with hello"); } try { cmd.execute("", "", new String[] {"world"}); + assert false; } catch (Exception e) { assertCMDFramework(e, ExecutionIdentifier.class, "RunStaticValue with world"); } @@ -56,16 +58,19 @@ public class StaticValueCommandTest { StaticValueCommand cmd = new StaticValueCommand(); try { cmd.execute("", "", new String[] {"-a"}); + assert false; } catch (Exception e) { assertCMDFramework(e, ExecutionIdentifier.class, "RunStaticValue with false"); } try { cmd.execute("", "", new String[] {"-b"}); + assert false; } catch (Exception e) { assertCMDFramework(e, ExecutionIdentifier.class, "RunStaticValue with true"); } try { cmd.execute("", "", new String[] {"-c"}); + assert false; } catch (Exception e) { assertCMDFramework(e, ExecutionIdentifier.class, "RunStaticValue with true"); } @@ -76,16 +81,19 @@ public class StaticValueCommandTest { StaticValueCommand cmd = new StaticValueCommand(); try { cmd.execute("", "", new String[] {"-d"}); + assert false; } catch (Exception e) { assertCMDFramework(e, ExecutionIdentifier.class, "RunStaticValue with true"); } try { cmd.execute("", "", new String[] {"-e"}); + assert false; } catch (Exception e) { assertCMDFramework(e, ExecutionIdentifier.class, "RunStaticValue with false"); } try { cmd.execute("", "", new String[] {"-f"}); + assert false; } catch (Exception e) { assertCMDFramework(e, ExecutionIdentifier.class, "RunStaticValue with true"); } @@ -96,16 +104,19 @@ public class StaticValueCommandTest { StaticValueCommand cmd = new StaticValueCommand(); try { cmd.execute("", "", new String[] {"-g"}); + assert false; } catch (Exception e) { assertCMDFramework(e, ExecutionIdentifier.class, "RunStaticValue with int 0"); } try { cmd.execute("", "", new String[] {"-h"}); + assert false; } catch (Exception e) { assertCMDFramework(e, ExecutionIdentifier.class, "RunStaticValue with int 1"); } try { cmd.execute("", "", new String[] {"-i"}); + assert false; } catch (Exception e) { assertCMDFramework(e, ExecutionIdentifier.class, "RunStaticValue with int 2"); } @@ -116,16 +127,19 @@ public class StaticValueCommandTest { StaticValueCommand cmd = new StaticValueCommand(); try { cmd.execute("", "", new String[] {"-j"}); + assert false; } catch (Exception e) { assertCMDFramework(e, ExecutionIdentifier.class, "RunStaticValue with long 0"); } try { cmd.execute("", "", new String[] {"-k"}); + assert false; } catch (Exception e) { assertCMDFramework(e, ExecutionIdentifier.class, "RunStaticValue with long 1"); } try { cmd.execute("", "", new String[] {"-l"}); + assert false; } catch (Exception e) { assertCMDFramework(e, ExecutionIdentifier.class, "RunStaticValue with long 2"); } diff --git a/testsrc/de/steamwar/command/SubCMDSortingCommand.java b/testsrc/de/steamwar/command/SubCMDSortingCommand.java new file mode 100644 index 0000000..35781e5 --- /dev/null +++ b/testsrc/de/steamwar/command/SubCMDSortingCommand.java @@ -0,0 +1,54 @@ +/* + * 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.command; + +import de.steamwar.command.dto.ExecutionIdentifier; +import de.steamwar.command.dto.TestSWCommand; + +public class SubCMDSortingCommand extends TestSWCommand { + + public SubCMDSortingCommand() { + super("subcmdsorting"); + } + + @Register + public void test(String s) { + System.out.println("HERE 1"); + throw new ExecutionIdentifier("Command with 0 parameters"); + } + + @Register + public void test(String s, String... args) { + System.out.println("HERE 2"); + throw new ExecutionIdentifier("Command with 1 parameters"); + } + + @Register + public void test(String s, String p, String... args) { + System.out.println("HERE 3"); + throw new ExecutionIdentifier("Command with 2 parameters"); + } + + @Register + public void test(String s, String p, String p2, String... args) { + System.out.println("HERE 4"); + throw new ExecutionIdentifier("Command with 3 parameters"); + } +} diff --git a/testsrc/de/steamwar/command/SubCMDSortingCommandTest.java b/testsrc/de/steamwar/command/SubCMDSortingCommandTest.java new file mode 100644 index 0000000..8f22085 --- /dev/null +++ b/testsrc/de/steamwar/command/SubCMDSortingCommandTest.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.command; + +import de.steamwar.command.dto.ExecutionIdentifier; +import org.junit.Test; + +import static de.steamwar.AssertionUtils.assertCMDFramework; + +public class SubCMDSortingCommandTest { + + @Test + public void testNoArgs() { + SubCMDSortingCommand cmd = new SubCMDSortingCommand(); + try { + cmd.execute("", "", new String[]{}); + assert false; + } catch (Exception e) { + assertCMDFramework(e, ExecutionIdentifier.class, "Command with 0 parameters"); + } + } + + @Test + public void testOneArgs() { + SubCMDSortingCommand cmd = new SubCMDSortingCommand(); + try { + cmd.execute("", "", new String[]{"Hello"}); + assert false; + } catch (Exception e) { + assertCMDFramework(e, ExecutionIdentifier.class, "Command with 1 parameters"); + } + } + + @Test + public void testTwoArgs() { + SubCMDSortingCommand cmd = new SubCMDSortingCommand(); + try { + cmd.execute("", "", new String[]{"Hello", "World"}); + assert false; + } catch (Exception e) { + assertCMDFramework(e, ExecutionIdentifier.class, "Command with 2 parameters"); + } + } + + @Test + public void testThreeArgs() { + SubCMDSortingCommand cmd = new SubCMDSortingCommand(); + try { + cmd.execute("", "", new String[]{"Hello", "World", "!"}); + assert false; + } catch (Exception e) { + assertCMDFramework(e, ExecutionIdentifier.class, "Command with 3 parameters"); + } + } +} From 65df8ddab08419d7daa2af625c1f87435601e483 Mon Sep 17 00:00:00 2001 From: yoyosource Date: Thu, 2 Feb 2023 17:53:56 +0100 Subject: [PATCH 81/85] Fix command sorting with varargs --- src/de/steamwar/command/CommandPart.java | 2 ++ src/de/steamwar/command/SubCommand.java | 7 +++++-- .../command/SubCMDSortingCommand.java | 21 ++++++------------- .../command/SubCMDSortingCommandTest.java | 17 +++------------ 4 files changed, 16 insertions(+), 31 deletions(-) diff --git a/src/de/steamwar/command/CommandPart.java b/src/de/steamwar/command/CommandPart.java index 971775a..5eb264c 100644 --- a/src/de/steamwar/command/CommandPart.java +++ b/src/de/steamwar/command/CommandPart.java @@ -124,6 +124,8 @@ class CommandPart { } if (next != null) { next.generateArgumentArray(errors, current, sender, args, startIndex + 1); + } else if (startIndex + 1 < args.length) { + throw new CommandParseException(); } } diff --git a/src/de/steamwar/command/SubCommand.java b/src/de/steamwar/command/SubCommand.java index 86543d5..6c076b1 100644 --- a/src/de/steamwar/command/SubCommand.java +++ b/src/de/steamwar/command/SubCommand.java @@ -76,8 +76,11 @@ public class SubCommand implements Comparable> { int tLength = parameters.length + subCommand.length; int oLength = o.parameters.length + o.subCommand.length; - if (parameters[parameters.length - 1].isVarArgs()) tLength *= -1; - if (o.parameters[o.parameters.length - 1].isVarArgs()) oLength *= -1; + boolean tVarArgs = parameters[parameters.length - 1].isVarArgs(); + boolean oVarArgs = o.parameters[o.parameters.length - 1].isVarArgs(); + + if (tVarArgs) tLength *= -1; + if (oVarArgs) oLength *= -1; return -Integer.compare(tLength, oLength); } diff --git a/testsrc/de/steamwar/command/SubCMDSortingCommand.java b/testsrc/de/steamwar/command/SubCMDSortingCommand.java index 35781e5..d2a67dd 100644 --- a/testsrc/de/steamwar/command/SubCMDSortingCommand.java +++ b/testsrc/de/steamwar/command/SubCMDSortingCommand.java @@ -30,25 +30,16 @@ public class SubCMDSortingCommand extends TestSWCommand { @Register public void test(String s) { - System.out.println("HERE 1"); throw new ExecutionIdentifier("Command with 0 parameters"); } + @Register + public void test(String s, String args) { + throw new ExecutionIdentifier("Command with 1 parameter"); + } + @Register public void test(String s, String... args) { - System.out.println("HERE 2"); - throw new ExecutionIdentifier("Command with 1 parameters"); - } - - @Register - public void test(String s, String p, String... args) { - System.out.println("HERE 3"); - throw new ExecutionIdentifier("Command with 2 parameters"); - } - - @Register - public void test(String s, String p, String p2, String... args) { - System.out.println("HERE 4"); - throw new ExecutionIdentifier("Command with 3 parameters"); + throw new ExecutionIdentifier("Command with n parameters"); } } diff --git a/testsrc/de/steamwar/command/SubCMDSortingCommandTest.java b/testsrc/de/steamwar/command/SubCMDSortingCommandTest.java index 8f22085..6981e29 100644 --- a/testsrc/de/steamwar/command/SubCMDSortingCommandTest.java +++ b/testsrc/de/steamwar/command/SubCMDSortingCommandTest.java @@ -44,29 +44,18 @@ public class SubCMDSortingCommandTest { cmd.execute("", "", new String[]{"Hello"}); assert false; } catch (Exception e) { - assertCMDFramework(e, ExecutionIdentifier.class, "Command with 1 parameters"); + assertCMDFramework(e, ExecutionIdentifier.class, "Command with 1 parameter"); } } @Test - public void testTwoArgs() { + public void testOneArgsVarArg() { SubCMDSortingCommand cmd = new SubCMDSortingCommand(); try { cmd.execute("", "", new String[]{"Hello", "World"}); assert false; } catch (Exception e) { - assertCMDFramework(e, ExecutionIdentifier.class, "Command with 2 parameters"); - } - } - - @Test - public void testThreeArgs() { - SubCMDSortingCommand cmd = new SubCMDSortingCommand(); - try { - cmd.execute("", "", new String[]{"Hello", "World", "!"}); - assert false; - } catch (Exception e) { - assertCMDFramework(e, ExecutionIdentifier.class, "Command with 3 parameters"); + assertCMDFramework(e, ExecutionIdentifier.class, "Command with n parameters"); } } } From 45e9698634f9c6c4e8ff4de315d6133056931da5 Mon Sep 17 00:00:00 2001 From: yoyosource Date: Thu, 2 Feb 2023 18:07:29 +0100 Subject: [PATCH 82/85] Fix command sorting with varargs --- .../steamwar/command/AbstractSWCommand.java | 1 - src/de/steamwar/command/SubCommand.java | 2 ++ .../command/SubCMDSortingCommand.java | 5 +++++ .../command/SubCMDSortingCommandTest.java | 22 +++++++++++++++++++ 4 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/de/steamwar/command/AbstractSWCommand.java b/src/de/steamwar/command/AbstractSWCommand.java index 1ad1969..84ae620 100644 --- a/src/de/steamwar/command/AbstractSWCommand.java +++ b/src/de/steamwar/command/AbstractSWCommand.java @@ -140,7 +140,6 @@ public abstract class AbstractSWCommand { } Collections.sort(commandList); - System.out.println(commandList.stream().map(o -> o.method).collect(Collectors.toList())); initialized = true; } diff --git a/src/de/steamwar/command/SubCommand.java b/src/de/steamwar/command/SubCommand.java index 6c076b1..58fa919 100644 --- a/src/de/steamwar/command/SubCommand.java +++ b/src/de/steamwar/command/SubCommand.java @@ -82,6 +82,8 @@ public class SubCommand implements Comparable> { if (tVarArgs) tLength *= -1; if (oVarArgs) oLength *= -1; + if (tVarArgs && oVarArgs) return Integer.compare(tLength, oLength); + return -Integer.compare(tLength, oLength); } diff --git a/testsrc/de/steamwar/command/SubCMDSortingCommand.java b/testsrc/de/steamwar/command/SubCMDSortingCommand.java index d2a67dd..0f4e151 100644 --- a/testsrc/de/steamwar/command/SubCMDSortingCommand.java +++ b/testsrc/de/steamwar/command/SubCMDSortingCommand.java @@ -38,6 +38,11 @@ public class SubCMDSortingCommand extends TestSWCommand { throw new ExecutionIdentifier("Command with 1 parameter"); } + @Register + public void test(String s, String i1, String i2, String i3, String... args) { + throw new ExecutionIdentifier("Command with 3+n parameters"); + } + @Register public void test(String s, String... args) { throw new ExecutionIdentifier("Command with n parameters"); diff --git a/testsrc/de/steamwar/command/SubCMDSortingCommandTest.java b/testsrc/de/steamwar/command/SubCMDSortingCommandTest.java index 6981e29..1f3203c 100644 --- a/testsrc/de/steamwar/command/SubCMDSortingCommandTest.java +++ b/testsrc/de/steamwar/command/SubCMDSortingCommandTest.java @@ -58,4 +58,26 @@ public class SubCMDSortingCommandTest { assertCMDFramework(e, ExecutionIdentifier.class, "Command with n parameters"); } } + + @Test + public void testThreeArgsVarArg() { + SubCMDSortingCommand cmd = new SubCMDSortingCommand(); + try { + cmd.execute("", "", new String[]{"Hello", "World", "YoyoNow", "Hugo"}); + assert false; + } catch (Exception e) { + assertCMDFramework(e, ExecutionIdentifier.class, "Command with 3+n parameters"); + } + } + + @Test + public void testThreeArgsVarArg2() { + SubCMDSortingCommand cmd = new SubCMDSortingCommand(); + try { + cmd.execute("", "", new String[]{"Hello", "World", "YoyoNow"}); + assert false; + } catch (Exception e) { + assertCMDFramework(e, ExecutionIdentifier.class, "Command with 3+n parameters"); + } + } } From 5d53ede5e8e7b707f10144f86d419fe4d6cf3366 Mon Sep 17 00:00:00 2001 From: yoyosource Date: Sat, 18 Feb 2023 12:05:03 +0100 Subject: [PATCH 83/85] Add PartOf annotation for splitting big commands into multiple files --- .../steamwar/command/AbstractSWCommand.java | 24 +++++++ .../de/steamwar/command/PartOfCommand.java | 70 +++++++++++++++++++ .../steamwar/command/PartOfCommandTest.java | 43 ++++++++++++ 3 files changed, 137 insertions(+) create mode 100644 testsrc/de/steamwar/command/PartOfCommand.java create mode 100644 testsrc/de/steamwar/command/PartOfCommandTest.java diff --git a/src/de/steamwar/command/AbstractSWCommand.java b/src/de/steamwar/command/AbstractSWCommand.java index 84ae620..7ba5e62 100644 --- a/src/de/steamwar/command/AbstractSWCommand.java +++ b/src/de/steamwar/command/AbstractSWCommand.java @@ -34,6 +34,8 @@ import java.util.stream.Collectors; public abstract class AbstractSWCommand { + private static Map>, List>> dependencyMap = new HashMap<>(); + private Class clazz; // This is used in createMappings() private boolean initialized = false; @@ -48,6 +50,13 @@ public abstract class AbstractSWCommand { protected AbstractSWCommand(Class clazz, String command, String... aliases) { this.clazz = clazz; + + PartOf partOf = this.getClass().getAnnotation(PartOf.class); + if (partOf != null) { + dependencyMap.computeIfAbsent((Class>) partOf.value(), k -> new ArrayList<>()).add(this); + return; + } + createAndSafeCommand(command, aliases); unregister(); register(); @@ -139,6 +148,15 @@ public abstract class AbstractSWCommand { }); } + if (dependencyMap.containsKey(this.getClass())) { + dependencyMap.get(this.getClass()).forEach(abstractSWCommand -> { + abstractSWCommand.localTypeMapper.putAll((Map) localTypeMapper); + abstractSWCommand.localValidators.putAll((Map) localValidators); + abstractSWCommand.initialize(); + commandList.addAll((Collection) abstractSWCommand.commandList); + }); + } + Collections.sort(commandList); initialized = true; } @@ -237,6 +255,12 @@ public abstract class AbstractSWCommand { return methods; } + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.TYPE}) + public @interface PartOf { + Class value(); + } + // --- Annotation for the command --- /** diff --git a/testsrc/de/steamwar/command/PartOfCommand.java b/testsrc/de/steamwar/command/PartOfCommand.java new file mode 100644 index 0000000..4f2fe9a --- /dev/null +++ b/testsrc/de/steamwar/command/PartOfCommand.java @@ -0,0 +1,70 @@ +/* + * 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.command; + +import de.steamwar.command.dto.ExecutionIdentifier; +import de.steamwar.command.dto.TestSWCommand; +import de.steamwar.command.dto.TestTypeMapper; + +import java.util.Arrays; +import java.util.Collection; + +public class PartOfCommand { + + public static class ParentCommand extends TestSWCommand { + + public ParentCommand() { + super("parent"); + } + + @Register + public void execute(String s, String... args) { + throw new ExecutionIdentifier("ParentCommand with String..."); + } + + @Mapper("test") + public TestTypeMapper typeMapper() { + return new TestTypeMapper() { + @Override + public Integer map(String sender, PreviousArguments previousArguments, String s) { + return Integer.parseInt(s); + } + + @Override + public Collection tabCompletes(String sender, PreviousArguments previousArguments, String s) { + return Arrays.asList(s); + } + }; + } + } + + @AbstractSWCommand.PartOf(ParentCommand.class) + public static class SubCommand extends TestSWCommand { + + public SubCommand() { + super(null); + } + + @Register + public void execute(String s, @Mapper("test") int i) { + throw new ExecutionIdentifier("SubCommand with int"); + } + } +} diff --git a/testsrc/de/steamwar/command/PartOfCommandTest.java b/testsrc/de/steamwar/command/PartOfCommandTest.java new file mode 100644 index 0000000..b02614a --- /dev/null +++ b/testsrc/de/steamwar/command/PartOfCommandTest.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.command; + +import de.steamwar.command.dto.ExecutionIdentifier; +import org.junit.Test; + +import static de.steamwar.AssertionUtils.assertCMDFramework; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class PartOfCommandTest { + + @Test + public void testMerging() { + PartOfCommand.ParentCommand command = new PartOfCommand.ParentCommand(); + new PartOfCommand.SubCommand(); + try { + command.execute("test", "", new String[]{"0"}); + assertThat(true, is(false)); + } catch (Exception e) { + e.printStackTrace(); + assertCMDFramework(e, ExecutionIdentifier.class, "SubCommand with int"); + } + } +} From 241117a6a65b95bfcee9bd4b784d344f635a78f9 Mon Sep 17 00:00:00 2001 From: yoyosource Date: Sat, 18 Feb 2023 12:13:20 +0100 Subject: [PATCH 84/85] Fix test --- testsrc/de/steamwar/command/PartOfCommand.java | 4 ++-- testsrc/de/steamwar/command/PartOfCommandTest.java | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/testsrc/de/steamwar/command/PartOfCommand.java b/testsrc/de/steamwar/command/PartOfCommand.java index 4f2fe9a..9c66a4b 100644 --- a/testsrc/de/steamwar/command/PartOfCommand.java +++ b/testsrc/de/steamwar/command/PartOfCommand.java @@ -44,7 +44,7 @@ public class PartOfCommand { return new TestTypeMapper() { @Override public Integer map(String sender, PreviousArguments previousArguments, String s) { - return Integer.parseInt(s); + return -1; } @Override @@ -64,7 +64,7 @@ public class PartOfCommand { @Register public void execute(String s, @Mapper("test") int i) { - throw new ExecutionIdentifier("SubCommand with int"); + throw new ExecutionIdentifier("SubCommand with int " + i); } } } diff --git a/testsrc/de/steamwar/command/PartOfCommandTest.java b/testsrc/de/steamwar/command/PartOfCommandTest.java index b02614a..726384e 100644 --- a/testsrc/de/steamwar/command/PartOfCommandTest.java +++ b/testsrc/de/steamwar/command/PartOfCommandTest.java @@ -36,8 +36,7 @@ public class PartOfCommandTest { command.execute("test", "", new String[]{"0"}); assertThat(true, is(false)); } catch (Exception e) { - e.printStackTrace(); - assertCMDFramework(e, ExecutionIdentifier.class, "SubCommand with int"); + assertCMDFramework(e, ExecutionIdentifier.class, "SubCommand with int -1"); } } } From 69cff99801238984fd463de390b581f91f18a5d1 Mon Sep 17 00:00:00 2001 From: yoyosource Date: Sat, 18 Feb 2023 14:01:14 +0100 Subject: [PATCH 85/85] Make Map final --- src/de/steamwar/command/AbstractSWCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/de/steamwar/command/AbstractSWCommand.java b/src/de/steamwar/command/AbstractSWCommand.java index 7ba5e62..b8dc078 100644 --- a/src/de/steamwar/command/AbstractSWCommand.java +++ b/src/de/steamwar/command/AbstractSWCommand.java @@ -34,7 +34,7 @@ import java.util.stream.Collectors; public abstract class AbstractSWCommand { - private static Map>, List>> dependencyMap = new HashMap<>(); + private static final Map>, List>> dependencyMap = new HashMap<>(); private Class clazz; // This is used in createMappings()