javaprogram-entry-pointspigot

Where am I initializing my main class twice?


I'm receiving the error Plugin already initialized!. I already know that this means that I'm initializing my main class twice and Bukkit/Spigot doesn't like this.

I cannot seem to figure out where I'm doing this, or what portion of my code is doing this. I've, in order of operation, combed through my code in order: QLottery.java, CommandManager.java, Statistics.java, and then PlayerStats.java. From what I can tell, I am only carrying an already-initialized instance of QLottery and not initializing another.

The compiler gives no errors, and this only seems to happen when I'm running the /ql stats command. All other commands work with no issues.

Here's the relevant code:

QLottery.java (main class):

package com.quietwind01;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import com.quietwind01.Listeners.CommandManager;
import com.quietwind01.Utils.EconomyUtils;
import com.quietwind01.YAML.PlayerStats;

public class QLottery extends JavaPlugin {

    public ConcurrentHashMap<String, Integer> playerTickets;
    public double totalPool;
    public double poolDefaultAmount = getConfig().getDouble("pool-default-amount");
    public int drawInterval = getConfig().getInt("draw-interval");
    private final AtomicInteger interval;
    ConcurrentHashMap<String, String> allTickets = new ConcurrentHashMap<>();
    private int taskID = -1;
    public String chatPrefix = "§5[§a§l§nQLottery§5]§f "; // Get plugin name for chat
    private PlayerStats stats;

    public QLottery() {

        totalPool = poolDefaultAmount;
        interval = new AtomicInteger(drawInterval);

    }

    @Override
    public final void onEnable() {

        try {

            // Initialize hashmaps
            playerTickets = new ConcurrentHashMap<>();

            // Create the config file if it doesn't exist
            saveDefaultConfig();

            // Create the stats.yml file if it doesn't exist
            saveStatsYAML();

            // Send startup messages
            startMessages(chatPrefix);

            // Start DrawInterval timer
            startMainTimer(interval);

            // Register commands
            getCommand("ql").setExecutor(new CommandManager(this));

        } catch (Exception e) {

            Bukkit.getServer().broadcastMessage(chatPrefix + "§cSomething went severely wrong. Please check logs. QLottery has been disabled.");
            e.printStackTrace();
            disablePlugin();
            return;

        }

    }

}

CommandManager.java:

package com.quietwind01.Listeners;

import com.quietwind01.QLottery;

import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;

public class CommandManager implements CommandExecutor {

    private final QLottery plugin;
    boolean debugMode = false;

    public CommandManager(QLottery plugin) {

        this.plugin = plugin;

    }

    @Override
    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {

        // Check args length
        if (args.length == 0) {
            sender.sendMessage(plugin.chatPrefix + "§cUsage: /ql <subcommand>");
            return true;
        }

        // Get parent subcommand
        String parentSubcommand = args[0].toLowerCase();
        String childSubcommand = "";
        if (args.length > 1) {
            childSubcommand = args[1].toLowerCase();
        }

        if (debugMode == true) {
            Player player = (Player) sender;
            player.sendMessage(plugin.chatPrefix + "Parent subcommand: " + parentSubcommand);
            player.sendMessage(plugin.chatPrefix + "Child subcommand: " + childSubcommand);
            player.sendMessage(plugin.chatPrefix + "Args Length: " + args.length);
            player.sendMessage(plugin.chatPrefix + "Is Ticket Command: " + "ticket".equals(parentSubcommand));
            player.sendMessage(plugin.chatPrefix + "Is Pool Command: " + "pool".equals(parentSubcommand));
            player.sendMessage(plugin.chatPrefix + "Is Stats Command: " + "stats".equals(parentSubcommand));
        }

        // Ticket subcommands
        if ("ticket".equals(parentSubcommand)) {
            switch (childSubcommand) {
                case "buy":
                    return new TicketBuy(plugin).onCommand(sender, command, label, args);
                case "sell":
                    return new TicketSell(plugin).onCommand(sender, command, label, args);
                case "cost":
                    return new TicketCost(plugin).onCommand(sender, command, label, args);
                default:
                    sender.sendMessage(plugin.chatPrefix + "§cUnknown subcommand. Usage: /ql <subcommand>");
            }
        }

        // Tickets command
        if ("tickets".equals(parentSubcommand)) {
            return new TicketsOwned(plugin).onCommand(sender, command, label, args);
        }

        // Pool subcommands
        if ("pool".equals(parentSubcommand)) {
            return new TotalPool(plugin).onCommand(sender, command, label, args);
        }

        // Stats command
        if ("stats".equals(parentSubcommand)) {
            return new Statistics(plugin).onCommand(sender, command, label, args);
        }

        return true;

    }
}

Statistics.java:

package com.quietwind01.Listeners;

import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;

import com.quietwind01.QLottery;
import com.quietwind01.YAML.PlayerStats;

public class Statistics implements CommandExecutor {
    
    private final QLottery plugin;
    private final PlayerStats stats;

    public Statistics(QLottery plugin) {

        this.plugin = plugin;
        this.stats = new PlayerStats();

    }

    @Override
    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {

        if (!(sender instanceof Player)) {
            sender.sendMessage(plugin.chatPrefix + "§cOnly players can use this command.");
            return true;
        }

        Player player = (Player) sender;

        // Check that player has proper permissions
        if (!player.hasPermission("qlottery.stats")) {
            player.sendMessage(plugin.chatPrefix + "§cYou do not have permission to use this command.");
            return true;
        }

        // Debug
        //player.sendMessage("TicketBuy Args: " + args.length);

        // Check that the command has valid amount of arguments
        if (args.length < 1) {
            player.sendMessage(plugin.chatPrefix + "§eUsage: /ql stats");
            return true;
        }

        // Check if the two arguments are "pool" and "amount"
        if (!args[0].equalsIgnoreCase("stats")) {
            player.sendMessage(plugin.chatPrefix + "§eUsage: /ql stats");
            return true;
        }

        // Fetch the stats for the server
        double serverTotalPayouts = stats.getServerPayoutTotal();
        double serverTotalDraws = stats.getServerDrawsTotal();
        double serverTotalWins = stats.getServerWinsTotal();
        double lotteryTotalTickets = stats.getTotalTicketsPurchased("__LOTTERY__");
        // Player stats
        String playerName = player.getName();
        stats.createNewPlayer(playerName); // Create the player in the stats file if they don't exist yet
        double playerTotalWins = stats.getTotalWins(playerName);
        double playerTotalPayout = stats.getTotalAmountWon(playerName);
        double playerTotalTickets = stats.getTotalTicketsPurchased(playerName);

        // Send the stats to the player
        player.sendMessage(plugin.chatPrefix + "§3You have §6" + playerTotalWins + " §3wins, you've purchased §6" + playerTotalTickets + " §3tickets, and you've won a total of §a$" + playerTotalPayout + "§3!");
        player.sendMessage(plugin.chatPrefix + "§3The server has drawn §6" + serverTotalDraws + " §3times, paid out §a$" + serverTotalPayouts + " §3and there have been a total of §6" + serverTotalWins + " §3jackpots won! There have been §6" + lotteryTotalTickets + " §3total tickets sold.");

        return true;

    }

}

PlayerStats.java:

package com.quietwind01.YAML;

import java.io.File;
import java.io.IOException;

import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.YamlConfiguration;

public class PlayerStats extends JavaPlugin {

    private File file;
    private YamlConfiguration config;

    public PlayerStats() {

        try {

            // QLottery folder
            File dir = getDataFolder();

            // Create file object
            this.file = new File(dir, "stats.yml");

            // Get config
            this.config = YamlConfiguration.loadConfiguration(file);

        } catch (Exception e) {

            Bukkit.broadcastMessage("§cCouldn't fetch stats.yml file.");
            e.printStackTrace();

        }

        

    }

    public boolean createNewPlayer(String playerName) {

        try {

            if (!config.contains("player-stats." + playerName)) {
                config.set("player-stats." + playerName + ".total-wins", 0);
                config.set("player-stats." + playerName + ".total-amount-won", 0);
                config.set("player-stats." + playerName + ".total-tickets-purchased", 0);
                config.save(file);
            }

            return true;

        } catch (IOException e) {

            Bukkit.broadcastMessage("§cCouldn't create new player (§b" + playerName + "§c).");
            e.printStackTrace();
            return false;
            
        }

    }

    /* 
     * Server stats
     */

    public double getServerPayoutTotal() {
        return config.getDouble("player-stats.all-time-payout-total");
    }

    public double getServerDrawsTotal() {
        return config.getDouble("player-stats.all-time-draws-total");
    }

    public double getServerWinsTotal() {
        return config.getDouble("player-stats.all-time-wins");
    }

    public boolean updateServerPayoutTotal(double amountToAdd) {

        try {

            config.set("player-stats.all-time-payout-total", amountToAdd + getServerPayoutTotal());
            config.save(file);
            return true;

        } catch (IOException e) {

            Bukkit.broadcastMessage("§cCouldn't update total payout (server).");
            e.printStackTrace();
            return false;

        }
        
    }

    public boolean updateServerDrawsTotal(double amountToAdd) {

        try {

            config.set("player-stats.all-time-draws-total", amountToAdd + getServerDrawsTotal());
            config.save(file);
            return true;

        } catch (IOException e) {

            Bukkit.broadcastMessage("§cCouldn't update total draws (server).");
            e.printStackTrace();
            return false;
            
        }

    }

    public boolean updateServerWinsTotal(double amountToAdd) {

        try {

            config.set("player-stats.all-time-wins", amountToAdd + getServerWinsTotal());
            config.save(file);
            return true;

        } catch (IOException e) {

            Bukkit.broadcastMessage("§cCouldn't update total wins (server).");
            e.printStackTrace();
            return false;
            
        }

    }

    /*
     * Player stats
     */
    
    public double getTotalWins(String playerName) {
        return config.getDouble("player-stats." + playerName + ".total-wins");
    }

    public double getTotalAmountWon(String playerName) {
        return config.getDouble("player-stats." + playerName + ".total-amount-won");
    }

    public double getTotalTicketsPurchased(String playerName) {
        return config.getDouble("player-stats." + playerName + ".total-tickets-purchased");
    }

    public boolean updateTotalWins(String playerName, double amountToAdd) {

        try {

            config.set("player-stats." + playerName + ".total-wins", amountToAdd + getTotalWins(playerName));
            config.save(file);
            return true;

        } catch (IOException e) {

            Bukkit.broadcastMessage("§cCouldn't update total wins. (Player)");
            e.printStackTrace();
            return false;
            
        }

    }

    public boolean updateTotalAmountWon(String playerName, double amountToAdd) {

        try {

            config.set("player-stats." + playerName + ".total-amount-won", amountToAdd + getTotalAmountWon(playerName));
            config.save(file);
            return true;

        } catch (IOException e) {

            Bukkit.broadcastMessage("§cCouldn't update total amount won (player).");
            e.printStackTrace();
            return false;
            
        }

    }

    public boolean updateTotalTicketsPurchased(String playerName, double amountToAdd) {

        try {

            config.set("player-stats." + playerName + ".total-tickets-purchased", amountToAdd + getTotalTicketsPurchased(playerName));
            config.save(file);
            return true;

        } catch (IOException e) {

            Bukkit.broadcastMessage("§cCouldn't update total tickets purchased (player).");
            e.printStackTrace();
            return false;
            
        }

    }

}

Here's the console stack trace:

org.bukkit.command.CommandException: Unhandled exception executing command 'ql' in plugin Qlottery v1.0.0-RC.3
    at org.bukkit.command.PluginCommand.execute(PluginCommand.java:47) ~[spigot-api-1.20.4-R0.1-SNAPSHOT.jar:?]
    at org.bukkit.command.SimpleCommandMap.dispatch(SimpleCommandMap.java:149) ~[spigot-api-1.20.4-R0.1-SNAPSHOT.jar:?]
    at org.bukkit.craftbukkit.v1_20_R3.CraftServer.dispatchCommand(CraftServer.java:887) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
    at org.bukkit.craftbukkit.v1_20_R3.command.BukkitCommandWrapper.run(BukkitCommandWrapper.java:50) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
    at com.mojang.brigadier.context.ContextChain.runExecutable(ContextChain.java:73) ~[brigadier-1.2.9.jar:?]
    at net.minecraft.commands.execution.tasks.ExecuteCommand.a(SourceFile:29) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
    at net.minecraft.commands.execution.tasks.ExecuteCommand.execute(SourceFile:13) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
    at net.minecraft.commands.execution.UnboundEntryAction.a(SourceFile:8) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
    at net.minecraft.commands.execution.CommandQueueEntry.a(SourceFile:8) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
    at net.minecraft.commands.execution.ExecutionContext.a(SourceFile:107) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
    at net.minecraft.commands.CommandDispatcher.a(CommandDispatcher.java:413) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
    at net.minecraft.commands.CommandDispatcher.performCommand(CommandDispatcher.java:335) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
    at net.minecraft.commands.CommandDispatcher.a(CommandDispatcher.java:322) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
    at net.minecraft.server.network.PlayerConnection.a(PlayerConnection.java:1856) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
    at net.minecraft.server.network.PlayerConnection.lambda$15(PlayerConnection.java:1818) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
    at net.minecraft.util.thread.IAsyncTaskHandler.b(SourceFile:67) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
    at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1768) ~[?:?]
    at net.minecraft.server.TickTask.run(SourceFile:18) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
    at net.minecraft.util.thread.IAsyncTaskHandler.d(SourceFile:156) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
    at net.minecraft.util.thread.IAsyncTaskHandlerReentrant.d(SourceFile:23) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
    at net.minecraft.server.MinecraftServer.b(MinecraftServer.java:1191) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
    at net.minecraft.server.MinecraftServer.d(MinecraftServer.java:1) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
    at net.minecraft.util.thread.IAsyncTaskHandler.x(SourceFile:130) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
    at net.minecraft.server.MinecraftServer.bl(MinecraftServer.java:1170) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
    at net.minecraft.server.MinecraftServer.x(MinecraftServer.java:1163) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
    at net.minecraft.util.thread.IAsyncTaskHandler.c(SourceFile:139) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
    at net.minecraft.server.MinecraftServer.w_(MinecraftServer.java:1147) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
    at net.minecraft.server.MinecraftServer.w(MinecraftServer.java:1060) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
    at net.minecraft.server.MinecraftServer.lambda$0(MinecraftServer.java:304) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
    at java.lang.Thread.run(Thread.java:1570) ~[?:?]
Caused by: java.lang.IllegalArgumentException: Plugin already initialized!
    at org.bukkit.plugin.java.PluginClassLoader.initialize(PluginClassLoader.java:238) ~[spigot-api-1.20.4-R0.1-SNAPSHOT.jar:?]
    at org.bukkit.plugin.java.JavaPlugin.<init>(JavaPlugin.java:55) ~[spigot-api-1.20.4-R0.1-SNAPSHOT.jar:?]
    at com.quietwind01.YAML.PlayerStats.<init>(PlayerStats.java:16) ~[?:?]
    at com.quietwind01.Listeners.Statistics.<init>(Statistics.java:14) ~[?:?]
    at com.quietwind01.Listeners.CommandManager.onCommand(CommandManager.java:74) ~[?:?]
    at org.bukkit.command.PluginCommand.execute(PluginCommand.java:45) ~[spigot-api-1.20.4-R0.1-SNAPSHOT.jar:?]
    ... 29 more
Caused by: java.lang.IllegalStateException: Initial initialization
    at org.bukkit.plugin.java.PluginClassLoader.initialize(PluginClassLoader.java:241) ~[spigot-api-1.20.4-R0.1-SNAPSHOT.jar:?]
    at org.bukkit.plugin.java.JavaPlugin.<init>(JavaPlugin.java:55) ~[spigot-api-1.20.4-R0.1-SNAPSHOT.jar:?]
    at com.quietwind01.QLottery.<init>(QLottery.java:33) ~[?:?]
    at jdk.internal.reflect.DirectConstructorHandleAccessor.newInstance(DirectConstructorHandleAccessor.java:62) ~[?:?]
    at java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:502) ~[?:?]
    at java.lang.reflect.Constructor.newInstance(Constructor.java:486) ~[?:?]
    at org.bukkit.plugin.java.PluginClassLoader.<init>(PluginClassLoader.java:88) ~[spigot-api-1.20.4-R0.1-SNAPSHOT.jar:?]
    at org.bukkit.plugin.java.JavaPluginLoader.loadPlugin(JavaPluginLoader.java:145) ~[spigot-api-1.20.4-R0.1-SNAPSHOT.jar:?]
    at org.bukkit.plugin.SimplePluginManager.loadPlugin(SimplePluginManager.java:405) ~[spigot-api-1.20.4-R0.1-SNAPSHOT.jar:?]
    at org.bukkit.plugin.SimplePluginManager.loadPlugins(SimplePluginManager.java:312) ~[spigot-api-1.20.4-R0.1-SNAPSHOT.jar:?]
    at org.bukkit.plugin.SimplePluginManager.loadPlugins(SimplePluginManager.java:121) ~[spigot-api-1.20.4-R0.1-SNAPSHOT.jar:?]
    at org.bukkit.craftbukkit.v1_20_R3.CraftServer.loadPlugins(CraftServer.java:430) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
    at net.minecraft.server.dedicated.DedicatedServer.e(DedicatedServer.java:223) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
    at net.minecraft.server.MinecraftServer.w(MinecraftServer.java:1000) ~[spigot-1.20.4-R0.1-SNAPSHOT.jar:4090-Spigot-b754dcc-38b1f49]
    ... 2 more

What portion of the above code would cause this? From what I can tell I don't seem to be initializing QLottery twice...


Solution

  • The plugin loader loads your plugin by creating an instance of QLottery, which calls the constructor of JavaPlugin, which initialises your plugin.

    You then create an instance of PlayerStats in Statistics:

    public Statistics(QLottery plugin) {
        this.plugin = plugin;
        this.stats = new PlayerStats(); // here!
    }
    

    This causes the constructor of JavaPlugin to be called again, and this is where the plugin class loader detects that your plugin has already been initialised and throws the exception you got.

    You should not have multiple JavaPlugin subclasses in the same plugin. It seems like PlayerStats should just be a regular class. Take the data folder directory as a constructor parameter

    public class PlayerStats {
    
        private File file;
        private YamlConfiguration config;
    
        public PlayerStats(File dataDir) {
            try {
                this.file = new File(dataDir, "stats.yml");
                // ...
    

    and pass the parameter like this:

    public Statistics(QLottery plugin) {
        this.plugin = plugin;
        this.stats = new PlayerStats(plugin.getDataFolder());
    }
    

    If you need to use other methods from JavaPlugin, consider passing the entire QLottery instance, in the same way you passed it to Statistics.


    If you actually want to create multiple plugins, you should have multiple plugin.yml files identifying each one, and they should be outputted as separated jars.