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