001/*
002 * PlotSquared, a land and world management plugin for Minecraft.
003 * Copyright (C) IntellectualSites <https://intellectualsites.com>
004 * Copyright (C) IntellectualSites team and contributors
005 *
006 * This program is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 *
011 * This program is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
014 * GNU General Public License for more details.
015 *
016 * You should have received a copy of the GNU General Public License
017 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
018 */
019package com.plotsquared.core.database;
020
021import com.google.common.base.Charsets;
022import com.plotsquared.core.PlotSquared;
023import com.plotsquared.core.configuration.ConfigurationSection;
024import com.plotsquared.core.configuration.Settings;
025import com.plotsquared.core.configuration.Storage;
026import com.plotsquared.core.configuration.caption.CaptionUtility;
027import com.plotsquared.core.configuration.file.YamlConfiguration;
028import com.plotsquared.core.inject.annotations.WorldConfig;
029import com.plotsquared.core.listener.PlotListener;
030import com.plotsquared.core.location.BlockLoc;
031import com.plotsquared.core.plot.Plot;
032import com.plotsquared.core.plot.PlotArea;
033import com.plotsquared.core.plot.PlotCluster;
034import com.plotsquared.core.plot.PlotId;
035import com.plotsquared.core.plot.PlotSettings;
036import com.plotsquared.core.plot.comment.PlotComment;
037import com.plotsquared.core.plot.flag.FlagContainer;
038import com.plotsquared.core.plot.flag.FlagParseException;
039import com.plotsquared.core.plot.flag.GlobalFlagContainer;
040import com.plotsquared.core.plot.flag.PlotFlag;
041import com.plotsquared.core.plot.flag.types.BlockTypeListFlag;
042import com.plotsquared.core.util.EventDispatcher;
043import com.plotsquared.core.util.HashUtil;
044import com.plotsquared.core.util.StringMan;
045import com.plotsquared.core.util.task.RunnableVal;
046import com.plotsquared.core.util.task.TaskManager;
047import org.apache.logging.log4j.LogManager;
048import org.apache.logging.log4j.Logger;
049import org.checkerframework.checker.nullness.qual.NonNull;
050
051import java.sql.Connection;
052import java.sql.DatabaseMetaData;
053import java.sql.PreparedStatement;
054import java.sql.ResultSet;
055import java.sql.SQLException;
056import java.sql.Statement;
057import java.sql.Timestamp;
058import java.text.ParseException;
059import java.text.SimpleDateFormat;
060import java.util.ArrayList;
061import java.util.Collection;
062import java.util.HashMap;
063import java.util.HashSet;
064import java.util.Iterator;
065import java.util.LinkedHashMap;
066import java.util.List;
067import java.util.Map;
068import java.util.Map.Entry;
069import java.util.Queue;
070import java.util.Set;
071import java.util.UUID;
072import java.util.concurrent.CompletableFuture;
073import java.util.concurrent.ConcurrentHashMap;
074import java.util.concurrent.ConcurrentLinkedQueue;
075import java.util.concurrent.atomic.AtomicInteger;
076
077
078@SuppressWarnings("SqlDialectInspection")
079public class SQLManager implements AbstractDB {
080
081    private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + SQLManager.class.getSimpleName());
082
083    // Public final
084    public final String SET_OWNER;
085    public final String GET_ALL_PLOTS;
086    public final String CREATE_PLOTS;
087    public final String CREATE_SETTINGS;
088    public final String CREATE_TIERS;
089    public final String CREATE_PLOT;
090    public final String CREATE_PLOT_SAFE;
091    public final String CREATE_CLUSTER;
092
093    // Private Final
094    private final String prefix;
095    private final Database database;
096    private final boolean mySQL;
097    @SuppressWarnings({"unused", "FieldCanBeLocal"})
098    private final EventDispatcher eventDispatcher;
099    @SuppressWarnings({"unused", "FieldCanBeLocal"})
100    private final PlotListener plotListener;
101    private final YamlConfiguration worldConfiguration;
102    /**
103     * important tasks
104     */
105    public volatile Queue<Runnable> globalTasks;
106    /**
107     * Notify tasks
108     */
109    public volatile Queue<Runnable> notifyTasks;
110    /**
111     * plot
112     * plot_denied
113     * plot_helpers
114     * plot_trusted
115     * plot_comments
116     * plot_settings
117     * plot_rating
118     */
119    public volatile ConcurrentHashMap<Plot, Queue<UniqueStatement>> plotTasks;
120    /**
121     * player_meta
122     */
123    public volatile ConcurrentHashMap<UUID, Queue<UniqueStatement>> playerTasks;
124    /**
125     * cluster
126     * cluster_helpers
127     * cluster_invited
128     * cluster_settings
129     */
130    public volatile ConcurrentHashMap<PlotCluster, Queue<UniqueStatement>> clusterTasks;
131    // Private
132    private Connection connection;
133    private boolean closed = false;
134
135    /**
136     * Constructor
137     *
138     * @param database
139     * @param prefix   prefix
140     * @throws SQLException
141     * @throws ClassNotFoundException
142     */
143    public SQLManager(
144            final @NonNull Database database,
145            final @NonNull String prefix,
146            final @NonNull EventDispatcher eventDispatcher,
147            final @NonNull PlotListener plotListener,
148            @WorldConfig final @NonNull YamlConfiguration worldConfiguration
149    )
150            throws SQLException, ClassNotFoundException {
151        // Private final
152        this.eventDispatcher = eventDispatcher;
153        this.plotListener = plotListener;
154        this.worldConfiguration = worldConfiguration;
155        this.database = database;
156        this.connection = database.openConnection();
157        this.mySQL = database instanceof MySQL;
158        this.globalTasks = new ConcurrentLinkedQueue<>();
159        this.notifyTasks = new ConcurrentLinkedQueue<>();
160        this.plotTasks = new ConcurrentHashMap<>();
161        this.playerTasks = new ConcurrentHashMap<>();
162        this.clusterTasks = new ConcurrentHashMap<>();
163        this.prefix = prefix;
164        this.SET_OWNER = "UPDATE `" + this.prefix
165                + "plot` SET `owner` = ? WHERE `plot_id_x` = ? AND `plot_id_z` = ? AND `world` = ?";
166        this.GET_ALL_PLOTS =
167                "SELECT `id`, `plot_id_x`, `plot_id_z`, `world` FROM `" + this.prefix + "plot`";
168        this.CREATE_PLOTS = "INSERT INTO `" + this.prefix
169                + "plot`(`plot_id_x`, `plot_id_z`, `owner`, `world`, `timestamp`) values ";
170        this.CREATE_SETTINGS =
171                "INSERT INTO `" + this.prefix + "plot_settings` (`plot_plot_id`) values ";
172        this.CREATE_TIERS =
173                "INSERT INTO `" + this.prefix + "plot_%tier%` (`plot_plot_id`, `user_uuid`) values ";
174        this.CREATE_PLOT = "INSERT INTO `" + this.prefix
175                + "plot`(`plot_id_x`, `plot_id_z`, `owner`, `world`, `timestamp`) VALUES(?, ?, ?, ?, ?)";
176
177        if (mySQL) {
178            this.CREATE_PLOT_SAFE = "INSERT IGNORE INTO `" + this.prefix
179                    + "plot`(`plot_id_x`, `plot_id_z`, `owner`, `world`, `timestamp`) SELECT ?, ?, ?, ?, ? FROM DUAL WHERE NOT EXISTS (SELECT null FROM `"
180                    + this.prefix + "plot` WHERE `world` = ? AND `plot_id_x` = ? AND `plot_id_z` = ?)";
181        } else {
182            this.CREATE_PLOT_SAFE = "INSERT INTO `" + this.prefix
183                    + "plot`(`plot_id_x`, `plot_id_z`, `owner`, `world`, `timestamp`) SELECT ?, ?, ?, ?, ? WHERE NOT EXISTS (SELECT null FROM `"
184                    + this.prefix + "plot` WHERE `world` = ? AND `plot_id_x` = ? AND `plot_id_z` = ?)";
185        }
186        this.CREATE_CLUSTER = "INSERT INTO `" + this.prefix
187                + "cluster`(`pos1_x`, `pos1_z`, `pos2_x`, `pos2_z`, `owner`, `world`) VALUES(?, ?, ?, ?, ?, ?)";
188        try {
189            createTables();
190        } catch (SQLException e) {
191            e.printStackTrace();
192        }
193        TaskManager.runTaskAsync(() -> {
194            long last = System.currentTimeMillis();
195            while (!SQLManager.this.closed) {
196                boolean hasTask =
197                        !globalTasks.isEmpty() || !playerTasks.isEmpty() || !plotTasks.isEmpty()
198                                || !clusterTasks.isEmpty();
199                if (hasTask) {
200                    if (SQLManager.this.mySQL && System.currentTimeMillis() - last > 550000
201                            || !isValid()) {
202                        last = System.currentTimeMillis();
203                        reconnect();
204                    }
205                    if (!sendBatch()) {
206                        try {
207                            if (!getNotifyTasks().isEmpty()) {
208                                for (Runnable task : getNotifyTasks()) {
209                                    TaskManager.runTask(task);
210                                }
211                                getNotifyTasks().clear();
212                            }
213                            Thread.sleep(50);
214                        } catch (InterruptedException e) {
215                            e.printStackTrace();
216                        }
217                    }
218                } else {
219                    try {
220                        Thread.sleep(1000);
221                    } catch (InterruptedException e) {
222                        e.printStackTrace();
223                    }
224                }
225            }
226        });
227    }
228
229    public boolean isValid() {
230        try {
231            if (connection.isClosed()) {
232                return false;
233            }
234        } catch (SQLException e) {
235            return false;
236        }
237        try (PreparedStatement stmt = this.connection.prepareStatement("SELECT 1")) {
238            stmt.execute();
239            return true;
240        } catch (Throwable e) {
241            return false;
242        }
243    }
244
245    public void reconnect() {
246        try {
247            close();
248            SQLManager.this.closed = false;
249            SQLManager.this.connection = database.forceConnection();
250        } catch (SQLException | ClassNotFoundException e) {
251            e.printStackTrace();
252        }
253    }
254
255    public synchronized Queue<Runnable> getGlobalTasks() {
256        return this.globalTasks;
257    }
258
259    public synchronized Queue<Runnable> getNotifyTasks() {
260        return this.notifyTasks;
261    }
262
263    public synchronized void addPlotTask(@NonNull Plot plot, UniqueStatement task) {
264        Queue<UniqueStatement> tasks = this.plotTasks.get(plot);
265        if (tasks == null) {
266            tasks = new ConcurrentLinkedQueue<>();
267            this.plotTasks.put(plot, tasks);
268        }
269        if (task == null) {
270            task = new UniqueStatement(String.valueOf(plot.hashCode())) {
271
272                @Override
273                public PreparedStatement get() {
274                    return null;
275                }
276
277                @Override
278                public void set(PreparedStatement statement) {
279                }
280
281                @Override
282                public void addBatch(PreparedStatement statement) {
283                }
284
285                @Override
286                public void execute(PreparedStatement statement) {
287                }
288
289            };
290        }
291        tasks.add(task);
292    }
293
294    public synchronized void addPlayerTask(UUID uuid, UniqueStatement task) {
295        if (uuid == null) {
296            return;
297        }
298        Queue<UniqueStatement> tasks = this.playerTasks.get(uuid);
299        if (tasks == null) {
300            tasks = new ConcurrentLinkedQueue<>();
301            this.playerTasks.put(uuid, tasks);
302        }
303        if (task == null) {
304            task = new UniqueStatement(String.valueOf(uuid.hashCode())) {
305
306                @Override
307                public PreparedStatement get() {
308                    return null;
309                }
310
311                @Override
312                public void set(PreparedStatement statement) {
313                }
314
315                @Override
316                public void addBatch(PreparedStatement statement) {
317                }
318
319                @Override
320                public void execute(PreparedStatement statement) {
321                }
322
323            };
324        }
325        tasks.add(task);
326    }
327
328    public synchronized void addClusterTask(PlotCluster cluster, UniqueStatement task) {
329        Queue<UniqueStatement> tasks = this.clusterTasks.get(cluster);
330        if (tasks == null) {
331            tasks = new ConcurrentLinkedQueue<>();
332            this.clusterTasks.put(cluster, tasks);
333        }
334        if (task == null) {
335            task = new UniqueStatement(String.valueOf(cluster.hashCode())) {
336
337                @Override
338                public PreparedStatement get() {
339                    return null;
340                }
341
342                @Override
343                public void set(PreparedStatement statement) {
344                }
345
346                @Override
347                public void addBatch(PreparedStatement statement) {
348                }
349
350                @Override
351                public void execute(PreparedStatement statement) {
352                }
353
354            };
355        }
356        tasks.add(task);
357    }
358
359    public synchronized void addGlobalTask(Runnable task) {
360        getGlobalTasks().add(task);
361    }
362
363    public synchronized void addNotifyTask(Runnable task) {
364        if (task != null) {
365            getNotifyTasks().add(task);
366        }
367    }
368
369    public boolean sendBatch() {
370        try {
371            if (!getGlobalTasks().isEmpty()) {
372                if (this.connection.getAutoCommit()) {
373                    this.connection.setAutoCommit(false);
374                }
375                Runnable task = getGlobalTasks().remove();
376                if (task != null) {
377                    try {
378                        task.run();
379                    } catch (Throwable e) {
380                        LOGGER.error("============ DATABASE ERROR ============");
381                        LOGGER.error("============ DATABASE ERROR ============");
382                        LOGGER.error("There was an error updating the database.");
383                        LOGGER.error(" - It will be corrected on shutdown");
384                        e.printStackTrace();
385                        LOGGER.error("========================================");
386                    }
387                }
388                commit();
389                return true;
390            }
391            int count = -1;
392            if (!this.plotTasks.isEmpty()) {
393                count = Math.max(count, 0);
394                if (this.connection.getAutoCommit()) {
395                    this.connection.setAutoCommit(false);
396                }
397                String method = null;
398                PreparedStatement statement = null;
399                UniqueStatement task = null;
400                UniqueStatement lastTask = null;
401                Iterator<Entry<Plot, Queue<UniqueStatement>>> iterator =
402                        this.plotTasks.entrySet().iterator();
403                while (iterator.hasNext()) {
404                    try {
405                        Entry<Plot, Queue<UniqueStatement>> entry = iterator.next();
406                        Queue<UniqueStatement> tasks = entry.getValue();
407                        if (tasks.isEmpty()) {
408                            iterator.remove();
409                            continue;
410                        }
411                        task = tasks.remove();
412                        count++;
413                        if (task != null) {
414                            if (task.method == null || !task.method.equals(method)
415                                    || statement == null) {
416                                if (statement != null) {
417                                    lastTask.execute(statement);
418                                    statement.close();
419                                }
420                                method = task.method;
421                                statement = task.get();
422                            }
423                            task.set(statement);
424                            task.addBatch(statement);
425                            try {
426                                if (statement.isClosed()) {
427                                    statement = null;
428                                }
429                            } catch (NullPointerException | AbstractMethodError ignore) {
430                            }
431                        }
432                        lastTask = task;
433                    } catch (Throwable e) {
434                        LOGGER.error("============ DATABASE ERROR ============");
435                        LOGGER.error("There was an error updating the database.");
436                        LOGGER.error(" - It will be corrected on shutdown");
437                        LOGGER.error("========================================");
438                        e.printStackTrace();
439                        LOGGER.error("========================================");
440                    }
441                }
442                if (statement != null && task != null) {
443                    task.execute(statement);
444                    statement.close();
445                }
446            }
447            if (!this.playerTasks.isEmpty()) {
448                count = Math.max(count, 0);
449                if (this.connection.getAutoCommit()) {
450                    this.connection.setAutoCommit(false);
451                }
452                String method = null;
453                PreparedStatement statement = null;
454                UniqueStatement task = null;
455                UniqueStatement lastTask = null;
456                for (Entry<UUID, Queue<UniqueStatement>> entry : this.playerTasks.entrySet()) {
457                    try {
458                        UUID uuid = entry.getKey();
459                        if (this.playerTasks.get(uuid).isEmpty()) {
460                            this.playerTasks.remove(uuid);
461                            continue;
462                        }
463                        task = this.playerTasks.get(uuid).remove();
464                        count++;
465                        if (task != null) {
466                            if (task.method == null || !task.method.equals(method)) {
467                                if (statement != null) {
468                                    lastTask.execute(statement);
469                                    statement.close();
470                                }
471                                method = task.method;
472                                statement = task.get();
473                            }
474                            task.set(statement);
475                            task.addBatch(statement);
476                        }
477                        lastTask = task;
478                    } catch (Throwable e) {
479                        LOGGER.error("============ DATABASE ERROR ============");
480                        LOGGER.error("There was an error updating the database.");
481                        LOGGER.error(" - It will be corrected on shutdown");
482                        LOGGER.error("========================================");
483                        e.printStackTrace();
484                        LOGGER.error("========================================");
485                    }
486                }
487                if (statement != null && task != null) {
488                    task.execute(statement);
489                    statement.close();
490                }
491            }
492            if (!this.clusterTasks.isEmpty()) {
493                count = Math.max(count, 0);
494                if (this.connection.getAutoCommit()) {
495                    this.connection.setAutoCommit(false);
496                }
497                String method = null;
498                PreparedStatement statement = null;
499                UniqueStatement task = null;
500                UniqueStatement lastTask = null;
501                for (Entry<PlotCluster, Queue<UniqueStatement>> entry : this.clusterTasks
502                        .entrySet()) {
503                    try {
504                        PlotCluster cluster = entry.getKey();
505                        if (this.clusterTasks.get(cluster).isEmpty()) {
506                            this.clusterTasks.remove(cluster);
507                            continue;
508                        }
509                        task = this.clusterTasks.get(cluster).remove();
510                        count++;
511                        if (task != null) {
512                            if (task.method == null || !task.method.equals(method)) {
513                                if (statement != null) {
514                                    lastTask.execute(statement);
515                                    statement.close();
516                                }
517                                method = task.method;
518                                statement = task.get();
519                            }
520                            task.set(statement);
521                            task.addBatch(statement);
522                        }
523                        lastTask = task;
524                    } catch (Throwable e) {
525                        LOGGER.error("============ DATABASE ERROR ============");
526                        LOGGER.error("There was an error updating the database.");
527                        LOGGER.error(" - It will be corrected on shutdown");
528                        LOGGER.error("========================================");
529                        e.printStackTrace();
530                        LOGGER.error("========================================");
531                    }
532                }
533                if (statement != null && task != null) {
534                    task.execute(statement);
535                    statement.close();
536                }
537            }
538            if (count > 0) {
539                commit();
540                return true;
541            }
542            if (count != -1) {
543                if (!this.connection.getAutoCommit()) {
544                    this.connection.setAutoCommit(true);
545                }
546            }
547            if (!this.clusterTasks.isEmpty()) {
548                this.clusterTasks.clear();
549            }
550            if (!this.plotTasks.isEmpty()) {
551                this.plotTasks.clear();
552            }
553        } catch (Throwable e) {
554            LOGGER.error("============ DATABASE ERROR ============");
555            LOGGER.error("There was an error updating the database.");
556            LOGGER.error(" - It will be corrected on shutdown");
557            LOGGER.error("========================================");
558            e.printStackTrace();
559            LOGGER.error("========================================");
560        }
561        return false;
562    }
563
564    public Connection getConnection() {
565        return this.connection;
566    }
567
568    /**
569     * Set Plot owner
570     *
571     * @param plot Plot Object
572     * @param uuid Owner UUID
573     */
574    @Override
575    public void setOwner(final Plot plot, final UUID uuid) {
576        addPlotTask(plot, new UniqueStatement("setOwner") {
577            @Override
578            public void set(PreparedStatement statement) throws SQLException {
579                statement.setString(1, uuid.toString());
580                statement.setInt(2, plot.getId().getX());
581                statement.setInt(3, plot.getId().getY());
582                statement.setString(4, plot.getArea().toString());
583            }
584
585            @Override
586            public PreparedStatement get() throws SQLException {
587                return SQLManager.this.connection.prepareStatement(SQLManager.this.SET_OWNER);
588            }
589        });
590    }
591
592    @Override
593    public void createPlotsAndData(final List<Plot> myList, final Runnable whenDone) {
594        addGlobalTask(() -> {
595            try {
596                // Create the plots
597                createPlots(myList, () -> {
598                    final Map<PlotId, Integer> idMap = new HashMap<>();
599
600                    try {
601                        // Creating datastructures
602                        HashMap<PlotId, Plot> plotMap = new HashMap<>();
603                        for (Plot plot : myList) {
604                            plotMap.put(plot.getId(), plot);
605                        }
606                        ArrayList<LegacySettings> settings = new ArrayList<>();
607                        final ArrayList<UUIDPair> helpers = new ArrayList<>();
608                        final ArrayList<UUIDPair> trusted = new ArrayList<>();
609                        final ArrayList<UUIDPair> denied = new ArrayList<>();
610
611                        // Populating structures
612                        try (PreparedStatement stmt = SQLManager.this.connection
613                                .prepareStatement(SQLManager.this.GET_ALL_PLOTS);
614                             ResultSet result = stmt.executeQuery()) {
615                            while (result.next()) {
616                                int id = result.getInt("id");
617                                int x = result.getInt("plot_id_x");
618                                int y = result.getInt("plot_id_z");
619                                PlotId plotId = PlotId.of(x, y);
620                                Plot plot = plotMap.get(plotId);
621                                idMap.put(plotId, id);
622                                if (plot != null) {
623                                    settings.add(new LegacySettings(id, plot.getSettings()));
624                                    for (UUID uuid : plot.getDenied()) {
625                                        denied.add(new UUIDPair(id, uuid));
626                                    }
627                                    for (UUID uuid : plot.getMembers()) {
628                                        trusted.add(new UUIDPair(id, uuid));
629                                    }
630                                    for (UUID uuid : plot.getTrusted()) {
631                                        helpers.add(new UUIDPair(id, uuid));
632                                    }
633                                }
634                            }
635                        }
636
637                        createFlags(idMap, myList, () -> createSettings(
638                                settings,
639                                () -> createTiers(helpers, "helpers",
640                                        () -> createTiers(trusted, "trusted",
641                                                () -> createTiers(denied, "denied", () -> {
642                                                    try {
643                                                        SQLManager.this.connection.commit();
644                                                    } catch (SQLException e) {
645                                                        e.printStackTrace();
646                                                    }
647                                                    if (whenDone != null) {
648                                                        whenDone.run();
649                                                    }
650                                                })
651                                        )
652                                )
653                        ));
654                    } catch (SQLException e) {
655                        LOGGER.warn("Failed to set all flags and member tiers for plots", e);
656                        try {
657                            SQLManager.this.connection.commit();
658                        } catch (SQLException e1) {
659                            e1.printStackTrace();
660                        }
661                    }
662                });
663            } catch (Exception e) {
664                LOGGER.warn("Warning! Failed to set all helper for plots", e);
665                try {
666                    SQLManager.this.connection.commit();
667                } catch (SQLException e1) {
668                    e1.printStackTrace();
669                }
670            }
671        });
672    }
673
674    /**
675     * Create a plot
676     *
677     * @param myList list of plots to be created
678     */
679    public void createTiers(ArrayList<UUIDPair> myList, final String tier, Runnable whenDone) {
680        StmtMod<UUIDPair> mod = new StmtMod<>() {
681            @Override
682            public String getCreateMySQL(int size) {
683                return getCreateMySQL(size, SQLManager.this.CREATE_TIERS.replaceAll("%tier%", tier),
684                        2
685                );
686            }
687
688            @Override
689            public String getCreateSQLite(int size) {
690                return getCreateSQLite(size,
691                        "INSERT INTO `" + SQLManager.this.prefix + "plot_" + tier
692                                + "` SELECT ? AS `plot_plot_id`, ? AS `user_uuid`", 2
693                );
694            }
695
696            @Override
697            public String getCreateSQL() {
698                return "INSERT INTO `" + SQLManager.this.prefix + "plot_" + tier
699                        + "` (`plot_plot_id`, `user_uuid`) VALUES(?,?)";
700            }
701
702            @Override
703            public void setMySQL(PreparedStatement stmt, int i, UUIDPair pair)
704                    throws SQLException {
705                stmt.setInt(i * 2 + 1, pair.id);
706                stmt.setString(i * 2 + 2, pair.uuid.toString());
707            }
708
709            @Override
710            public void setSQLite(PreparedStatement stmt, int i, UUIDPair pair)
711                    throws SQLException {
712                stmt.setInt(i * 2 + 1, pair.id);
713                stmt.setString(i * 2 + 2, pair.uuid.toString());
714            }
715
716            @Override
717            public void setSQL(PreparedStatement stmt, UUIDPair pair)
718                    throws SQLException {
719                stmt.setInt(1, pair.id);
720                stmt.setString(2, pair.uuid.toString());
721            }
722        };
723        setBulk(myList, mod, whenDone);
724    }
725
726    public void createFlags(Map<PlotId, Integer> ids, List<Plot> plots, Runnable whenDone) {
727        try (final PreparedStatement preparedStatement = this.connection.prepareStatement(
728                "INSERT INTO `" + SQLManager.this.prefix
729                        + "plot_flags`(`plot_id`, `flag`, `value`) VALUES(?, ?, ?)")) {
730            for (final Plot plot : plots) {
731                final FlagContainer flagContainer = plot.getFlagContainer();
732                for (final PlotFlag<?, ?> flagEntry : flagContainer.getFlagMap().values()) {
733                    preparedStatement.setInt(1, ids.get(plot.getId()));
734                    preparedStatement.setString(2, flagEntry.getName());
735                    preparedStatement.setString(3, flagEntry.toString());
736                    preparedStatement.addBatch();
737                }
738                try {
739                    preparedStatement.executeBatch();
740                } catch (final Exception e) {
741                    LOGGER.error("Failed to store flag values for plot with entry ID: {}", plot);
742                    e.printStackTrace();
743                    continue;
744                }
745                LOGGER.info(
746                        "- Finished converting flag values for plot with entry ID: {}",
747                        plot.getId()
748                );
749            }
750        } catch (final Exception e) {
751            LOGGER.error("Failed to store flag values", e);
752        }
753        LOGGER.info("Finished converting flags ({} plots processed)", plots.size());
754        whenDone.run();
755    }
756
757    /**
758     * Create a plot
759     *
760     * @param myList list of plots to be created
761     */
762    public void createPlots(List<Plot> myList, Runnable whenDone) {
763        StmtMod<Plot> mod = new StmtMod<>() {
764            @Override
765            public String getCreateMySQL(int size) {
766                return getCreateMySQL(size, SQLManager.this.CREATE_PLOTS, 5);
767            }
768
769            @Override
770            public String getCreateSQLite(int size) {
771                return getCreateSQLite(size, "INSERT INTO `" + SQLManager.this.prefix
772                                + "plot` SELECT ? AS `id`, ? AS `plot_id_x`, ? AS `plot_id_z`, ? AS `owner`, ? AS `world`, ? AS `timestamp` ",
773                        6
774                );
775            }
776
777            @Override
778            public String getCreateSQL() {
779                return SQLManager.this.CREATE_PLOT;
780            }
781
782            @Override
783            public void setMySQL(PreparedStatement stmt, int i, Plot plot)
784                    throws SQLException {
785                stmt.setInt(i * 5 + 1, plot.getId().getX());
786                stmt.setInt(i * 5 + 2, plot.getId().getY());
787                try {
788                    stmt.setString(i * 5 + 3, plot.getOwnerAbs().toString());
789                } catch (SQLException ignored) {
790                    stmt.setString(i * 5 + 3, everyone.toString());
791                }
792                stmt.setString(i * 5 + 4, plot.getArea().toString());
793                stmt.setTimestamp(i * 5 + 5, new Timestamp(plot.getTimestamp()));
794            }
795
796            @Override
797            public void setSQLite(PreparedStatement stmt, int i, Plot plot)
798                    throws SQLException {
799                stmt.setNull(i * 6 + 1, 4);
800                stmt.setInt(i * 6 + 2, plot.getId().getX());
801                stmt.setInt(i * 6 + 3, plot.getId().getY());
802                try {
803                    stmt.setString(i * 6 + 4, plot.getOwnerAbs().toString());
804                } catch (SQLException ignored) {
805                    stmt.setString(i * 6 + 4, everyone.toString());
806                }
807                stmt.setString(i * 6 + 5, plot.getArea().toString());
808                stmt.setTimestamp(i * 6 + 6, new Timestamp(plot.getTimestamp()));
809            }
810
811            @Override
812            public void setSQL(PreparedStatement stmt, Plot plot) throws SQLException {
813                stmt.setInt(1, plot.getId().getX());
814                stmt.setInt(2, plot.getId().getY());
815                stmt.setString(3, plot.getOwnerAbs().toString());
816                stmt.setString(4, plot.getArea().toString());
817                stmt.setTimestamp(5, new Timestamp(plot.getTimestamp()));
818
819            }
820        };
821        setBulk(myList, mod, whenDone);
822    }
823
824    public <T> void setBulk(List<T> objList, StmtMod<T> mod, Runnable whenDone) {
825        int size = objList.size();
826        if (size == 0) {
827            if (whenDone != null) {
828                whenDone.run();
829            }
830            return;
831        }
832        int packet;
833        if (this.mySQL) {
834            packet = Math.min(size, 5000);
835        } else {
836            packet = Math.min(size, 50);
837        }
838        int amount = size / packet;
839        try {
840            int count = 0;
841            PreparedStatement preparedStmt = null;
842            int last = -1;
843            for (int j = 0; j <= amount; j++) {
844                List<T> subList = objList.subList(j * packet, Math.min(size, (j + 1) * packet));
845                if (subList.isEmpty()) {
846                    break;
847                }
848                String statement;
849                if (last == -1) {
850                    last = subList.size();
851                    statement = mod.getCreateMySQL(subList.size());
852                    preparedStmt = this.connection.prepareStatement(statement);
853                }
854                if (subList.size() != last || count % 5000 == 0 && count > 0) {
855                    preparedStmt.executeBatch();
856                    preparedStmt.close();
857                    statement = mod.getCreateMySQL(subList.size());
858                    preparedStmt = this.connection.prepareStatement(statement);
859                }
860                for (int i = 0; i < subList.size(); i++) {
861                    count++;
862                    T obj = subList.get(i);
863                    mod.setMySQL(preparedStmt, i, obj);
864                }
865                last = subList.size();
866                preparedStmt.addBatch();
867            }
868            preparedStmt.executeBatch();
869            preparedStmt.clearParameters();
870            preparedStmt.close();
871            if (whenDone != null) {
872                whenDone.run();
873            }
874            return;
875        } catch (SQLException e) {
876            if (this.mySQL) {
877                LOGGER.error("1: | {}", objList.get(0).getClass().getCanonicalName());
878                e.printStackTrace();
879            }
880        }
881        try {
882            int count = 0;
883            PreparedStatement preparedStmt = null;
884            int last = -1;
885            for (int j = 0; j <= amount; j++) {
886                List<T> subList = objList.subList(j * packet, Math.min(size, (j + 1) * packet));
887                if (subList.isEmpty()) {
888                    break;
889                }
890                String statement;
891                if (last == -1) {
892                    last = subList.size();
893                    statement = mod.getCreateSQLite(subList.size());
894                    preparedStmt = this.connection.prepareStatement(statement);
895                }
896                if (subList.size() != last || count % 5000 == 0 && count > 0) {
897                    preparedStmt.executeBatch();
898                    preparedStmt.clearParameters();
899                    statement = mod.getCreateSQLite(subList.size());
900                    preparedStmt = this.connection.prepareStatement(statement);
901                }
902                for (int i = 0; i < subList.size(); i++) {
903                    count++;
904                    T obj = subList.get(i);
905                    mod.setSQLite(preparedStmt, i, obj);
906                }
907                last = subList.size();
908                preparedStmt.addBatch();
909            }
910            preparedStmt.executeBatch();
911            preparedStmt.clearParameters();
912            preparedStmt.close();
913        } catch (SQLException e) {
914            e.printStackTrace();
915            LOGGER.error("2: | {}", objList.get(0).getClass().getCanonicalName());
916            LOGGER.error("Could not bulk save!");
917            try (PreparedStatement preparedStmt = this.connection
918                    .prepareStatement(mod.getCreateSQL())) {
919                for (T obj : objList) {
920                    mod.setSQL(preparedStmt, obj);
921                    preparedStmt.addBatch();
922                }
923                preparedStmt.executeBatch();
924            } catch (SQLException e3) {
925                LOGGER.error("Failed to save all", e);
926                e3.printStackTrace();
927            }
928        }
929        if (whenDone != null) {
930            whenDone.run();
931        }
932    }
933
934    public void createSettings(final ArrayList<LegacySettings> myList, final Runnable whenDone) {
935        try (final PreparedStatement preparedStatement = this.connection.prepareStatement(
936                "INSERT INTO `" + SQLManager.this.prefix + "plot_settings`"
937                        + "(`plot_plot_id`,`biome`,`rain`,`custom_time`,`time`,`deny_entry`,`alias`,`merged`,`position`) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)")) {
938
939            int packet;
940            if (this.mySQL) {
941                packet = Math.min(myList.size(), 5000);
942            } else {
943                packet = Math.min(myList.size(), 50);
944            }
945
946            int totalUpdated = 0;
947            int updated = 0;
948
949            for (final LegacySettings legacySettings : myList) {
950                preparedStatement.setInt(1, legacySettings.id);
951                preparedStatement.setNull(2, 4);
952                preparedStatement.setNull(3, 4);
953                preparedStatement.setNull(4, 4);
954                preparedStatement.setNull(5, 4);
955                preparedStatement.setNull(6, 4);
956                if (legacySettings.settings.getAlias().isEmpty()) {
957                    preparedStatement.setNull(7, 4);
958                } else {
959                    preparedStatement.setString(7, legacySettings.settings.getAlias());
960                }
961                boolean[] merged = legacySettings.settings.getMerged();
962                int hash = HashUtil.hash(merged);
963                preparedStatement.setInt(8, hash);
964                BlockLoc loc = legacySettings.settings.getPosition();
965                String position;
966                if (loc.getY() == 0) {
967                    position = "DEFAULT";
968                } else {
969                    position = loc.getX() + "," + loc.getY() + ',' + loc.getZ();
970                }
971                preparedStatement.setString(9, position);
972                preparedStatement.addBatch();
973                if (++updated >= packet) {
974                    try {
975                        preparedStatement.executeBatch();
976                    } catch (final Exception e) {
977                        LOGGER.error("Failed to store settings for plot with entry ID: {}", legacySettings.id);
978                        e.printStackTrace();
979                        continue;
980                    }
981                }
982                totalUpdated += 1;
983            }
984
985            if (totalUpdated < myList.size()) {
986                try {
987                    preparedStatement.executeBatch();
988                } catch (final Exception e) {
989                    LOGGER.error("Failed to store settings", e);
990                }
991            }
992        } catch (final Exception e) {
993            LOGGER.error("Failed to store settings", e);
994        }
995        LOGGER.info("Finished converting settings ({} plots processed)", myList.size());
996        whenDone.run();
997    }
998
999    public void createEmptySettings(final ArrayList<Integer> myList, final Runnable whenDone) {
1000        final StmtMod<Integer> mod = new StmtMod<>() {
1001            @Override
1002            public String getCreateMySQL(int size) {
1003                return getCreateMySQL(size, SQLManager.this.CREATE_SETTINGS, 1);
1004            }
1005
1006            @Override
1007            public String getCreateSQLite(int size) {
1008                return getCreateSQLite(size, "INSERT INTO `" + SQLManager.this.prefix
1009                        + "plot_settings` SELECT ? AS `plot_plot_id`, ? AS `biome`, ? AS `rain`, ? AS `custom_time`, ? AS `time`, ? AS "
1010                        + "`deny_entry`, ? AS `alias`, ? AS `merged`, ? AS `position` ", 10);
1011            }
1012
1013            @Override
1014            public String getCreateSQL() {
1015                return "INSERT INTO `" + SQLManager.this.prefix
1016                        + "plot_settings`(`plot_plot_id`) VALUES(?)";
1017            }
1018
1019            @Override
1020            public void setMySQL(PreparedStatement stmt, int i, Integer id)
1021                    throws SQLException {
1022                stmt.setInt(i + 1, id);
1023            }
1024
1025            @Override
1026            public void setSQLite(PreparedStatement stmt, int i, Integer id)
1027                    throws SQLException {
1028                stmt.setInt(i * 10 + 1, id);
1029                stmt.setNull(i * 10 + 2, 4);
1030                stmt.setNull(i * 10 + 3, 4);
1031                stmt.setNull(i * 10 + 4, 4);
1032                stmt.setNull(i * 10 + 5, 4);
1033                stmt.setNull(i * 10 + 6, 4);
1034                stmt.setNull(i * 10 + 7, 4);
1035                stmt.setNull(i * 10 + 8, 4);
1036                stmt.setString(i * 10 + 9, "DEFAULT");
1037            }
1038
1039            @Override
1040            public void setSQL(PreparedStatement stmt, Integer id) throws SQLException {
1041                stmt.setInt(1, id);
1042            }
1043        };
1044        addGlobalTask(() -> setBulk(myList, mod, whenDone));
1045    }
1046
1047    public void createPlotSafe(final Plot plot, final Runnable success, final Runnable failure) {
1048        addPlotTask(plot, new UniqueStatement("createPlotSafe_" + plot.hashCode()) {
1049            @Override
1050            public void set(PreparedStatement statement) throws SQLException {
1051                statement.setInt(1, plot.getId().getX());
1052                statement.setInt(2, plot.getId().getY());
1053                statement.setString(3, plot.getOwnerAbs().toString());
1054                statement.setString(4, plot.getArea().toString());
1055                statement.setTimestamp(5, new Timestamp(plot.getTimestamp()));
1056                statement.setString(6, plot.getArea().toString());
1057                statement.setInt(7, plot.getId().getX());
1058                statement.setInt(8, plot.getId().getY());
1059            }
1060
1061            @Override
1062            public PreparedStatement get() throws SQLException {
1063                return SQLManager.this.connection.prepareStatement(
1064                        SQLManager.this.CREATE_PLOT_SAFE,
1065                        Statement.RETURN_GENERATED_KEYS
1066                );
1067            }
1068
1069            @Override
1070            public void execute(PreparedStatement statement) {
1071
1072            }
1073
1074            @Override
1075            public void addBatch(PreparedStatement statement) throws SQLException {
1076                int inserted = statement.executeUpdate();
1077                if (inserted > 0) {
1078                    try (ResultSet keys = statement.getGeneratedKeys()) {
1079                        if (keys.next()) {
1080                            plot.temp = keys.getInt(1);
1081                            addPlotTask(plot, new UniqueStatement(
1082                                    "createPlotAndSettings_settings_" + plot.hashCode()) {
1083                                @Override
1084                                public void set(PreparedStatement statement)
1085                                        throws SQLException {
1086                                    statement.setInt(1, getId(plot));
1087                                }
1088
1089                                @Override
1090                                public PreparedStatement get() throws SQLException {
1091                                    return SQLManager.this.connection.prepareStatement(
1092                                            "INSERT INTO `" + SQLManager.this.prefix
1093                                                    + "plot_settings`(`plot_plot_id`) VALUES(?)");
1094                                }
1095                            });
1096                            if (success != null) {
1097                                addNotifyTask(success);
1098                            }
1099                            return;
1100                        }
1101                    }
1102                }
1103                if (failure != null) {
1104                    failure.run();
1105                }
1106            }
1107        });
1108    }
1109
1110    public void commit() {
1111        if (this.closed) {
1112            return;
1113        }
1114        try {
1115            if (!this.connection.getAutoCommit()) {
1116                this.connection.commit();
1117                this.connection.setAutoCommit(true);
1118            }
1119        } catch (SQLException e) {
1120            e.printStackTrace();
1121        }
1122    }
1123
1124    @Override
1125    public void createPlotAndSettings(final Plot plot, Runnable whenDone) {
1126        addPlotTask(plot, new UniqueStatement("createPlotAndSettings_" + plot.hashCode()) {
1127            @Override
1128            public void set(PreparedStatement statement) throws SQLException {
1129                statement.setInt(1, plot.getId().getX());
1130                statement.setInt(2, plot.getId().getY());
1131                statement.setString(3, plot.getOwnerAbs().toString());
1132                statement.setString(4, plot.getArea().toString());
1133                statement.setTimestamp(5, new Timestamp(plot.getTimestamp()));
1134            }
1135
1136            @Override
1137            public PreparedStatement get() throws SQLException {
1138                return SQLManager.this.connection
1139                        .prepareStatement(SQLManager.this.CREATE_PLOT, Statement.RETURN_GENERATED_KEYS);
1140            }
1141
1142            @Override
1143            public void execute(PreparedStatement statement) {
1144            }
1145
1146            @Override
1147            public void addBatch(PreparedStatement statement) throws SQLException {
1148                statement.executeUpdate();
1149                try (ResultSet keys = statement.getGeneratedKeys()) {
1150                    if (keys.next()) {
1151                        plot.temp = keys.getInt(1);
1152                    }
1153                }
1154            }
1155        });
1156        addPlotTask(plot, new UniqueStatement("createPlotAndSettings_settings_" + plot.hashCode()) {
1157            @Override
1158            public void set(PreparedStatement statement) throws SQLException {
1159                statement.setInt(1, getId(plot));
1160            }
1161
1162            @Override
1163            public PreparedStatement get() throws SQLException {
1164                return SQLManager.this.connection.prepareStatement(
1165                        "INSERT INTO `" + SQLManager.this.prefix
1166                                + "plot_settings`(`plot_plot_id`) VALUES(?)");
1167            }
1168        });
1169        addNotifyTask(whenDone);
1170    }
1171
1172    /**
1173     * Create tables.
1174     *
1175     * @throws SQLException
1176     */
1177    @Override
1178    public void createTables() throws SQLException {
1179        String[] tables =
1180                new String[]{"plot", "plot_denied", "plot_helpers", "plot_comments", "plot_trusted",
1181                        "plot_rating", "plot_settings", "cluster", "player_meta", "plot_flags"};
1182        DatabaseMetaData meta = this.connection.getMetaData();
1183        int create = 0;
1184        for (String s : tables) {
1185            ResultSet set = meta.getTables(null, null, this.prefix + s, new String[]{"TABLE"});
1186            //            ResultSet set = meta.getTables(null, null, prefix + s, null);
1187            if (!set.next()) {
1188                create++;
1189            }
1190            set.close();
1191        }
1192        if (create == 0) {
1193            return;
1194        }
1195        boolean addConstraint = create == tables.length;
1196        try (Statement stmt = this.connection.createStatement()) {
1197            if (this.mySQL) {
1198                stmt.addBatch("CREATE TABLE IF NOT EXISTS `" + this.prefix + "plot` ("
1199                        + "`id` INT(11) NOT NULL AUTO_INCREMENT," + "`plot_id_x` INT(11) NOT NULL,"
1200                        + "`plot_id_z` INT(11) NOT NULL," + "`owner` VARCHAR(40) NOT NULL,"
1201                        + "`world` VARCHAR(45) NOT NULL,"
1202                        + "`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,"
1203                        + "PRIMARY KEY (`id`)"
1204                        + ") ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=0");
1205                stmt.addBatch("CREATE TABLE IF NOT EXISTS `" + this.prefix
1206                        + "plot_denied` (`plot_plot_id` INT(11) NOT NULL,"
1207                        + "`user_uuid` VARCHAR(40) NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8");
1208                stmt.addBatch("CREATE TABLE IF NOT EXISTS `" + this.prefix + "plot_helpers` ("
1209                        + "`plot_plot_id` INT(11) NOT NULL," + "`user_uuid` VARCHAR(40) NOT NULL"
1210                        + ") ENGINE=InnoDB DEFAULT CHARSET=utf8");
1211                stmt.addBatch("CREATE TABLE IF NOT EXISTS `" + this.prefix + "plot_comments` ("
1212                        + "`world` VARCHAR(40) NOT NULL, `hashcode` INT(11) NOT NULL,"
1213                        + "`comment` VARCHAR(40) NOT NULL," + "`inbox` VARCHAR(40) NOT NULL,"
1214                        + "`timestamp` INT(11) NOT NULL," + "`sender` VARCHAR(40) NOT NULL"
1215                        + ") ENGINE=InnoDB DEFAULT CHARSET=utf8");
1216                stmt.addBatch("CREATE TABLE IF NOT EXISTS `" + this.prefix + "plot_trusted` ("
1217                        + "`plot_plot_id` INT(11) NOT NULL," + "`user_uuid` VARCHAR(40) NOT NULL"
1218                        + ") ENGINE=InnoDB DEFAULT CHARSET=utf8");
1219                stmt.addBatch("CREATE TABLE IF NOT EXISTS `" + this.prefix + "plot_settings` ("
1220                        + "  `plot_plot_id` INT(11) NOT NULL,"
1221                        + "  `biome` VARCHAR(45) DEFAULT 'FOREST'," + "  `rain` INT(1) DEFAULT 0,"
1222                        + "  `custom_time` TINYINT(1) DEFAULT '0'," + "  `time` INT(11) DEFAULT '8000',"
1223                        + "  `deny_entry` TINYINT(1) DEFAULT '0',"
1224                        + "  `alias` VARCHAR(50) DEFAULT NULL," + "  `merged` INT(11) DEFAULT NULL,"
1225                        + "  `position` VARCHAR(50) NOT NULL DEFAULT 'DEFAULT',"
1226                        + "  PRIMARY KEY (`plot_plot_id`)" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8");
1227                stmt.addBatch("CREATE TABLE IF NOT EXISTS `" + this.prefix
1228                        + "plot_rating` ( `plot_plot_id` INT(11) NOT NULL, `rating` INT(2) NOT NULL, `player` VARCHAR(40) NOT NULL) ENGINE=InnoDB "
1229                        + "DEFAULT CHARSET=utf8");
1230                if (addConstraint) {
1231                    stmt.addBatch("ALTER TABLE `" + this.prefix + "plot_settings` ADD CONSTRAINT `"
1232                            + this.prefix
1233                            + "plot_settings_ibfk_1` FOREIGN KEY (`plot_plot_id`) REFERENCES `"
1234                            + this.prefix + "plot` (`id`) ON DELETE CASCADE");
1235                }
1236                stmt.addBatch("CREATE TABLE IF NOT EXISTS `" + this.prefix + "cluster` ("
1237                        + "`id` INT(11) NOT NULL AUTO_INCREMENT," + "`pos1_x` INT(11) NOT NULL,"
1238                        + "`pos1_z` INT(11) NOT NULL," + "`pos2_x` INT(11) NOT NULL,"
1239                        + "`pos2_z` INT(11) NOT NULL," + "`owner` VARCHAR(40) NOT NULL,"
1240                        + "`world` VARCHAR(45) NOT NULL,"
1241                        + "`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,"
1242                        + "PRIMARY KEY (`id`)"
1243                        + ") ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=0");
1244                stmt.addBatch("CREATE TABLE IF NOT EXISTS `" + this.prefix + "cluster_helpers` ("
1245                        + "`cluster_id` INT(11) NOT NULL," + "`user_uuid` VARCHAR(40) NOT NULL"
1246                        + ") ENGINE=InnoDB DEFAULT CHARSET=utf8");
1247                stmt.addBatch("CREATE TABLE IF NOT EXISTS `" + this.prefix + "cluster_invited` ("
1248                        + "`cluster_id` INT(11) NOT NULL," + "`user_uuid` VARCHAR(40) NOT NULL"
1249                        + ") ENGINE=InnoDB DEFAULT CHARSET=utf8");
1250                stmt.addBatch("CREATE TABLE IF NOT EXISTS `" + this.prefix + "cluster_settings` ("
1251                        + "  `cluster_id` INT(11) NOT NULL," + "  `biome` VARCHAR(45) DEFAULT 'FOREST',"
1252                        + "  `rain` INT(1) DEFAULT 0," + "  `custom_time` TINYINT(1) DEFAULT '0',"
1253                        + "  `time` INT(11) DEFAULT '8000'," + "  `deny_entry` TINYINT(1) DEFAULT '0',"
1254                        + "  `alias` VARCHAR(50) DEFAULT NULL," + "  `merged` INT(11) DEFAULT NULL,"
1255                        + "  `position` VARCHAR(50) NOT NULL DEFAULT 'DEFAULT',"
1256                        + "  PRIMARY KEY (`cluster_id`)" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8");
1257                stmt.addBatch("CREATE TABLE IF NOT EXISTS `" + this.prefix + "player_meta` ("
1258                        + " `meta_id` INT(11) NOT NULL AUTO_INCREMENT,"
1259                        + " `uuid` VARCHAR(40) NOT NULL," + " `key` VARCHAR(32) NOT NULL,"
1260                        + " `value` blob NOT NULL," + " PRIMARY KEY (`meta_id`)"
1261                        + ") ENGINE=InnoDB DEFAULT CHARSET=utf8");
1262                stmt.addBatch("CREATE TABLE IF NOT EXISTS `" + this.prefix + "plot_flags`("
1263                        + "`id` INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,"
1264                        + "`plot_id` INT(11) NOT NULL," + " `flag` VARCHAR(64),"
1265                        + " `value` VARCHAR(512)," + "FOREIGN KEY (plot_id) REFERENCES `" + this.prefix
1266                        + "plot` (id) ON DELETE CASCADE, " + "UNIQUE (plot_id, flag)"
1267                        + ") ENGINE=InnoDB DEFAULT CHARSET=utf8");
1268            } else {
1269                stmt.addBatch("CREATE TABLE IF NOT EXISTS `" + this.prefix + "plot` ("
1270                        + "`id` INTEGER PRIMARY KEY AUTOINCREMENT," + "`plot_id_x` INT(11) NOT NULL,"
1271                        + "`plot_id_z` INT(11) NOT NULL," + "`owner` VARCHAR(45) NOT NULL,"
1272                        + "`world` VARCHAR(45) NOT NULL,"
1273                        + "`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP)");
1274                stmt.addBatch("CREATE TABLE IF NOT EXISTS `" + this.prefix
1275                        + "plot_denied` (`plot_plot_id` INT(11) NOT NULL,"
1276                        + "`user_uuid` VARCHAR(40) NOT NULL)");
1277                stmt.addBatch("CREATE TABLE IF NOT EXISTS `" + this.prefix
1278                        + "plot_helpers` (`plot_plot_id` INT(11) NOT NULL,"
1279                        + "`user_uuid` VARCHAR(40) NOT NULL)");
1280                stmt.addBatch("CREATE TABLE IF NOT EXISTS `" + this.prefix
1281                        + "plot_trusted` (`plot_plot_id` INT(11) NOT NULL,"
1282                        + "`user_uuid` VARCHAR(40) NOT NULL)");
1283                stmt.addBatch("CREATE TABLE IF NOT EXISTS `" + this.prefix + "plot_comments` ("
1284                        + "`world` VARCHAR(40) NOT NULL, `hashcode` INT(11) NOT NULL,"
1285                        + "`comment` VARCHAR(40) NOT NULL,"
1286                        + "`inbox` VARCHAR(40) NOT NULL, `timestamp` INT(11) NOT NULL,"
1287                        + "`sender` VARCHAR(40) NOT NULL" + ')');
1288                stmt.addBatch("CREATE TABLE IF NOT EXISTS `" + this.prefix + "plot_settings` ("
1289                        + "  `plot_plot_id` INT(11) NOT NULL,"
1290                        + "  `biome` VARCHAR(45) DEFAULT 'FOREST'," + "  `rain` INT(1) DEFAULT 0,"
1291                        + "  `custom_time` TINYINT(1) DEFAULT '0'," + "  `time` INT(11) DEFAULT '8000',"
1292                        + "  `deny_entry` TINYINT(1) DEFAULT '0',"
1293                        + "  `alias` VARCHAR(50) DEFAULT NULL," + "  `merged` INT(11) DEFAULT NULL,"
1294                        + "  `position` VARCHAR(50) NOT NULL DEFAULT 'DEFAULT',"
1295                        + "  PRIMARY KEY (`plot_plot_id`)" + ')');
1296                stmt.addBatch("CREATE TABLE IF NOT EXISTS `" + this.prefix
1297                        + "plot_rating` (`plot_plot_id` INT(11) NOT NULL, `rating` INT(2) NOT NULL, `player` VARCHAR(40) NOT NULL)");
1298                stmt.addBatch("CREATE TABLE IF NOT EXISTS `" + this.prefix + "cluster` ("
1299                        + "`id` INTEGER PRIMARY KEY AUTOINCREMENT," + "`pos1_x` INT(11) NOT NULL,"
1300                        + "`pos1_z` INT(11) NOT NULL," + "`pos2_x` INT(11) NOT NULL,"
1301                        + "`pos2_z` INT(11) NOT NULL," + "`owner` VARCHAR(40) NOT NULL,"
1302                        + "`world` VARCHAR(45) NOT NULL,"
1303                        + "`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP" + ')');
1304                stmt.addBatch("CREATE TABLE IF NOT EXISTS `" + this.prefix
1305                        + "cluster_helpers` (`cluster_id` INT(11) NOT NULL,"
1306                        + "`user_uuid` VARCHAR(40) NOT NULL)");
1307                stmt.addBatch("CREATE TABLE IF NOT EXISTS `" + this.prefix
1308                        + "cluster_invited` (`cluster_id` INT(11) NOT NULL,"
1309                        + "`user_uuid` VARCHAR(40) NOT NULL)");
1310                stmt.addBatch("CREATE TABLE IF NOT EXISTS `" + this.prefix + "cluster_settings` ("
1311                        + "  `cluster_id` INT(11) NOT NULL," + "  `biome` VARCHAR(45) DEFAULT 'FOREST',"
1312                        + "  `rain` INT(1) DEFAULT 0," + "  `custom_time` TINYINT(1) DEFAULT '0',"
1313                        + "  `time` INT(11) DEFAULT '8000'," + "  `deny_entry` TINYINT(1) DEFAULT '0',"
1314                        + "  `alias` VARCHAR(50) DEFAULT NULL," + "  `merged` INT(11) DEFAULT NULL,"
1315                        + "  `position` VARCHAR(50) NOT NULL DEFAULT 'DEFAULT',"
1316                        + "  PRIMARY KEY (`cluster_id`)" + ')');
1317                stmt.addBatch("CREATE TABLE IF NOT EXISTS `" + this.prefix + "player_meta` ("
1318                        + " `meta_id` INTEGER PRIMARY KEY AUTOINCREMENT,"
1319                        + " `uuid` VARCHAR(40) NOT NULL," + " `key` VARCHAR(32) NOT NULL,"
1320                        + " `value` blob NOT NULL" + ')');
1321                stmt.addBatch("CREATE TABLE IF NOT EXISTS `" + this.prefix + "plot_flags`("
1322                        + "`id` INTEGER PRIMARY KEY AUTOINCREMENT," + "`plot_id` INTEGER NOT NULL,"
1323                        + " `flag` VARCHAR(64)," + " `value` VARCHAR(512),"
1324                        + "FOREIGN KEY (plot_id) REFERENCES `" + this.prefix
1325                        + "plot` (id) ON DELETE CASCADE, " + "UNIQUE (plot_id, flag))");
1326            }
1327            stmt.executeBatch();
1328            stmt.clearBatch();
1329        }
1330    }
1331
1332    @Override
1333    public void deleteSettings(final Plot plot) {
1334        addPlotTask(plot, new UniqueStatement("delete_plot_settings") {
1335            @Override
1336            public void set(PreparedStatement statement) throws SQLException {
1337                statement.setInt(1, getId(plot));
1338            }
1339
1340            @Override
1341            public PreparedStatement get() throws SQLException {
1342                return SQLManager.this.connection.prepareStatement(
1343                        "DELETE FROM `" + SQLManager.this.prefix
1344                                + "plot_settings` WHERE `plot_plot_id` = ?");
1345            }
1346        });
1347    }
1348
1349    @Override
1350    public void deleteHelpers(final Plot plot) {
1351        if (plot.getTrusted().isEmpty()) {
1352            return;
1353        }
1354        addPlotTask(plot, new UniqueStatement("delete_plot_helpers") {
1355            @Override
1356            public void set(PreparedStatement statement) throws SQLException {
1357                statement.setInt(1, getId(plot));
1358            }
1359
1360            @Override
1361            public PreparedStatement get() throws SQLException {
1362                return SQLManager.this.connection.prepareStatement(
1363                        "DELETE FROM `" + SQLManager.this.prefix
1364                                + "plot_helpers` WHERE `plot_plot_id` = ?");
1365            }
1366        });
1367    }
1368
1369    @Override
1370    public void deleteTrusted(final Plot plot) {
1371        if (plot.getMembers().isEmpty()) {
1372            return;
1373        }
1374        addPlotTask(plot, new UniqueStatement("delete_plot_trusted") {
1375            @Override
1376            public void set(PreparedStatement statement) throws SQLException {
1377                statement.setInt(1, getId(plot));
1378            }
1379
1380            @Override
1381            public PreparedStatement get() throws SQLException {
1382                return SQLManager.this.connection.prepareStatement(
1383                        "DELETE FROM `" + SQLManager.this.prefix
1384                                + "plot_trusted` WHERE `plot_plot_id` = ?");
1385            }
1386        });
1387    }
1388
1389    @Override
1390    public void deleteDenied(final Plot plot) {
1391        if (plot.getDenied().isEmpty()) {
1392            return;
1393        }
1394        addPlotTask(plot, new UniqueStatement("delete_plot_denied") {
1395            @Override
1396            public void set(PreparedStatement statement) throws SQLException {
1397                statement.setInt(1, getId(plot));
1398            }
1399
1400            @Override
1401            public PreparedStatement get() throws SQLException {
1402                return SQLManager.this.connection.prepareStatement(
1403                        "DELETE FROM `" + SQLManager.this.prefix
1404                                + "plot_denied` WHERE `plot_plot_id` = ?");
1405            }
1406        });
1407    }
1408
1409    @Override
1410    public void deleteComments(final Plot plot) {
1411        addPlotTask(plot, new UniqueStatement("delete_plot_comments") {
1412            @Override
1413            public void set(PreparedStatement statement) throws SQLException {
1414                statement.setString(1, plot.getArea().toString());
1415                statement.setInt(2, plot.hashCode());
1416            }
1417
1418            @Override
1419            public PreparedStatement get() throws SQLException {
1420                return SQLManager.this.connection.prepareStatement(
1421                        "DELETE FROM `" + SQLManager.this.prefix
1422                                + "plot_comments` WHERE `world` = ? AND `hashcode` = ?");
1423            }
1424        });
1425    }
1426
1427    @Override
1428    public void deleteRatings(final Plot plot) {
1429        if (Settings.Enabled_Components.RATING_CACHE && plot.getSettings().getRatings().isEmpty()) {
1430            return;
1431        }
1432        addPlotTask(plot, new UniqueStatement("delete_plot_ratings") {
1433            @Override
1434            public void set(PreparedStatement statement) throws SQLException {
1435                statement.setInt(1, getId(plot));
1436            }
1437
1438            @Override
1439            public PreparedStatement get() throws SQLException {
1440                return SQLManager.this.connection.prepareStatement(
1441                        "DELETE FROM `" + SQLManager.this.prefix
1442                                + "plot_rating` WHERE `plot_plot_id` = ?");
1443            }
1444        });
1445    }
1446
1447    /**
1448     * Delete a plot.
1449     *
1450     * @param plot
1451     */
1452    @Override
1453    public void delete(final Plot plot) {
1454        deleteSettings(plot);
1455        deleteDenied(plot);
1456        deleteHelpers(plot);
1457        deleteTrusted(plot);
1458        deleteComments(plot);
1459        deleteRatings(plot);
1460        addPlotTask(plot, new UniqueStatement("delete_plot") {
1461            @Override
1462            public void set(PreparedStatement statement) throws SQLException {
1463                statement.setInt(1, getId(plot));
1464            }
1465
1466            @Override
1467            public PreparedStatement get() throws SQLException {
1468                return SQLManager.this.connection.prepareStatement(
1469                        "DELETE FROM `" + SQLManager.this.prefix + "plot` WHERE `id` = ?");
1470            }
1471        });
1472    }
1473
1474    /**
1475     * Creates plot settings
1476     *
1477     * @param id
1478     * @param plot
1479     */
1480    @Override
1481    public void createPlotSettings(final int id, Plot plot) {
1482        addPlotTask(plot, new UniqueStatement("createPlotSettings") {
1483            @Override
1484            public void set(PreparedStatement statement) throws SQLException {
1485                statement.setInt(1, id);
1486            }
1487
1488            @Override
1489            public PreparedStatement get() throws SQLException {
1490                return SQLManager.this.connection.prepareStatement(
1491                        "INSERT INTO `" + SQLManager.this.prefix
1492                                + "plot_settings`(`plot_plot_id`) VALUES(?)");
1493            }
1494        });
1495    }
1496
1497    @Override
1498    public int getClusterId(PlotCluster cluster) {
1499        if (cluster.temp > 0) {
1500            return cluster.temp;
1501        }
1502        try {
1503            commit();
1504            if (cluster.temp > 0) {
1505                return cluster.temp;
1506            }
1507            int c_id;
1508            try (PreparedStatement stmt = this.connection.prepareStatement(
1509                    "SELECT `id` FROM `" + this.prefix
1510                            + "cluster` WHERE `pos1_x` = ? AND `pos1_z` = ? AND `pos2_x` = ? AND `pos2_z` = ? AND `world` = ? ORDER BY `timestamp` ASC")) {
1511                stmt.setInt(1, cluster.getP1().getX());
1512                stmt.setInt(2, cluster.getP1().getY());
1513                stmt.setInt(3, cluster.getP2().getX());
1514                stmt.setInt(4, cluster.getP2().getY());
1515                stmt.setString(5, cluster.area.toString());
1516                try (ResultSet resultSet = stmt.executeQuery()) {
1517                    c_id = Integer.MAX_VALUE;
1518                    while (resultSet.next()) {
1519                        c_id = resultSet.getInt("id");
1520                    }
1521                }
1522            }
1523            if (c_id == Integer.MAX_VALUE || c_id == 0) {
1524                if (cluster.temp > 0) {
1525                    return cluster.temp;
1526                }
1527                throw new SQLException("Cluster does not exist in database");
1528            }
1529            cluster.temp = c_id;
1530            return c_id;
1531        } catch (SQLException e) {
1532            e.printStackTrace();
1533        }
1534        return Integer.MAX_VALUE;
1535    }
1536
1537    @Override
1538    public int getId(Plot plot) {
1539        if (plot.temp > 0) {
1540            return plot.temp;
1541        }
1542        try {
1543            commit();
1544            if (plot.temp > 0) {
1545                return plot.temp;
1546            }
1547            int id;
1548            try (PreparedStatement statement = this.connection.prepareStatement(
1549                    "SELECT `id` FROM `" + this.prefix
1550                            + "plot` WHERE `plot_id_x` = ? AND `plot_id_z` = ? AND world = ? ORDER BY `timestamp` ASC")) {
1551                statement.setInt(1, plot.getId().getX());
1552                statement.setInt(2, plot.getId().getY());
1553                statement.setString(3, plot.getArea().toString());
1554                try (ResultSet resultSet = statement.executeQuery()) {
1555                    id = Integer.MAX_VALUE;
1556                    while (resultSet.next()) {
1557                        id = resultSet.getInt("id");
1558                    }
1559                }
1560            }
1561            if (id == Integer.MAX_VALUE || id == 0) {
1562                if (plot.temp > 0) {
1563                    return plot.temp;
1564                }
1565                throw new SQLException("Plot does not exist in database");
1566            }
1567            plot.temp = id;
1568            return id;
1569        } catch (SQLException e) {
1570            e.printStackTrace();
1571        }
1572        return Integer.MAX_VALUE;
1573    }
1574
1575    @Override
1576    public void updateTables(int[] oldVersion) {
1577        try {
1578            if (this.mySQL && !PlotSquared.get().checkVersion(oldVersion, 3, 3, 2)) {
1579                try (Statement stmt = this.connection.createStatement()) {
1580                    stmt.executeUpdate(
1581                            "ALTER TABLE `" + this.prefix + "plots` DROP INDEX `unique_alias`");
1582                } catch (SQLException ignored) {
1583                }
1584            }
1585            DatabaseMetaData data = this.connection.getMetaData();
1586            ResultSet rs =
1587                    data.getColumns(null, null, this.prefix + "plot_comments", "plot_plot_id");
1588            if (rs.next()) {
1589                rs.close();
1590                rs = data.getColumns(null, null, this.prefix + "plot_comments", "hashcode");
1591                if (!rs.next()) {
1592                    rs.close();
1593                    try (Statement statement = this.connection.createStatement()) {
1594                        statement.addBatch("DROP TABLE `" + this.prefix + "plot_comments`");
1595                        if (Storage.MySQL.USE) {
1596                            statement.addBatch(
1597                                    "CREATE TABLE IF NOT EXISTS `" + this.prefix + "plot_comments` ("
1598                                            + "`world` VARCHAR(40) NOT NULL, `hashcode` INT(11) NOT NULL,"
1599                                            + "`comment` VARCHAR(40) NOT NULL,"
1600                                            + "`inbox` VARCHAR(40) NOT NULL,"
1601                                            + "`timestamp` INT(11) NOT NULL,"
1602                                            + "`sender` VARCHAR(40) NOT NULL"
1603                                            + ") ENGINE=InnoDB DEFAULT CHARSET=utf8");
1604                        } else {
1605                            statement.addBatch(
1606                                    "CREATE TABLE IF NOT EXISTS `" + this.prefix + "plot_comments` ("
1607                                            + "`world` VARCHAR(40) NOT NULL, `hashcode` INT(11) NOT NULL,"
1608                                            + "`comment` VARCHAR(40) NOT NULL,"
1609                                            + "`inbox` VARCHAR(40) NOT NULL, `timestamp` INT(11) NOT NULL,"
1610                                            + "`sender` VARCHAR(40) NOT NULL" + ')');
1611                        }
1612                        statement.executeBatch();
1613                    } catch (SQLException ignored) {
1614                        try (Statement statement = this.connection.createStatement()) {
1615                            statement.addBatch("ALTER IGNORE TABLE `" + this.prefix
1616                                    + "plot_comments` ADD `inbox` VARCHAR(11) DEFAULT `public`");
1617                            statement.addBatch("ALTER IGNORE TABLE `" + this.prefix
1618                                    + "plot_comments` ADD `timestamp` INT(11) DEFAULT 0");
1619                            statement.addBatch("ALTER TABLE `" + this.prefix + "plot` DROP `tier`");
1620                            statement.executeBatch();
1621                        }
1622                    }
1623                }
1624            }
1625            rs.close();
1626            rs = data.getColumns(null, null, this.prefix + "plot_denied", "plot_plot_id");
1627            if (rs.next()) {
1628                try (Statement statement = this.connection.createStatement()) {
1629                    statement.executeUpdate("DELETE FROM `" + this.prefix
1630                            + "plot_denied` WHERE `plot_plot_id` NOT IN (SELECT `id` FROM `"
1631                            + this.prefix + "plot`)");
1632                } catch (SQLException e) {
1633                    e.printStackTrace();
1634                }
1635
1636                rs.close();
1637                try (Statement statement = this.connection.createStatement()) {
1638                    for (String table : new String[]{"plot_denied", "plot_helpers",
1639                            "plot_trusted"}) {
1640                        ResultSet result = statement.executeQuery(
1641                                "SELECT plot_plot_id, user_uuid, COUNT(*) FROM " + this.prefix + table
1642                                        + " GROUP BY plot_plot_id, user_uuid HAVING COUNT(*) > 1");
1643                        if (result.next()) {
1644                            result.close();
1645                            statement.executeUpdate(
1646                                    "CREATE TABLE " + this.prefix + table + "_tmp AS SELECT * FROM "
1647                                            + this.prefix + table + " GROUP BY plot_plot_id, user_uuid");
1648                            statement.executeUpdate("DROP TABLE " + this.prefix + table);
1649                            statement.executeUpdate(
1650                                    "CREATE TABLE " + this.prefix + table + " AS SELECT * FROM "
1651                                            + this.prefix + table + "_tmp");
1652                            statement.executeUpdate("DROP TABLE " + this.prefix + table + "_tmp");
1653                        }
1654                    }
1655                } catch (SQLException e2) {
1656                    e2.printStackTrace();
1657                }
1658            }
1659        } catch (SQLException e) {
1660            e.printStackTrace();
1661        }
1662
1663    }
1664
1665    public void deleteRows(ArrayList<Integer> rowIds, final String table, final String column) {
1666        setBulk(rowIds, new StmtMod<>() {
1667
1668            @Override
1669            public String getCreateMySQL(int size) {
1670                return getCreateMySQL(1, "DELETE FROM `" + table + "` WHERE `" + column + "` IN ",
1671                        size
1672                );
1673            }
1674
1675            @Override
1676            public String getCreateSQLite(int size) {
1677                return getCreateMySQL(1, "DELETE FROM `" + table + "` WHERE `" + column + "` IN ",
1678                        size
1679                );
1680            }
1681
1682            @Override
1683            public String getCreateSQL() {
1684                return "DELETE FROM `" + table + "` WHERE `" + column + "` = ?";
1685            }
1686
1687            @Override
1688            public void setMySQL(PreparedStatement stmt, int i, Integer obj)
1689                    throws SQLException {
1690                stmt.setInt(i + 1, obj);
1691            }
1692
1693            @Override
1694            public void setSQLite(PreparedStatement stmt, int i, Integer obj)
1695                    throws SQLException {
1696                stmt.setInt(i + 1, obj);
1697            }
1698
1699            @Override
1700            public void setSQL(PreparedStatement stmt, Integer obj) throws SQLException {
1701                stmt.setInt(1, obj);
1702            }
1703        }, null);
1704    }
1705
1706    @Override
1707    public boolean convertFlags() {
1708        final Map<Integer, Map<String, String>> flagMap = new HashMap<>();
1709        try (Statement statement = this.connection.createStatement()) {
1710            try (ResultSet resultSet = statement
1711                    .executeQuery("SELECT * FROM `" + this.prefix + "plot_settings`")) {
1712                while (resultSet.next()) {
1713                    final int id = resultSet.getInt("plot_plot_id");
1714                    final String plotFlags = resultSet.getString("flags");
1715                    if (plotFlags == null || plotFlags.isEmpty()) {
1716                        continue;
1717                    }
1718                    flagMap.put(id, new HashMap<>());
1719                    for (String element : plotFlags.split(",")) {
1720                        if (element.contains(":")) {
1721                            String[] split = element.split(":"); // splits flag:value
1722                            try {
1723                                String flag_str =
1724                                        split[1].replaceAll("¯", ":").replaceAll("\u00B4", ",");
1725                                flagMap.get(id).put(split[0], flag_str);
1726                            } catch (Exception e) {
1727                                e.printStackTrace();
1728                            }
1729                        }
1730                    }
1731                }
1732            }
1733        } catch (final Exception e) {
1734            LOGGER.error("Failed to load old flag values", e);
1735            return false;
1736        }
1737        LOGGER.info("Loaded {} plot flag collections...", flagMap.size());
1738        LOGGER.info("Attempting to store these flags in the new table...");
1739        try (final PreparedStatement preparedStatement = this.connection.prepareStatement(
1740                "INSERT INTO `" + SQLManager.this.prefix
1741                        + "plot_flags`(`plot_id`, `flag`, `value`) VALUES(?, ?, ?)")) {
1742
1743            long timeStarted = System.currentTimeMillis();
1744            int flagsProcessed = 0;
1745            int plotsProcessed = 0;
1746
1747            int totalFlags = 0;
1748            for (final Map<String, String> flags : flagMap.values()) {
1749                totalFlags += flags.size();
1750            }
1751
1752            for (final Map.Entry<Integer, Map<String, String>> plotFlagEntry : flagMap.entrySet()) {
1753                for (final Map.Entry<String, String> flagEntry : plotFlagEntry.getValue()
1754                        .entrySet()) {
1755                    preparedStatement.setInt(1, plotFlagEntry.getKey());
1756                    preparedStatement.setString(2, flagEntry.getKey());
1757                    preparedStatement.setString(3, flagEntry.getValue());
1758                    preparedStatement.addBatch();
1759                    flagsProcessed += 1;
1760                }
1761                plotsProcessed += 1;
1762
1763                try {
1764                    preparedStatement.executeBatch();
1765                } catch (final Exception e) {
1766                    LOGGER.error("Failed to store flag values for plot with entry ID: {}", plotFlagEntry.getKey());
1767                    e.printStackTrace();
1768                    continue;
1769                }
1770
1771                if (System.currentTimeMillis() - timeStarted >= 1000L || plotsProcessed >= flagMap
1772                        .size()) {
1773                    timeStarted = System.currentTimeMillis();
1774                    LOGGER.info(
1775                            "... Flag conversion in progress. {}% done",
1776                            String.format("%.1f", ((float) flagsProcessed / totalFlags) * 100)
1777                    );
1778                }
1779                LOGGER.info(
1780                        "- Finished converting flags for plot with entry ID: {}",
1781                        plotFlagEntry.getKey()
1782                );
1783            }
1784        } catch (final Exception e) {
1785            LOGGER.error("Failed to store flag values", e);
1786            return false;
1787        }
1788        return true;
1789    }
1790
1791    /**
1792     * Load all plots, helpers, denied, trusted, and every setting from DB into a {@link HashMap}.
1793     */
1794    @Override
1795    public HashMap<String, HashMap<PlotId, Plot>> getPlots() {
1796        HashMap<String, HashMap<PlotId, Plot>> newPlots = new HashMap<>();
1797        HashMap<Integer, Plot> plots = new HashMap<>();
1798        try {
1799            HashSet<String> areas = new HashSet<>();
1800            if (this.worldConfiguration.contains("worlds")) {
1801                ConfigurationSection worldSection = this.worldConfiguration.getConfigurationSection("worlds");
1802                if (worldSection != null) {
1803                    for (String worldKey : worldSection.getKeys(false)) {
1804                        areas.add(worldKey);
1805                        ConfigurationSection areaSection =
1806                                worldSection.getConfigurationSection(worldKey + ".areas");
1807                        if (areaSection != null) {
1808                            for (String areaKey : areaSection.getKeys(false)) {
1809                                String[] split = areaKey.split("(?<![;])-");
1810                                if (split.length == 3) {
1811                                    areas.add(worldKey + ';' + split[0]);
1812                                }
1813                            }
1814                        }
1815                    }
1816                }
1817            }
1818            HashMap<String, UUID> uuids = new HashMap<>();
1819            HashMap<String, AtomicInteger> noExist = new HashMap<>();
1820
1821            /*
1822             * Getting plots
1823             */
1824            try (Statement statement = this.connection.createStatement()) {
1825                int id;
1826                String o;
1827                UUID user;
1828                try (ResultSet resultSet = statement.executeQuery(
1829                        "SELECT `id`, `plot_id_x`, `plot_id_z`, `owner`, `world`, `timestamp` FROM `"
1830                                + this.prefix + "plot`")) {
1831                    ArrayList<Integer> toDelete = new ArrayList<>();
1832                    while (resultSet.next()) {
1833                        PlotId plot_id = PlotId.of(
1834                                resultSet.getInt("plot_id_x"),
1835                                resultSet.getInt("plot_id_z")
1836                        );
1837                        id = resultSet.getInt("id");
1838                        String areaID = resultSet.getString("world");
1839                        if (!areas.contains(areaID)) {
1840                            if (Settings.Enabled_Components.DATABASE_PURGER) {
1841                                toDelete.add(id);
1842                                continue;
1843                            } else {
1844                                AtomicInteger value = noExist.get(areaID);
1845                                if (value != null) {
1846                                    value.incrementAndGet();
1847                                } else {
1848                                    noExist.put(areaID, new AtomicInteger(1));
1849                                }
1850                            }
1851                        }
1852                        o = resultSet.getString("owner");
1853                        user = uuids.get(o);
1854                        if (user == null) {
1855                            try {
1856                                user = UUID.fromString(o);
1857                            } catch (IllegalArgumentException e) {
1858                                if (Settings.UUID.FORCE_LOWERCASE) {
1859                                    user = UUID.nameUUIDFromBytes(
1860                                            ("OfflinePlayer:" + o.toLowerCase())
1861                                                    .getBytes(Charsets.UTF_8));
1862                                } else {
1863                                    user = UUID.nameUUIDFromBytes(
1864                                            ("OfflinePlayer:" + o).getBytes(Charsets.UTF_8));
1865                                }
1866                            }
1867                            uuids.put(o, user);
1868                        }
1869                        long time;
1870                        try {
1871                            Timestamp timestamp = resultSet.getTimestamp("timestamp");
1872                            time = timestamp.getTime();
1873                        } catch (SQLException exception) {
1874                            String parsable = resultSet.getString("timestamp");
1875                            try {
1876                                time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(parsable)
1877                                        .getTime();
1878                            } catch (ParseException e) {
1879                                LOGGER.error("Could not parse date for plot: #{}({};{}) ({})",
1880                                        id, areaID, plot_id, parsable
1881                                );
1882                                time = System.currentTimeMillis() + id;
1883                            }
1884                        }
1885                        Plot p = new Plot(plot_id, user, new HashSet<>(), new HashSet<>(),
1886                                new HashSet<>(), "", null, null, null,
1887                                new boolean[]{false, false, false, false}, time, id
1888                        );
1889                        HashMap<PlotId, Plot> map = newPlots.get(areaID);
1890                        if (map != null) {
1891                            Plot last = map.put(p.getId(), p);
1892                            if (last != null) {
1893                                if (Settings.Enabled_Components.DATABASE_PURGER) {
1894                                    toDelete.add(last.temp);
1895                                } else {
1896                                    LOGGER.info(
1897                                            "Plot #{}({}) in `{}plot` is a duplicate."
1898                                                    + " Delete this plot or set `database-purger: true` in the settings.yml",
1899                                            id,
1900                                            last,
1901                                            this.prefix
1902                                    );
1903                                }
1904                            }
1905                        } else {
1906                            map = new HashMap<>();
1907                            newPlots.put(areaID, map);
1908                            map.put(p.getId(), p);
1909                        }
1910                        plots.put(id, p);
1911                    }
1912                    deleteRows(toDelete, this.prefix + "plot", "id");
1913                }
1914                if (Settings.Enabled_Components.RATING_CACHE) {
1915                    try (ResultSet r = statement.executeQuery(
1916                            "SELECT `plot_plot_id`, `player`, `rating` FROM `" + this.prefix
1917                                    + "plot_rating`")) {
1918                        ArrayList<Integer> toDelete = new ArrayList<>();
1919                        while (r.next()) {
1920                            id = r.getInt("plot_plot_id");
1921                            o = r.getString("player");
1922                            user = uuids.get(o);
1923                            if (user == null) {
1924                                user = UUID.fromString(o);
1925                                uuids.put(o, user);
1926                            }
1927                            Plot plot = plots.get(id);
1928                            if (plot != null) {
1929                                plot.getSettings().getRatings().put(user, r.getInt("rating"));
1930                            } else if (Settings.Enabled_Components.DATABASE_PURGER) {
1931                                toDelete.add(id);
1932                            } else {
1933                                LOGGER.warn("Entry #{}({}) in `plot_rating` does not exist."
1934                                        + " Create this plot or set `database-purger: true` in settings.yml", id, plot);
1935                            }
1936                        }
1937                        deleteRows(toDelete, this.prefix + "plot_rating", "plot_plot_id");
1938                    }
1939                }
1940
1941                /*
1942                 * Getting helpers
1943                 */
1944                try (ResultSet r = statement.executeQuery(
1945                        "SELECT `user_uuid`, `plot_plot_id` FROM `" + this.prefix + "plot_helpers`")) {
1946                    ArrayList<Integer> toDelete = new ArrayList<>();
1947                    while (r.next()) {
1948                        id = r.getInt("plot_plot_id");
1949                        o = r.getString("user_uuid");
1950                        user = uuids.get(o);
1951                        if (user == null) {
1952                            user = UUID.fromString(o);
1953                            uuids.put(o, user);
1954                        }
1955                        Plot plot = plots.get(id);
1956                        if (plot != null) {
1957                            plot.getTrusted().add(user);
1958                        } else if (Settings.Enabled_Components.DATABASE_PURGER) {
1959                            toDelete.add(id);
1960                        } else {
1961                            LOGGER.warn("Entry #{}({}) in `plot_helpers` does not exist."
1962                                    + " Create this plot or set `database-purger: true` in settings.yml", id, plot);
1963                        }
1964                    }
1965                    deleteRows(toDelete, this.prefix + "plot_helpers", "plot_plot_id");
1966                }
1967
1968                /*
1969                 * Getting trusted
1970                 */
1971                try (ResultSet r = statement.executeQuery(
1972                        "SELECT `user_uuid`, `plot_plot_id` FROM `" + this.prefix + "plot_trusted`")) {
1973                    ArrayList<Integer> toDelete = new ArrayList<>();
1974                    while (r.next()) {
1975                        id = r.getInt("plot_plot_id");
1976                        o = r.getString("user_uuid");
1977                        user = uuids.get(o);
1978                        if (user == null) {
1979                            user = UUID.fromString(o);
1980                            uuids.put(o, user);
1981                        }
1982                        Plot plot = plots.get(id);
1983                        if (plot != null) {
1984                            plot.getMembers().add(user);
1985                        } else if (Settings.Enabled_Components.DATABASE_PURGER) {
1986                            toDelete.add(id);
1987                        } else {
1988                            LOGGER.warn("Entry #{}({}) in `plot_trusted` does not exist."
1989                                    + " Create this plot or set `database-purger: true` in settings.yml", id, plot);
1990                        }
1991                    }
1992                    deleteRows(toDelete, this.prefix + "plot_trusted", "plot_plot_id");
1993                }
1994
1995                /*
1996                 * Getting denied
1997                 */
1998                try (ResultSet r = statement.executeQuery(
1999                        "SELECT `user_uuid`, `plot_plot_id` FROM `" + this.prefix + "plot_denied`")) {
2000                    ArrayList<Integer> toDelete = new ArrayList<>();
2001                    while (r.next()) {
2002                        id = r.getInt("plot_plot_id");
2003                        o = r.getString("user_uuid");
2004                        user = uuids.get(o);
2005                        if (user == null) {
2006                            user = UUID.fromString(o);
2007                            uuids.put(o, user);
2008                        }
2009                        Plot plot = plots.get(id);
2010                        if (plot != null) {
2011                            plot.getDenied().add(user);
2012                        } else if (Settings.Enabled_Components.DATABASE_PURGER) {
2013                            toDelete.add(id);
2014                        } else {
2015                            LOGGER.warn("Entry #{}({}) in `plot_denied` does not exist."
2016                                    + " Create this plot or set `database-purger: true` in settings.yml", id, plot);
2017                        }
2018                    }
2019                    deleteRows(toDelete, this.prefix + "plot_denied", "plot_plot_id");
2020                }
2021
2022                try (final ResultSet resultSet = statement
2023                        .executeQuery("SELECT * FROM `" + this.prefix + "plot_flags`")) {
2024                    BlockTypeListFlag.skipCategoryVerification =
2025                            true; // allow invalid tags, as initialized lazily
2026                    final ArrayList<Integer> toDelete = new ArrayList<>();
2027                    final Map<Plot, Collection<PlotFlag<?, ?>>> invalidFlags = new HashMap<>();
2028                    while (resultSet.next()) {
2029                        id = resultSet.getInt("plot_id");
2030                        final String flag = resultSet.getString("flag");
2031                        String value = resultSet.getString("value");
2032                        final Plot plot = plots.get(id);
2033                        if (plot != null) {
2034                            final PlotFlag<?, ?> plotFlag =
2035                                    GlobalFlagContainer.getInstance().getFlagFromString(flag);
2036                            if (plotFlag == null) {
2037                                plot.getFlagContainer().addUnknownFlag(flag, value);
2038                            } else {
2039                                value = CaptionUtility.stripClickEvents(plotFlag, value);
2040                                try {
2041                                    plot.getFlagContainer().addFlag(plotFlag.parse(value));
2042                                } catch (final FlagParseException e) {
2043                                    e.printStackTrace();
2044                                    LOGGER.error("Plot with ID {} has an invalid value:", id);
2045                                    LOGGER.error("Failed to parse flag '{}', value '{}': {}",
2046                                            plotFlag.getName(), e.getValue(), e.getErrorMessage()
2047                                    );
2048                                    if (!invalidFlags.containsKey(plot)) {
2049                                        invalidFlags.put(plot, new ArrayList<>());
2050                                    }
2051                                    invalidFlags.get(plot).add(plotFlag);
2052                                }
2053                            }
2054                        } else if (Settings.Enabled_Components.DATABASE_PURGER) {
2055                            toDelete.add(id);
2056                        } else {
2057                            LOGGER.warn("Entry #{}({}) in `plot_flags` does not exist."
2058                                    + " Create this plot or set `database-purger: true` in settings.yml", id, plot);
2059                        }
2060                    }
2061                    BlockTypeListFlag.skipCategoryVerification =
2062                            false; // don't allow invalid tags anymore
2063                    if (Settings.Enabled_Components.DATABASE_PURGER) {
2064                        for (final Map.Entry<Plot, Collection<PlotFlag<?, ?>>> plotFlagEntry : invalidFlags
2065                                .entrySet()) {
2066                            for (final PlotFlag<?, ?> flag : plotFlagEntry.getValue()) {
2067                                LOGGER.info(
2068                                        "Plot {} has an invalid flag ({}). A fix has been attempted",
2069                                        plotFlagEntry.getKey(), flag.getName()
2070                                );
2071                                removeFlag(plotFlagEntry.getKey(), flag);
2072                            }
2073                        }
2074                    }
2075                    deleteRows(toDelete, this.prefix + "plot_flags", "plot_id");
2076                }
2077
2078                try (ResultSet resultSet = statement
2079                        .executeQuery("SELECT * FROM `" + this.prefix + "plot_settings`")) {
2080                    ArrayList<Integer> toDelete = new ArrayList<>();
2081                    while (resultSet.next()) {
2082                        id = resultSet.getInt("plot_plot_id");
2083                        Plot plot = plots.get(id);
2084                        if (plot != null) {
2085                            plots.remove(id);
2086                            String alias = resultSet.getString("alias");
2087                            if (alias != null) {
2088                                plot.getSettings().setAlias(alias);
2089                            }
2090                            String pos = resultSet.getString("position");
2091                            switch (pos.toLowerCase()) {
2092                                case "":
2093                                case "default":
2094                                case "0,0,0":
2095                                case "center":
2096                                case "centre":
2097                                    break;
2098                                default:
2099                                    try {
2100                                        plot.getSettings().setPosition(BlockLoc.fromString(pos));
2101                                    } catch (Exception ignored) {
2102                                    }
2103                            }
2104                            int m = resultSet.getInt("merged");
2105                            boolean[] merged = new boolean[4];
2106                            for (int i = 0; i < 4; i++) {
2107                                merged[3 - i] = (m & 1 << i) != 0;
2108                            }
2109                            plot.getSettings().setMerged(merged);
2110                        } else if (Settings.Enabled_Components.DATABASE_PURGER) {
2111                            toDelete.add(id);
2112                        } else {
2113                            LOGGER.warn("Entry #{}({}) in `plot_settings` does not exist."
2114                                    + " Create this plot or set `database-purger: true` in settings.yml", id, plot);
2115                        }
2116                    }
2117                    deleteRows(toDelete, this.prefix + "plot_settings", "plot_plot_id");
2118                }
2119            }
2120            if (!plots.entrySet().isEmpty()) {
2121                createEmptySettings(new ArrayList<>(plots.keySet()), null);
2122                for (Entry<Integer, Plot> entry : plots.entrySet()) {
2123                    entry.getValue().getSettings();
2124                }
2125            }
2126            boolean invalidPlot = false;
2127            for (Entry<String, AtomicInteger> entry : noExist.entrySet()) {
2128                String worldName = entry.getKey();
2129                invalidPlot = true;
2130                if (Settings.DEBUG) {
2131                    LOGGER.info("Warning! Found {} plots in DB for non existent world: '{}'",
2132                            entry.getValue().intValue(), worldName
2133                    );
2134                }
2135            }
2136            if (invalidPlot && Settings.DEBUG) {
2137                LOGGER.info("Warning! Please create the world(s) or remove the plots using the purge command");
2138            }
2139        } catch (SQLException e) {
2140            LOGGER.error("Failed to load plots", e);
2141        }
2142        return newPlots;
2143    }
2144
2145    @Override
2146    public void setMerged(final Plot plot, final boolean[] merged) {
2147        plot.getSettings().setMerged(merged);
2148        addPlotTask(plot, new UniqueStatement("setMerged") {
2149            @Override
2150            public void set(PreparedStatement statement) throws SQLException {
2151                int hash = HashUtil.hash(merged);
2152                statement.setInt(1, hash);
2153                statement.setInt(2, getId(plot));
2154            }
2155
2156            @Override
2157            public PreparedStatement get() throws SQLException {
2158                return SQLManager.this.connection.prepareStatement(
2159                        "UPDATE `" + SQLManager.this.prefix
2160                                + "plot_settings` SET `merged` = ? WHERE `plot_plot_id` = ?");
2161            }
2162        });
2163    }
2164
2165    @Override
2166    public CompletableFuture<Boolean> swapPlots(Plot plot1, Plot plot2) {
2167        final CompletableFuture<Boolean> future = new CompletableFuture<>();
2168        TaskManager.runTaskAsync(() -> {
2169            final int id1 = getId(plot1);
2170            final int id2 = getId(plot2);
2171            final PlotId pos1 = plot1.getId();
2172            final PlotId pos2 = plot2.getId();
2173            try (final PreparedStatement preparedStatement = this.connection.prepareStatement(
2174                    "UPDATE `" + SQLManager.this.prefix
2175                            + "plot` SET `plot_id_x` = ?, `plot_id_z` = ? WHERE `id` = ?")) {
2176                preparedStatement.setInt(1, pos1.getX());
2177                preparedStatement.setInt(2, pos1.getY());
2178                preparedStatement.setInt(3, id1);
2179                preparedStatement.execute();
2180                preparedStatement.setInt(1, pos2.getX());
2181                preparedStatement.setInt(2, pos2.getY());
2182                preparedStatement.setInt(3, id2);
2183                preparedStatement.execute();
2184            } catch (final Exception e) {
2185                LOGGER.error("Failed to persist wap of {} and {}", plot1, plot2);
2186                e.printStackTrace();
2187                future.complete(false);
2188                return;
2189            }
2190            future.complete(true);
2191        });
2192        return future;
2193    }
2194
2195    @Override
2196    public void movePlot(final Plot original, final Plot newPlot) {
2197        addPlotTask(original, new UniqueStatement("movePlot") {
2198            @Override
2199            public void set(PreparedStatement statement) throws SQLException {
2200                statement.setInt(1, newPlot.getId().getX());
2201                statement.setInt(2, newPlot.getId().getY());
2202                statement.setString(3, newPlot.getArea().toString());
2203                statement.setInt(4, getId(original));
2204            }
2205
2206            @Override
2207            public PreparedStatement get() throws SQLException {
2208                return SQLManager.this.connection.prepareStatement(
2209                        "UPDATE `" + SQLManager.this.prefix
2210                                + "plot` SET `plot_id_x` = ?, `plot_id_z` = ?, `world` = ? WHERE `id` = ?");
2211            }
2212        });
2213        addPlotTask(newPlot, null);
2214    }
2215
2216    @Override
2217    public void setFlag(final Plot plot, final PlotFlag<?, ?> flag) {
2218        addPlotTask(plot, new UniqueStatement("setFlag") {
2219            @Override
2220            public void set(PreparedStatement statement) throws SQLException {
2221                statement.setInt(1, getId(plot));
2222                statement.setString(2, flag.getName());
2223                statement.setString(3, flag.toString());
2224                statement.setString(4, flag.toString());
2225            }
2226
2227            @Override
2228            public PreparedStatement get() throws SQLException {
2229                final String statement;
2230                if (SQLManager.this.mySQL) {
2231                    statement = "INSERT INTO `" + SQLManager.this.prefix
2232                            + "plot_flags`(`plot_id`, `flag`, `value`) VALUES(?, ?, ?) "
2233                            + "ON DUPLICATE KEY UPDATE `value` = ?";
2234                } else {
2235                    statement = "INSERT INTO `" + SQLManager.this.prefix
2236                            + "plot_flags`(`plot_id`, `flag`, `value`) VALUES(?, ?, ?) "
2237                            + "ON CONFLICT(`plot_id`,`flag`) DO UPDATE SET `value` = ?";
2238                }
2239                return SQLManager.this.connection.prepareStatement(statement);
2240            }
2241        });
2242    }
2243
2244    @Override
2245    public void removeFlag(final Plot plot, final PlotFlag<?, ?> flag) {
2246        addPlotTask(plot, new UniqueStatement("removeFlag") {
2247            @Override
2248            public void set(PreparedStatement statement) throws SQLException {
2249                statement.setInt(1, getId(plot));
2250                statement.setString(2, flag.getName());
2251            }
2252
2253            @Override
2254            public PreparedStatement get() throws SQLException {
2255                return SQLManager.this.connection.prepareStatement(
2256                        "DELETE FROM `" + SQLManager.this.prefix
2257                                + "plot_flags` WHERE `plot_id` = ? AND `flag` = ?");
2258            }
2259        });
2260    }
2261
2262    @Override
2263    public void setAlias(final Plot plot, final String alias) {
2264        addPlotTask(plot, new UniqueStatement("setAlias") {
2265            @Override
2266            public void set(PreparedStatement statement) throws SQLException {
2267                statement.setString(1, alias);
2268                statement.setInt(2, getId(plot));
2269            }
2270
2271            @Override
2272            public PreparedStatement get() throws SQLException {
2273                return SQLManager.this.connection.prepareStatement(
2274                        "UPDATE `" + SQLManager.this.prefix
2275                                + "plot_settings` SET `alias` = ?  WHERE `plot_plot_id` = ?");
2276            }
2277        });
2278    }
2279
2280    /**
2281     * Purge all plots with the following database IDs
2282     */
2283    @Override
2284    public void purgeIds(final Set<Integer> uniqueIds) {
2285        addGlobalTask(() -> {
2286            if (!uniqueIds.isEmpty()) {
2287                try {
2288                    ArrayList<Integer> uniqueIdsList = new ArrayList<>(uniqueIds);
2289                    int size = uniqueIdsList.size();
2290                    int packet = 990;
2291                    int amount = size / packet;
2292                    for (int j = 0; j <= amount; j++) {
2293                        List<Integer> subList =
2294                                uniqueIdsList.subList(j * packet, Math.min(size, (j + 1) * packet));
2295                        if (subList.isEmpty()) {
2296                            break;
2297                        }
2298                        StringBuilder idstr2 = new StringBuilder();
2299                        String stmt_prefix = "";
2300                        for (Integer id : subList) {
2301                            idstr2.append(stmt_prefix).append(id);
2302                            stmt_prefix = " OR `id` = ";
2303                        }
2304                        stmt_prefix = "";
2305                        StringBuilder idstr = new StringBuilder();
2306                        for (Integer id : subList) {
2307                            idstr.append(stmt_prefix).append(id);
2308                            stmt_prefix = " OR `plot_plot_id` = ";
2309                        }
2310                        PreparedStatement stmt = SQLManager.this.connection.prepareStatement(
2311                                "DELETE FROM `" + SQLManager.this.prefix
2312                                        + "plot_helpers` WHERE `plot_plot_id` = " + idstr);
2313                        stmt.executeUpdate();
2314                        stmt.close();
2315                        stmt = SQLManager.this.connection.prepareStatement(
2316                                "DELETE FROM `" + SQLManager.this.prefix
2317                                        + "plot_denied` WHERE `plot_plot_id` = " + idstr);
2318                        stmt.executeUpdate();
2319                        stmt.close();
2320                        stmt = SQLManager.this.connection.prepareStatement(
2321                                "DELETE FROM `" + SQLManager.this.prefix
2322                                        + "plot_settings` WHERE `plot_plot_id` = " + idstr);
2323                        stmt.executeUpdate();
2324                        stmt.close();
2325                        stmt = SQLManager.this.connection.prepareStatement(
2326                                "DELETE FROM `" + SQLManager.this.prefix
2327                                        + "plot_trusted` WHERE `plot_plot_id` = " + idstr);
2328                        stmt.executeUpdate();
2329                        stmt.close();
2330                        stmt = SQLManager.this.connection.prepareStatement(
2331                                "DELETE FROM `" + SQLManager.this.prefix + "plot` WHERE `id` = "
2332                                        + idstr2);
2333                        stmt.executeUpdate();
2334                        stmt.close();
2335                        commit();
2336                    }
2337                } catch (SQLException e) {
2338                    LOGGER.error("Failed to purge plots", e);
2339                    return;
2340                }
2341            }
2342            LOGGER.info("Successfully purged {} plots", uniqueIds.size());
2343        });
2344    }
2345
2346    @Override
2347    public void purge(final PlotArea area, final Set<PlotId> plots) {
2348        addGlobalTask(() -> {
2349            try (PreparedStatement stmt = SQLManager.this.connection.prepareStatement(
2350                    "SELECT `id`, `plot_id_x`, `plot_id_z` FROM `" + SQLManager.this.prefix
2351                            + "plot` WHERE `world` = ?")) {
2352                stmt.setString(1, area.toString());
2353                Set<Integer> ids;
2354                try (ResultSet r = stmt.executeQuery()) {
2355                    ids = new HashSet<>();
2356                    while (r.next()) {
2357                        PlotId plot_id = PlotId.of(r.getInt("plot_id_x"), r.getInt("plot_id_z"));
2358                        if (plots.contains(plot_id)) {
2359                            ids.add(r.getInt("id"));
2360                        }
2361                    }
2362                }
2363                purgeIds(ids);
2364            } catch (SQLException e) {
2365                LOGGER.error("Failed to purge area '{}'", area);
2366                e.printStackTrace();
2367            }
2368            for (Iterator<PlotId> iterator = plots.iterator(); iterator.hasNext(); ) {
2369                PlotId plotId = iterator.next();
2370                iterator.remove();
2371                PlotId id = PlotId.of(plotId.getX(), plotId.getY());
2372                area.removePlot(id);
2373            }
2374        });
2375    }
2376
2377    @Override
2378    public void setPosition(final Plot plot, final String position) {
2379        addPlotTask(plot, new UniqueStatement("setPosition") {
2380            @Override
2381            public void set(PreparedStatement statement) throws SQLException {
2382                statement.setString(1, position == null ? "" : position);
2383                statement.setInt(2, getId(plot));
2384            }
2385
2386            @Override
2387            public PreparedStatement get() throws SQLException {
2388                return SQLManager.this.connection.prepareStatement(
2389                        "UPDATE `" + SQLManager.this.prefix
2390                                + "plot_settings` SET `position` = ?  WHERE `plot_plot_id` = ?");
2391            }
2392        });
2393    }
2394
2395    @Override
2396    public void removeComment(final Plot plot, final PlotComment comment) {
2397        addPlotTask(plot, new UniqueStatement("removeComment") {
2398            @Override
2399            public void set(PreparedStatement statement) throws SQLException {
2400                if (plot != null) {
2401                    statement.setString(1, plot.getArea().toString());
2402                    statement.setInt(2, plot.getId().hashCode());
2403                    statement.setString(3, comment.comment());
2404                    statement.setString(4, comment.inbox());
2405                    statement.setString(5, comment.senderName());
2406                } else {
2407                    statement.setString(1, comment.comment());
2408                    statement.setString(2, comment.inbox());
2409                    statement.setString(3, comment.senderName());
2410                }
2411            }
2412
2413            @Override
2414            public PreparedStatement get() throws SQLException {
2415                if (plot != null) {
2416                    return SQLManager.this.connection.prepareStatement(
2417                            "DELETE FROM `" + SQLManager.this.prefix
2418                                    + "plot_comments` WHERE `world` = ? AND `hashcode` = ? AND `comment` = ? AND `inbox` = ? AND `sender` = ?");
2419                }
2420                return SQLManager.this.connection.prepareStatement(
2421                        "DELETE FROM `" + SQLManager.this.prefix
2422                                + "plot_comments` WHERE `comment` = ? AND `inbox` = ? AND `sender` = ?");
2423            }
2424        });
2425    }
2426
2427    @Override
2428    public void clearInbox(final Plot plot, final String inbox) {
2429        addPlotTask(plot, new UniqueStatement("clearInbox") {
2430            @Override
2431            public void set(PreparedStatement statement) throws SQLException {
2432                if (plot != null) {
2433                    statement.setString(1, plot.getArea().toString());
2434                    statement.setInt(2, plot.getId().hashCode());
2435                    statement.setString(3, inbox);
2436                } else {
2437                    statement.setString(1, inbox);
2438                }
2439            }
2440
2441            @Override
2442            public PreparedStatement get() throws SQLException {
2443                if (plot != null) {
2444                    return SQLManager.this.connection.prepareStatement(
2445                            "DELETE FROM `" + SQLManager.this.prefix
2446                                    + "plot_comments` WHERE `world` = ? AND `hashcode` = ? AND `inbox` = ?");
2447                }
2448                return SQLManager.this.connection.prepareStatement(
2449                        "DELETE FROM `" + SQLManager.this.prefix + "plot_comments` `inbox` = ?");
2450            }
2451        });
2452    }
2453
2454    @Override
2455    public void getComments(
2456            @NonNull Plot plot, final String inbox,
2457            final RunnableVal<List<PlotComment>> whenDone
2458    ) {
2459        addPlotTask(plot, new UniqueStatement("getComments_" + plot) {
2460            @Override
2461            public void set(PreparedStatement statement) throws SQLException {
2462                if (plot != null) {
2463                    statement.setString(1, plot.getArea().toString());
2464                    statement.setInt(2, plot.getId().hashCode());
2465                    statement.setString(3, inbox);
2466                } else {
2467                    statement.setString(1, inbox);
2468                }
2469            }
2470
2471            @Override
2472            public PreparedStatement get() throws SQLException {
2473                if (plot != null) {
2474                    return SQLManager.this.connection.prepareStatement(
2475                            "SELECT * FROM `" + SQLManager.this.prefix
2476                                    + "plot_comments` WHERE `world` = ? AND `hashcode` = ? AND `inbox` = ?");
2477                }
2478                return SQLManager.this.connection.prepareStatement(
2479                        "SELECT * FROM `" + SQLManager.this.prefix
2480                                + "plot_comments` WHERE `inbox` = ?");
2481            }
2482
2483            @Override
2484            public void execute(PreparedStatement statement) {
2485            }
2486
2487            @Override
2488            public void addBatch(PreparedStatement statement) throws SQLException {
2489                ArrayList<PlotComment> comments = new ArrayList<>();
2490                try (ResultSet set = statement.executeQuery()) {
2491                    while (set.next()) {
2492                        String sender = set.getString("sender");
2493                        String world = set.getString("world");
2494                        int hash = set.getInt("hashcode");
2495                        PlotId id;
2496                        if (hash != 0) {
2497                            id = PlotId.unpair(hash);
2498                        } else {
2499                            id = null;
2500                        }
2501                        String msg = set.getString("comment");
2502                        long timestamp = set.getInt("timestamp") * 1000;
2503                        PlotComment comment =
2504                                new PlotComment(world, id, msg, sender, inbox, timestamp);
2505                        comments.add(comment);
2506                    }
2507                    whenDone.value = comments;
2508                }
2509                TaskManager.runTask(whenDone);
2510            }
2511        });
2512    }
2513
2514    @Override
2515    public void setComment(final Plot plot, final PlotComment comment) {
2516        addPlotTask(plot, new UniqueStatement("setComment") {
2517            @Override
2518            public void set(PreparedStatement statement) throws SQLException {
2519                statement.setString(1, plot.getArea().toString());
2520                statement.setInt(2, plot.getId().hashCode());
2521                statement.setString(3, comment.comment());
2522                statement.setString(4, comment.inbox());
2523                statement.setInt(5, (int) (comment.timestamp() / 1000));
2524                statement.setString(6, comment.senderName());
2525            }
2526
2527            @Override
2528            public PreparedStatement get() throws SQLException {
2529                return SQLManager.this.connection.prepareStatement(
2530                        "INSERT INTO `" + SQLManager.this.prefix
2531                                + "plot_comments` (`world`, `hashcode`, `comment`, `inbox`, `timestamp`, `sender`) VALUES(?,?,?,?,?,?)");
2532            }
2533        });
2534    }
2535
2536    @Override
2537    public void removeTrusted(final Plot plot, final UUID uuid) {
2538        addPlotTask(plot, new UniqueStatement("removeTrusted") {
2539            @Override
2540            public void set(PreparedStatement statement) throws SQLException {
2541                statement.setInt(1, getId(plot));
2542                statement.setString(2, uuid.toString());
2543            }
2544
2545            @Override
2546            public PreparedStatement get() throws SQLException {
2547                return SQLManager.this.connection.prepareStatement(
2548                        "DELETE FROM `" + SQLManager.this.prefix
2549                                + "plot_helpers` WHERE `plot_plot_id` = ? AND `user_uuid` = ?");
2550            }
2551        });
2552    }
2553
2554    @Override
2555    public void removeMember(final Plot plot, final UUID uuid) {
2556        addPlotTask(plot, new UniqueStatement("removeMember") {
2557            @Override
2558            public void set(PreparedStatement statement) throws SQLException {
2559                statement.setInt(1, getId(plot));
2560                statement.setString(2, uuid.toString());
2561            }
2562
2563            @Override
2564            public PreparedStatement get() throws SQLException {
2565                return SQLManager.this.connection.prepareStatement(
2566                        "DELETE FROM `" + SQLManager.this.prefix
2567                                + "plot_trusted` WHERE `plot_plot_id` = ? AND `user_uuid` = ?");
2568            }
2569        });
2570    }
2571
2572    @Override
2573    public void setTrusted(final Plot plot, final UUID uuid) {
2574        addPlotTask(plot, new UniqueStatement("setTrusted") {
2575            @Override
2576            public void set(PreparedStatement statement) throws SQLException {
2577                statement.setInt(1, getId(plot));
2578                statement.setString(2, uuid.toString());
2579            }
2580
2581            @Override
2582            public PreparedStatement get() throws SQLException {
2583                return SQLManager.this.connection.prepareStatement(
2584                        "INSERT INTO `" + SQLManager.this.prefix
2585                                + "plot_helpers` (`plot_plot_id`, `user_uuid`) VALUES(?,?)");
2586            }
2587        });
2588    }
2589
2590    @Override
2591    public void setMember(final Plot plot, final UUID uuid) {
2592        addPlotTask(plot, new UniqueStatement("setMember") {
2593            @Override
2594            public void set(PreparedStatement statement) throws SQLException {
2595                statement.setInt(1, getId(plot));
2596                statement.setString(2, uuid.toString());
2597            }
2598
2599            @Override
2600            public PreparedStatement get() throws SQLException {
2601                return SQLManager.this.connection.prepareStatement(
2602                        "INSERT INTO `" + SQLManager.this.prefix
2603                                + "plot_trusted` (`plot_plot_id`, `user_uuid`) VALUES(?,?)");
2604            }
2605        });
2606    }
2607
2608    @Override
2609    public void removeDenied(final Plot plot, final UUID uuid) {
2610        addPlotTask(plot, new UniqueStatement("removeDenied") {
2611            @Override
2612            public void set(PreparedStatement statement) throws SQLException {
2613                statement.setInt(1, getId(plot));
2614                statement.setString(2, uuid.toString());
2615            }
2616
2617            @Override
2618            public PreparedStatement get() throws SQLException {
2619                return SQLManager.this.connection.prepareStatement(
2620                        "DELETE FROM `" + SQLManager.this.prefix
2621                                + "plot_denied` WHERE `plot_plot_id` = ? AND `user_uuid` = ?");
2622            }
2623        });
2624    }
2625
2626    @Override
2627    public void setDenied(final Plot plot, final UUID uuid) {
2628        addPlotTask(plot, new UniqueStatement("setDenied") {
2629            @Override
2630            public void set(PreparedStatement statement) throws SQLException {
2631                statement.setInt(1, getId(plot));
2632                statement.setString(2, uuid.toString());
2633            }
2634
2635            @Override
2636            public PreparedStatement get() throws SQLException {
2637                return SQLManager.this.connection.prepareStatement(
2638                        "INSERT INTO `" + SQLManager.this.prefix
2639                                + "plot_denied` (`plot_plot_id`, `user_uuid`) VALUES(?,?)");
2640            }
2641        });
2642    }
2643
2644    @Override
2645    public HashMap<UUID, Integer> getRatings(Plot plot) {
2646        HashMap<UUID, Integer> map = new HashMap<>();
2647        try (PreparedStatement statement = this.connection.prepareStatement(
2648                "SELECT `rating`, `player` FROM `" + this.prefix
2649                        + "plot_rating` WHERE `plot_plot_id` = ? ")) {
2650            statement.setInt(1, getId(plot));
2651            try (ResultSet resultSet = statement.executeQuery()) {
2652                while (resultSet.next()) {
2653                    UUID uuid = UUID.fromString(resultSet.getString("player"));
2654                    int rating = resultSet.getInt("rating");
2655                    map.put(uuid, rating);
2656                }
2657            }
2658        } catch (SQLException e) {
2659            LOGGER.error("Failed to fetch rating for plot {}", plot.getId().toString());
2660            e.printStackTrace();
2661        }
2662        return map;
2663    }
2664
2665    @Override
2666    public void setRating(final Plot plot, final UUID rater, final int value) {
2667        addPlotTask(plot, new UniqueStatement("setRating") {
2668            @Override
2669            public void set(PreparedStatement statement) throws SQLException {
2670                statement.setInt(1, getId(plot));
2671                statement.setInt(2, value);
2672                statement.setString(3, rater.toString());
2673            }
2674
2675            @Override
2676            public PreparedStatement get() throws SQLException {
2677                return SQLManager.this.connection.prepareStatement(
2678                        "INSERT INTO `" + SQLManager.this.prefix
2679                                + "plot_rating` (`plot_plot_id`, `rating`, `player`) VALUES(?,?,?)");
2680            }
2681        });
2682    }
2683
2684    @Override
2685    public void delete(PlotCluster cluster) {
2686        final int id = getClusterId(cluster);
2687        addClusterTask(cluster, new UniqueStatement("delete_cluster_settings") {
2688            @Override
2689            public void set(PreparedStatement statement) throws SQLException {
2690                statement.setInt(1, id);
2691            }
2692
2693            @Override
2694            public PreparedStatement get() throws SQLException {
2695                return SQLManager.this.connection.prepareStatement(
2696                        "DELETE FROM `" + SQLManager.this.prefix
2697                                + "cluster_settings` WHERE `cluster_id` = ?");
2698            }
2699        });
2700        addClusterTask(cluster, new UniqueStatement("delete_cluster_helpers") {
2701            @Override
2702            public void set(PreparedStatement statement) throws SQLException {
2703                statement.setInt(1, id);
2704            }
2705
2706            @Override
2707            public PreparedStatement get() throws SQLException {
2708                return SQLManager.this.connection.prepareStatement(
2709                        "DELETE FROM `" + SQLManager.this.prefix
2710                                + "cluster_helpers` WHERE `cluster_id` = ?");
2711            }
2712        });
2713        addClusterTask(cluster, new UniqueStatement("delete_cluster_invited") {
2714            @Override
2715            public void set(PreparedStatement statement) throws SQLException {
2716                statement.setInt(1, id);
2717            }
2718
2719            @Override
2720            public PreparedStatement get() throws SQLException {
2721                return SQLManager.this.connection.prepareStatement(
2722                        "DELETE FROM `" + SQLManager.this.prefix
2723                                + "cluster_invited` WHERE `cluster_id` = ?");
2724            }
2725        });
2726        addClusterTask(cluster, new UniqueStatement("delete_cluster") {
2727            @Override
2728            public void set(PreparedStatement statement) throws SQLException {
2729                statement.setInt(1, id);
2730            }
2731
2732            @Override
2733            public PreparedStatement get() throws SQLException {
2734                return SQLManager.this.connection.prepareStatement(
2735                        "DELETE FROM `" + SQLManager.this.prefix + "cluster` WHERE `id` = ?");
2736            }
2737        });
2738    }
2739
2740    @Override
2741    public void addPersistentMeta(
2742            final UUID uuid, final String key, final byte[] meta,
2743            final boolean replace
2744    ) {
2745        addPlayerTask(uuid, new UniqueStatement("addPersistentMeta") {
2746            @Override
2747            public void set(PreparedStatement statement) throws SQLException {
2748                if (replace) {
2749                    statement.setBytes(1, meta);
2750                    statement.setString(2, uuid.toString());
2751                    statement.setString(3, key);
2752                } else {
2753                    statement.setString(1, uuid.toString());
2754                    statement.setString(2, key);
2755                    statement.setBytes(3, meta);
2756                }
2757            }
2758
2759            @Override
2760            public PreparedStatement get() throws SQLException {
2761                if (replace) {
2762                    return SQLManager.this.connection.prepareStatement(
2763                            "UPDATE `" + SQLManager.this.prefix
2764                                    + "player_meta` SET `value` = ? WHERE `uuid` = ? AND `key` = ?");
2765                } else {
2766                    return SQLManager.this.connection.prepareStatement(
2767                            "INSERT INTO `" + SQLManager.this.prefix
2768                                    + "player_meta`(`uuid`, `key`, `value`) VALUES(?, ? ,?)");
2769                }
2770            }
2771        });
2772    }
2773
2774    @Override
2775    public void removePersistentMeta(final UUID uuid, final String key) {
2776        addPlayerTask(uuid, new UniqueStatement("removePersistentMeta") {
2777            @Override
2778            public void set(PreparedStatement statement) throws SQLException {
2779                statement.setString(1, uuid.toString());
2780                statement.setString(2, key);
2781            }
2782
2783            @Override
2784            public PreparedStatement get() throws SQLException {
2785                return SQLManager.this.connection.prepareStatement(
2786                        "DELETE FROM `" + SQLManager.this.prefix
2787                                + "player_meta` WHERE `uuid` = ? AND `key` = ?");
2788            }
2789        });
2790    }
2791
2792    @Override
2793    public void getPersistentMeta(final UUID uuid, final RunnableVal<Map<String, byte[]>> result) {
2794        addPlayerTask(uuid, new UniqueStatement("getPersistentMeta") {
2795            @Override
2796            public void set(PreparedStatement statement) throws SQLException {
2797                statement.setString(1, uuid.toString());
2798            }
2799
2800            @Override
2801            public PreparedStatement get() throws SQLException {
2802                return SQLManager.this.connection.prepareStatement(
2803                        "SELECT * FROM `" + SQLManager.this.prefix
2804                                + "player_meta` WHERE `uuid` = ? ORDER BY `meta_id` ASC");
2805            }
2806
2807            @Override
2808            public void execute(PreparedStatement statement) {
2809            }
2810
2811            @Override
2812            public void addBatch(PreparedStatement statement) throws SQLException {
2813                ResultSet resultSet = statement.executeQuery();
2814
2815                final Map<String, byte[]> metaMap = new HashMap<>();
2816
2817                while (resultSet.next()) {
2818                    String key = resultSet.getString("key");
2819                    byte[] bytes = resultSet.getBytes("value");
2820                    metaMap.put(key, bytes);
2821                }
2822
2823                resultSet.close();
2824                TaskManager.runTaskAsync(() -> result.run(metaMap));
2825            }
2826
2827        });
2828    }
2829
2830    @Override
2831    public HashMap<String, Set<PlotCluster>> getClusters() {
2832        LinkedHashMap<String, Set<PlotCluster>> newClusters = new LinkedHashMap<>();
2833        HashMap<Integer, PlotCluster> clusters = new HashMap<>();
2834        try {
2835            HashSet<String> areas = new HashSet<>();
2836            if (this.worldConfiguration.contains("worlds")) {
2837                ConfigurationSection worldSection = this.worldConfiguration.getConfigurationSection("worlds");
2838                if (worldSection != null) {
2839                    for (String worldKey : worldSection.getKeys(false)) {
2840                        areas.add(worldKey);
2841                        ConfigurationSection areaSection =
2842                                worldSection.getConfigurationSection(worldKey + ".areas");
2843                        if (areaSection != null) {
2844                            for (String areaKey : areaSection.getKeys(false)) {
2845                                String[] split = areaKey.split("(?<![;])-");
2846                                if (split.length == 3) {
2847                                    areas.add(worldKey + ';' + split[0]);
2848                                }
2849                            }
2850                        }
2851                    }
2852                }
2853            }
2854            HashMap<String, UUID> uuids = new HashMap<>();
2855            HashMap<String, Integer> noExist = new HashMap<>();
2856            /*
2857             * Getting clusters
2858             */
2859            try (Statement stmt = this.connection.createStatement()) {
2860                ResultSet resultSet =
2861                        stmt.executeQuery("SELECT * FROM `" + this.prefix + "cluster`");
2862                PlotCluster cluster;
2863                String owner;
2864                UUID user;
2865                int id;
2866                while (resultSet.next()) {
2867                    PlotId pos1 =
2868                            PlotId.of(resultSet.getInt("pos1_x"), resultSet.getInt("pos1_z"));
2869                    PlotId pos2 =
2870                            PlotId.of(resultSet.getInt("pos2_x"), resultSet.getInt("pos2_z"));
2871                    id = resultSet.getInt("id");
2872                    String areaid = resultSet.getString("world");
2873                    if (!areas.contains(areaid)) {
2874                        noExist.merge(areaid, 1, Integer::sum);
2875                    }
2876                    owner = resultSet.getString("owner");
2877                    user = uuids.get(owner);
2878                    if (user == null) {
2879                        user = UUID.fromString(owner);
2880                        uuids.put(owner, user);
2881                    }
2882                    cluster = new PlotCluster(null, pos1, pos2, user, id);
2883                    clusters.put(id, cluster);
2884                    Set<PlotCluster> set =
2885                            newClusters.computeIfAbsent(areaid, k -> new HashSet<>());
2886                    set.add(cluster);
2887                }
2888                //Getting helpers
2889                resultSet = stmt.executeQuery(
2890                        "SELECT `user_uuid`, `cluster_id` FROM `" + this.prefix + "cluster_helpers`");
2891                while (resultSet.next()) {
2892                    id = resultSet.getInt("cluster_id");
2893                    owner = resultSet.getString("user_uuid");
2894                    user = uuids.get(owner);
2895                    if (user == null) {
2896                        user = UUID.fromString(owner);
2897                        uuids.put(owner, user);
2898                    }
2899                    cluster = clusters.get(id);
2900                    if (cluster != null) {
2901                        cluster.helpers.add(user);
2902                    } else {
2903                        LOGGER.warn("Cluster #{}({}) in cluster_helpers does not exist."
2904                                + " Please create the cluster or remove this entry", id, cluster);
2905                    }
2906                }
2907                // Getting invited
2908                resultSet = stmt.executeQuery(
2909                        "SELECT `user_uuid`, `cluster_id` FROM `" + this.prefix + "cluster_invited`");
2910                while (resultSet.next()) {
2911                    id = resultSet.getInt("cluster_id");
2912                    owner = resultSet.getString("user_uuid");
2913                    user = uuids.get(owner);
2914                    if (user == null) {
2915                        user = UUID.fromString(owner);
2916                        uuids.put(owner, user);
2917                    }
2918                    cluster = clusters.get(id);
2919                    if (cluster != null) {
2920                        cluster.invited.add(user);
2921                    } else {
2922                        LOGGER.warn("Cluster #{}({}) in cluster_helpers does not exist."
2923                                + " Please create the cluster or remove this entry", id, cluster);
2924                    }
2925                }
2926                resultSet =
2927                        stmt.executeQuery("SELECT * FROM `" + this.prefix + "cluster_settings`");
2928                while (resultSet.next()) {
2929                    id = resultSet.getInt("cluster_id");
2930                    cluster = clusters.get(id);
2931                    if (cluster != null) {
2932                        String alias = resultSet.getString("alias");
2933                        if (alias != null) {
2934                            cluster.settings.setAlias(alias);
2935                        }
2936                        String pos = resultSet.getString("position");
2937                        switch (pos.toLowerCase()) {
2938                            case "":
2939                            case "default":
2940                            case "0,0,0":
2941                            case "center":
2942                            case "centre":
2943                                break;
2944                            default:
2945                                try {
2946                                    BlockLoc loc = BlockLoc.fromString(pos);
2947                                    cluster.settings.setPosition(loc);
2948                                } catch (Exception ignored) {
2949                                }
2950                        }
2951                        int m = resultSet.getInt("merged");
2952                        boolean[] merged = new boolean[4];
2953                        for (int i = 0; i < 4; i++) {
2954                            merged[3 - i] = (m & 1 << i) != 0;
2955                        }
2956                        cluster.settings.setMerged(merged);
2957                    } else {
2958                        LOGGER.warn("Cluster #{}({}) in cluster_helpers does not exist."
2959                                + " Please create the cluster or remove this entry", id, cluster);
2960                    }
2961                }
2962                resultSet.close();
2963            }
2964            boolean invalidPlot = false;
2965            for (Entry<String, Integer> entry : noExist.entrySet()) {
2966                String a = entry.getKey();
2967                invalidPlot = true;
2968                LOGGER.warn("Warning! Found {} clusters in DB for non existent area; '{}'", noExist.get(a), a);
2969            }
2970            if (invalidPlot) {
2971                LOGGER.warn("Warning! Please create the world(s) or remove the clusters using the purge command");
2972            }
2973        } catch (SQLException e) {
2974            LOGGER.error("Failed to load clusters", e);
2975        }
2976        return newClusters;
2977    }
2978
2979    @Override
2980    public void setClusterName(final PlotCluster cluster, final String name) {
2981        addClusterTask(cluster, new UniqueStatement("setClusterName") {
2982            @Override
2983            public void set(PreparedStatement statement) throws SQLException {
2984                statement.setString(1, name);
2985                statement.setInt(2, getClusterId(cluster));
2986            }
2987
2988            @Override
2989            public PreparedStatement get() throws SQLException {
2990                return SQLManager.this.connection.prepareStatement(
2991                        "UPDATE `" + SQLManager.this.prefix
2992                                + "cluster_settings` SET `alias` = ?  WHERE `cluster_id` = ?");
2993            }
2994        });
2995        cluster.settings.setAlias(name);
2996    }
2997
2998    @Override
2999    public void removeHelper(final PlotCluster cluster, final UUID uuid) {
3000        addClusterTask(cluster, new UniqueStatement("removeHelper") {
3001            @Override
3002            public void set(PreparedStatement statement) throws SQLException {
3003                statement.setInt(1, getClusterId(cluster));
3004                statement.setString(2, uuid.toString());
3005            }
3006
3007            @Override
3008            public PreparedStatement get() throws SQLException {
3009                return SQLManager.this.connection.prepareStatement(
3010                        "DELETE FROM `" + SQLManager.this.prefix
3011                                + "cluster_helpers` WHERE `cluster_id` = ? AND `user_uuid` = ?");
3012            }
3013        });
3014    }
3015
3016    @Override
3017    public void setHelper(final PlotCluster cluster, final UUID uuid) {
3018        addClusterTask(cluster, new UniqueStatement("setHelper") {
3019            @Override
3020            public void set(PreparedStatement statement) throws SQLException {
3021                statement.setInt(1, getClusterId(cluster));
3022                statement.setString(2, uuid.toString());
3023            }
3024
3025            @Override
3026            public PreparedStatement get() throws SQLException {
3027                return SQLManager.this.connection.prepareStatement(
3028                        "INSERT INTO `" + SQLManager.this.prefix
3029                                + "cluster_helpers` (`cluster_id`, `user_uuid`) VALUES(?,?)");
3030            }
3031        });
3032    }
3033
3034    @Override
3035    public void createCluster(final PlotCluster cluster) {
3036        addClusterTask(cluster, new UniqueStatement("createCluster_" + cluster.hashCode()) {
3037            @Override
3038            public void set(PreparedStatement statement) throws SQLException {
3039                statement.setInt(1, cluster.getP1().getX());
3040                statement.setInt(2, cluster.getP1().getY());
3041                statement.setInt(3, cluster.getP2().getX());
3042                statement.setInt(4, cluster.getP2().getY());
3043                statement.setString(5, cluster.owner.toString());
3044                statement.setString(6, cluster.area.toString());
3045            }
3046
3047            @Override
3048            public PreparedStatement get() throws SQLException {
3049                return SQLManager.this.connection.prepareStatement(
3050                        SQLManager.this.CREATE_CLUSTER,
3051                        Statement.RETURN_GENERATED_KEYS
3052                );
3053            }
3054
3055            @Override
3056            public void execute(PreparedStatement statement) {
3057            }
3058
3059            @Override
3060            public void addBatch(PreparedStatement statement) throws SQLException {
3061                statement.executeUpdate();
3062                try (ResultSet keys = statement.getGeneratedKeys()) {
3063                    if (keys.next()) {
3064                        cluster.temp = keys.getInt(1);
3065                    }
3066                }
3067            }
3068        });
3069        addClusterTask(
3070                cluster,
3071                new UniqueStatement("createCluster_settings_" + cluster.hashCode()) {
3072                    @Override
3073                    public void set(PreparedStatement statement) throws SQLException {
3074                        statement.setInt(1, getClusterId(cluster));
3075                        statement.setString(2, cluster.settings.getAlias());
3076                    }
3077
3078                    @Override
3079                    public PreparedStatement get() throws SQLException {
3080                        return SQLManager.this.connection.prepareStatement(
3081                                "INSERT INTO `" + SQLManager.this.prefix
3082                                        + "cluster_settings`(`cluster_id`, `alias`) VALUES(?, ?)");
3083                    }
3084                }
3085        );
3086    }
3087
3088    @Override
3089    public void resizeCluster(final PlotCluster current, PlotId min, PlotId max) {
3090        final PlotId pos1 = PlotId.of(current.getP1().getX(), current.getP1().getY());
3091        final PlotId pos2 = PlotId.of(current.getP2().getX(), current.getP2().getY());
3092        current.setP1(min);
3093        current.setP2(max);
3094
3095        addClusterTask(current, new UniqueStatement("resizeCluster") {
3096            @Override
3097            public void set(PreparedStatement statement) throws SQLException {
3098                statement.setInt(1, pos1.getX());
3099                statement.setInt(2, pos1.getY());
3100                statement.setInt(3, pos2.getX());
3101                statement.setInt(4, pos2.getY());
3102                statement.setInt(5, getClusterId(current));
3103            }
3104
3105            @Override
3106            public PreparedStatement get() throws SQLException {
3107                return SQLManager.this.connection.prepareStatement(
3108                        "UPDATE `" + SQLManager.this.prefix
3109                                + "cluster` SET `pos1_x` = ?, `pos1_z` = ?, `pos2_x` = ?, `pos2_z` = ?  WHERE `id` = ?");
3110            }
3111        });
3112    }
3113
3114    @Override
3115    public void setPosition(final PlotCluster cluster, final String position) {
3116        addClusterTask(cluster, new UniqueStatement("setPosition") {
3117            @Override
3118            public void set(PreparedStatement statement) throws SQLException {
3119                statement.setString(1, position);
3120                statement.setInt(2, getClusterId(cluster));
3121            }
3122
3123            @Override
3124            public PreparedStatement get() throws SQLException {
3125                return SQLManager.this.connection.prepareStatement(
3126                        "UPDATE `" + SQLManager.this.prefix
3127                                + "cluster_settings` SET `position` = ?  WHERE `cluster_id` = ?");
3128            }
3129        });
3130    }
3131
3132    @Override
3133    public void removeInvited(final PlotCluster cluster, final UUID uuid) {
3134        addClusterTask(cluster, new UniqueStatement("removeInvited") {
3135            @Override
3136            public void set(PreparedStatement statement) throws SQLException {
3137                statement.setInt(1, getClusterId(cluster));
3138                statement.setString(2, uuid.toString());
3139            }
3140
3141            @Override
3142            public PreparedStatement get() throws SQLException {
3143                return SQLManager.this.connection.prepareStatement(
3144                        "DELETE FROM `" + SQLManager.this.prefix
3145                                + "cluster_invited` WHERE `cluster_id` = ? AND `user_uuid` = ?");
3146            }
3147        });
3148    }
3149
3150    @Override
3151    public void setInvited(final PlotCluster cluster, final UUID uuid) {
3152        addClusterTask(cluster, new UniqueStatement("setInvited") {
3153            @Override
3154            public void set(PreparedStatement statement) throws SQLException {
3155                statement.setInt(1, getClusterId(cluster));
3156                statement.setString(2, uuid.toString());
3157            }
3158
3159            @Override
3160            public PreparedStatement get() throws SQLException {
3161                return SQLManager.this.connection.prepareStatement(
3162                        "INSERT INTO `" + SQLManager.this.prefix
3163                                + "cluster_invited` (`cluster_id`, `user_uuid`) VALUES(?,?)");
3164            }
3165        });
3166    }
3167
3168    @Override
3169    public boolean deleteTables() {
3170        try (Statement stmt = this.connection.createStatement();
3171             PreparedStatement statement = this.connection
3172                     .prepareStatement("DROP TABLE `" + this.prefix + "plot`")) {
3173            close();
3174            this.closed = false;
3175            SQLManager.this.connection = this.database.forceConnection();
3176            stmt.addBatch("DROP TABLE `" + this.prefix + "cluster_invited`");
3177            stmt.addBatch("DROP TABLE `" + this.prefix + "cluster_helpers`");
3178            stmt.addBatch("DROP TABLE `" + this.prefix + "cluster`");
3179            stmt.addBatch("DROP TABLE `" + this.prefix + "plot_rating`");
3180            stmt.addBatch("DROP TABLE `" + this.prefix + "plot_settings`");
3181            stmt.addBatch("DROP TABLE `" + this.prefix + "plot_comments`");
3182            stmt.addBatch("DROP TABLE `" + this.prefix + "plot_trusted`");
3183            stmt.addBatch("DROP TABLE `" + this.prefix + "plot_helpers`");
3184            stmt.addBatch("DROP TABLE `" + this.prefix + "plot_denied`");
3185            stmt.executeBatch();
3186            stmt.clearBatch();
3187            statement.executeUpdate();
3188        } catch (ClassNotFoundException | SQLException e) {
3189            e.printStackTrace();
3190
3191        }
3192        return true;
3193    }
3194
3195    @SuppressWarnings({"unchecked", "unused"})
3196    @Override
3197    public void validateAllPlots(Set<Plot> toValidate) {
3198        if (!isValid()) {
3199            reconnect();
3200        }
3201        LOGGER.info(
3202                "All DB transactions during this session are being validated (This may take a while if corrections need to be made)");
3203        commit();
3204        while (true) {
3205            if (!sendBatch()) {
3206                break;
3207            }
3208        }
3209        try {
3210            if (this.connection.getAutoCommit()) {
3211                this.connection.setAutoCommit(false);
3212            }
3213        } catch (SQLException e) {
3214            e.printStackTrace();
3215        }
3216        HashMap<String, HashMap<PlotId, Plot>> database = getPlots();
3217        ArrayList<Plot> toCreate = new ArrayList<>();
3218        for (Plot plot : toValidate) {
3219            if (plot.temp == -1) {
3220                continue;
3221            }
3222            if (plot.getArea() == null) {
3223                LOGGER.error("CRITICAL ERROR IN VALIDATION TASK: {}", plot);
3224                LOGGER.error("PLOT AREA CANNOT BE NULL! SKIPPING PLOT!");
3225                LOGGER.info("Delete this entry from your database or set `database-purger: true` in the settings.yml");
3226                continue;
3227            }
3228            if (database == null) {
3229                LOGGER.error("CRITICAL ERROR IN VALIDATION TASK!");
3230                LOGGER.error("DATABASE VARIABLE CANNOT BE NULL! NOW ENDING VALIDATION!");
3231                break;
3232            }
3233            HashMap<PlotId, Plot> worldPlots = database.get(plot.getArea().toString());
3234            if (worldPlots == null) {
3235                toCreate.add(plot);
3236                continue;
3237            }
3238            Plot dataPlot = worldPlots.remove(plot.getId());
3239            if (dataPlot == null) {
3240                toCreate.add(plot);
3241                continue;
3242            }
3243            // owner
3244            if (!plot.getOwnerAbs().equals(dataPlot.getOwnerAbs())) {
3245                setOwner(plot, plot.getOwnerAbs());
3246            }
3247            // trusted
3248            if (!plot.getTrusted().equals(dataPlot.getTrusted())) {
3249                HashSet<UUID> toAdd = (HashSet<UUID>) plot.getTrusted().clone();
3250                HashSet<UUID> toRemove = (HashSet<UUID>) dataPlot.getTrusted().clone();
3251                toRemove.removeAll(plot.getTrusted());
3252                toAdd.removeAll(dataPlot.getTrusted());
3253                if (!toRemove.isEmpty()) {
3254                    for (UUID uuid : toRemove) {
3255                        removeTrusted(plot, uuid);
3256                    }
3257                }
3258                if (!toAdd.isEmpty()) {
3259                    for (UUID uuid : toAdd) {
3260                        setTrusted(plot, uuid);
3261                    }
3262                }
3263            }
3264            if (!plot.getMembers().equals(dataPlot.getMembers())) {
3265                HashSet<UUID> toAdd = (HashSet<UUID>) plot.getMembers().clone();
3266                HashSet<UUID> toRemove = (HashSet<UUID>) dataPlot.getMembers().clone();
3267                toRemove.removeAll(plot.getMembers());
3268                toAdd.removeAll(dataPlot.getMembers());
3269                if (!toRemove.isEmpty()) {
3270                    for (UUID uuid : toRemove) {
3271                        removeMember(plot, uuid);
3272                    }
3273                }
3274                if (!toAdd.isEmpty()) {
3275                    for (UUID uuid : toAdd) {
3276                        setMember(plot, uuid);
3277                    }
3278                }
3279            }
3280            if (!plot.getDenied().equals(dataPlot.getDenied())) {
3281                HashSet<UUID> toAdd = (HashSet<UUID>) plot.getDenied().clone();
3282                HashSet<UUID> toRemove = (HashSet<UUID>) dataPlot.getDenied().clone();
3283                toRemove.removeAll(plot.getDenied());
3284                toAdd.removeAll(dataPlot.getDenied());
3285                if (!toRemove.isEmpty()) {
3286                    for (UUID uuid : toRemove) {
3287                        removeDenied(plot, uuid);
3288                    }
3289                }
3290                if (!toAdd.isEmpty()) {
3291                    for (UUID uuid : toAdd) {
3292                        setDenied(plot, uuid);
3293                    }
3294                }
3295            }
3296            boolean[] pm = plot.getMerged();
3297            boolean[] dm = dataPlot.getMerged();
3298            if (pm[0] != dm[0] || pm[1] != dm[1]) {
3299                setMerged(dataPlot, plot.getMerged());
3300            }
3301            Set<PlotFlag<?, ?>> pf = plot.getFlags();
3302            Set<PlotFlag<?, ?>> df = dataPlot.getFlags();
3303            if (!pf.isEmpty() && !df.isEmpty()) {
3304                if (pf.size() != df.size() || !StringMan
3305                        .isEqual(StringMan.joinOrdered(pf, ","), StringMan.joinOrdered(df, ","))) {
3306                    // setFlags(plot, pf);
3307                    // TODO: Re-implement
3308                }
3309            }
3310        }
3311
3312        for (Entry<String, HashMap<PlotId, Plot>> entry : database.entrySet()) {
3313            HashMap<PlotId, Plot> map = entry.getValue();
3314            if (!map.isEmpty()) {
3315                for (Entry<PlotId, Plot> entry2 : map.entrySet()) {
3316                    // TODO implement this when sure safe"
3317                }
3318            }
3319        }
3320        commit();
3321    }
3322
3323    @Override
3324    public void replaceWorld(
3325            final String oldWorld, final String newWorld, final PlotId min,
3326            final PlotId max
3327    ) {
3328        addGlobalTask(() -> {
3329            if (min == null) {
3330                try (PreparedStatement stmt = SQLManager.this.connection.prepareStatement(
3331                        "UPDATE `" + SQLManager.this.prefix
3332                                + "plot` SET `world` = ? WHERE `world` = ?")) {
3333                    stmt.setString(1, newWorld);
3334                    stmt.setString(2, oldWorld);
3335                    stmt.executeUpdate();
3336                } catch (SQLException e) {
3337                    e.printStackTrace();
3338                }
3339                try (PreparedStatement stmt = SQLManager.this.connection.prepareStatement(
3340                        "UPDATE `" + SQLManager.this.prefix
3341                                + "cluster` SET `world` = ? WHERE `world` = ?")) {
3342                    stmt.setString(1, newWorld);
3343                    stmt.setString(2, oldWorld);
3344                    stmt.executeUpdate();
3345                } catch (SQLException e) {
3346                    e.printStackTrace();
3347                }
3348            } else {
3349                try (PreparedStatement stmt = SQLManager.this.connection.prepareStatement(
3350                        "UPDATE `" + SQLManager.this.prefix
3351                                + "plot` SET `world` = ? WHERE `world` = ? AND `plot_id_x` BETWEEN ? AND ? AND `plot_id_z` BETWEEN ? AND ?")) {
3352                    stmt.setString(1, newWorld);
3353                    stmt.setString(2, oldWorld);
3354                    stmt.setInt(3, min.getX());
3355                    stmt.setInt(4, max.getX());
3356                    stmt.setInt(5, min.getY());
3357                    stmt.setInt(6, max.getY());
3358                    stmt.executeUpdate();
3359                } catch (SQLException e) {
3360                    e.printStackTrace();
3361                }
3362                try (PreparedStatement stmt = SQLManager.this.connection.prepareStatement(
3363                        "UPDATE `" + SQLManager.this.prefix
3364                                + "cluster` SET `world` = ? WHERE `world` = ? AND `pos1_x` <= ? AND `pos1_z` <= ? AND `pos2_x` >= ? AND `pos2_z` >= ?")) {
3365                    stmt.setString(1, newWorld);
3366                    stmt.setString(2, oldWorld);
3367                    stmt.setInt(3, max.getX());
3368                    stmt.setInt(4, max.getY());
3369                    stmt.setInt(5, min.getX());
3370                    stmt.setInt(6, min.getY());
3371                    stmt.executeUpdate();
3372                } catch (SQLException e) {
3373                    e.printStackTrace();
3374                }
3375            }
3376        });
3377    }
3378
3379    @Override
3380    public void replaceUUID(final UUID old, final UUID now) {
3381        addGlobalTask(() -> {
3382            try (Statement stmt = SQLManager.this.connection.createStatement()) {
3383                stmt.executeUpdate(
3384                        "UPDATE `" + SQLManager.this.prefix + "cluster` SET `owner` = '" + now
3385                                .toString() + "' WHERE `owner` = '" + old.toString() + '\'');
3386                stmt.executeUpdate(
3387                        "UPDATE `" + SQLManager.this.prefix + "cluster_helpers` SET `user_uuid` = '"
3388                                + now + "' WHERE `user_uuid` = '" + old + '\'');
3389                stmt.executeUpdate(
3390                        "UPDATE `" + SQLManager.this.prefix + "cluster_invited` SET `user_uuid` = '"
3391                                + now + "' WHERE `user_uuid` = '" + old + '\'');
3392                stmt.executeUpdate(
3393                        "UPDATE `" + SQLManager.this.prefix + "plot` SET `owner` = '" + now
3394                                + "' WHERE `owner` = '" + old + '\'');
3395                stmt.executeUpdate(
3396                        "UPDATE `" + SQLManager.this.prefix + "plot_denied` SET `user_uuid` = '" + now + "' WHERE `user_uuid` = '" + old + '\'');
3397                stmt.executeUpdate(
3398                        "UPDATE `" + SQLManager.this.prefix + "plot_helpers` SET `user_uuid` = '" + now + "' WHERE `user_uuid` = '" + old + '\'');
3399                stmt.executeUpdate(
3400                        "UPDATE `" + SQLManager.this.prefix + "plot_trusted` SET `user_uuid` = '" + now + "' WHERE `user_uuid` = '" + old + '\'');
3401            } catch (SQLException e) {
3402                e.printStackTrace();
3403            }
3404        });
3405    }
3406
3407    @Override
3408    public void close() {
3409        try {
3410            this.closed = true;
3411            this.connection.close();
3412        } catch (SQLException e) {
3413            e.printStackTrace();
3414        }
3415    }
3416
3417    private record LegacySettings(
3418            int id,
3419            PlotSettings settings
3420    ) {
3421
3422    }
3423
3424    public abstract static class UniqueStatement {
3425
3426        public final String method;
3427
3428        public UniqueStatement(String method) {
3429            this.method = method;
3430        }
3431
3432        public void addBatch(PreparedStatement statement) throws SQLException {
3433            statement.addBatch();
3434        }
3435
3436        public void execute(PreparedStatement statement) throws SQLException {
3437            statement.executeBatch();
3438        }
3439
3440        public abstract PreparedStatement get() throws SQLException;
3441
3442        public abstract void set(PreparedStatement statement) throws SQLException;
3443
3444    }
3445
3446    private record UUIDPair(int id, UUID uuid) {
3447
3448    }
3449
3450}