From e2c84251dc52a433dd927bcab676f301ecf82984 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 25 Oct 2025 06:27:12 +0000 Subject: [PATCH 1/8] Initial plan From 43ad1a2f6fa2d554206d3dbd4b9fffe7942cd591 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 25 Oct 2025 06:31:14 +0000 Subject: [PATCH 2/8] Fix data migration not executing when data_version is outdated Co-authored-by: ptthanh02 <73684260+ptthanh02@users.noreply.github.com> --- .../migration/SpawnerDataMigration.java | 39 +++++++++++++------ 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/github/nighter/smartspawner/migration/SpawnerDataMigration.java b/core/src/main/java/github/nighter/smartspawner/migration/SpawnerDataMigration.java index 478b6a36..c3b8cc52 100644 --- a/core/src/main/java/github/nighter/smartspawner/migration/SpawnerDataMigration.java +++ b/core/src/main/java/github/nighter/smartspawner/migration/SpawnerDataMigration.java @@ -38,28 +38,45 @@ public boolean checkAndMigrateData() { try { needsMigration = false; - // Check if data_version exists - if (!config.contains(MIGRATION_FLAG)) { - config.set(MIGRATION_FLAG, CURRENT_VERSION); - try { - config.save(dataFile); - } catch (IOException e) { - plugin.getLogger().warning("Could not save data_version flag: " + e.getMessage()); - } + // Check if data_version exists and compare with current version + int dataVersion = config.getInt(MIGRATION_FLAG, 0); + + if (dataVersion == 0) { + // data_version doesn't exist, check the structure to determine if migration is needed + plugin.getLogger().info("No data_version found. Checking data structure..."); + needsMigration = true; + } else if (dataVersion < CURRENT_VERSION) { + // data_version exists but is outdated + plugin.getLogger().info("Data version " + dataVersion + " is outdated. Current version is " + CURRENT_VERSION + "."); + needsMigration = true; } - // Validate the spawners section - if (config.contains("spawners")) { + // If version check indicates migration is needed, validate by checking structure + if (needsMigration && config.contains("spawners")) { + // Double-check by validating the spawners section structure + boolean hasNewFormat = true; for (String spawnerId : config.getConfigurationSection("spawners").getKeys(false)) { String spawnerPath = "spawners." + spawnerId; // Check if the spawner data is in the new format if (!config.contains(spawnerPath + ".location") || !config.contains(spawnerPath + ".settings") || !config.contains(spawnerPath + ".inventory")) { - needsMigration = true; + hasNewFormat = false; break; } } + + // If structure is already in new format, just update version + if (hasNewFormat) { + plugin.getLogger().info("Data structure is already in current format. Updating version flag..."); + config.set(MIGRATION_FLAG, CURRENT_VERSION); + try { + config.save(dataFile); + } catch (IOException e) { + plugin.getLogger().warning("Could not save data_version flag: " + e.getMessage()); + } + needsMigration = false; + } } if (!needsMigration) { From f10dcd63184e99e72c23bf24a63459009b614094 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 25 Oct 2025 06:32:52 +0000 Subject: [PATCH 3/8] Add null check to prevent NullPointerException Co-authored-by: ptthanh02 <73684260+ptthanh02@users.noreply.github.com> --- .../migration/SpawnerDataMigration.java | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/github/nighter/smartspawner/migration/SpawnerDataMigration.java b/core/src/main/java/github/nighter/smartspawner/migration/SpawnerDataMigration.java index c3b8cc52..4ffb94c0 100644 --- a/core/src/main/java/github/nighter/smartspawner/migration/SpawnerDataMigration.java +++ b/core/src/main/java/github/nighter/smartspawner/migration/SpawnerDataMigration.java @@ -55,14 +55,17 @@ public boolean checkAndMigrateData() { if (needsMigration && config.contains("spawners")) { // Double-check by validating the spawners section structure boolean hasNewFormat = true; - for (String spawnerId : config.getConfigurationSection("spawners").getKeys(false)) { - String spawnerPath = "spawners." + spawnerId; - // Check if the spawner data is in the new format - if (!config.contains(spawnerPath + ".location") || - !config.contains(spawnerPath + ".settings") || - !config.contains(spawnerPath + ".inventory")) { - hasNewFormat = false; - break; + var spawnersSection = config.getConfigurationSection("spawners"); + if (spawnersSection != null) { + for (String spawnerId : spawnersSection.getKeys(false)) { + String spawnerPath = "spawners." + spawnerId; + // Check if the spawner data is in the new format + if (!config.contains(spawnerPath + ".location") || + !config.contains(spawnerPath + ".settings") || + !config.contains(spawnerPath + ".inventory")) { + hasNewFormat = false; + break; + } } } From 1558ddee00c2373606768473f1c02ff22ad11b1f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 25 Oct 2025 06:33:59 +0000 Subject: [PATCH 4/8] Replace var with explicit type for better readability Co-authored-by: ptthanh02 <73684260+ptthanh02@users.noreply.github.com> --- .../nighter/smartspawner/migration/SpawnerDataMigration.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/github/nighter/smartspawner/migration/SpawnerDataMigration.java b/core/src/main/java/github/nighter/smartspawner/migration/SpawnerDataMigration.java index 4ffb94c0..04cd2016 100644 --- a/core/src/main/java/github/nighter/smartspawner/migration/SpawnerDataMigration.java +++ b/core/src/main/java/github/nighter/smartspawner/migration/SpawnerDataMigration.java @@ -1,6 +1,7 @@ package github.nighter.smartspawner.migration; import github.nighter.smartspawner.SmartSpawner; +import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; @@ -55,7 +56,7 @@ public boolean checkAndMigrateData() { if (needsMigration && config.contains("spawners")) { // Double-check by validating the spawners section structure boolean hasNewFormat = true; - var spawnersSection = config.getConfigurationSection("spawners"); + ConfigurationSection spawnersSection = config.getConfigurationSection("spawners"); if (spawnersSection != null) { for (String spawnerId : spawnersSection.getKeys(false)) { String spawnerPath = "spawners." + spawnerId; From 2b7c3b95577bb1d4ed02d9a28773d558567b6807 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 25 Oct 2025 06:56:17 +0000 Subject: [PATCH 5/8] Fix version 2 to version 3 migration to handle settings field differences Co-authored-by: ptthanh02 <73684260+ptthanh02@users.noreply.github.com> --- .../migration/SpawnerDataMigration.java | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/core/src/main/java/github/nighter/smartspawner/migration/SpawnerDataMigration.java b/core/src/main/java/github/nighter/smartspawner/migration/SpawnerDataMigration.java index 04cd2016..76528b28 100644 --- a/core/src/main/java/github/nighter/smartspawner/migration/SpawnerDataMigration.java +++ b/core/src/main/java/github/nighter/smartspawner/migration/SpawnerDataMigration.java @@ -67,6 +67,17 @@ public boolean checkAndMigrateData() { hasNewFormat = false; break; } + + // Also check if settings string has the correct number of fields for version 3 + String settingsString = config.getString(spawnerPath + ".settings"); + if (settingsString != null) { + String[] settings = settingsString.split(","); + // Version 3 requires 13 fields (including maxStackSize and isAtCapacity) + if (settings.length < 13) { + hasNewFormat = false; + break; + } + } } } @@ -147,6 +158,15 @@ private void restoreFromBackup(File dataFile) { private boolean migrateData(FileConfiguration oldConfig, File dataFile) { try { + int oldVersion = oldConfig.getInt(MIGRATION_FLAG, 1); + + // Check if this is a version 2 to version 3 migration + if (oldVersion == 2 && oldConfig.contains("spawners")) { + plugin.getLogger().info("Migrating from version 2 to version 3..."); + return migrateVersion2ToVersion3(oldConfig, dataFile); + } + + // Otherwise, handle old format to new format migration // Create new data file FileConfiguration newConfig = new YamlConfiguration(); @@ -167,4 +187,68 @@ private boolean migrateData(FileConfiguration oldConfig, File dataFile) { return false; } } + + private boolean migrateVersion2ToVersion3(FileConfiguration config, File dataFile) { + try { + ConfigurationSection spawnersSection = config.getConfigurationSection("spawners"); + if (spawnersSection == null) { + plugin.getLogger().warning("No spawners section found in version 2 data"); + return false; + } + + int migratedCount = 0; + for (String spawnerId : spawnersSection.getKeys(false)) { + String settingsPath = "spawners." + spawnerId + ".settings"; + String settingsString = config.getString(settingsPath); + + if (settingsString != null) { + String[] settings = settingsString.split(","); + + // Version 2 has 11 fields, version 3 has 13 fields + if (settings.length == 11) { + // Migrate version 2 (11 fields) to version 3 (13 fields) + // Version 2: exp,active,range,stop,delay,maxLoot,maxExp,minMobs,maxMobs,stack,lastSpawnTime + // Version 3: exp,active,range,stop,delay,maxLoot,maxExp,minMobs,maxMobs,stack,maxStack,lastSpawnTime,isAtCapacity + + // Get the default maxStackSize from config + int defaultMaxStackSize = plugin.getConfig().getInt("spawner.max_stack_size", 10); + + // Build new settings string with maxStackSize inserted at position 10 + String newSettings = String.format("%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%d,%s,%b", + settings[0], // spawnerExp + settings[1], // spawnerActive + settings[2], // spawnerRange + settings[3], // spawnerStop + settings[4], // spawnDelay + settings[5], // maxSpawnerLootSlots + settings[6], // maxStoredExp + settings[7], // minMobs + settings[8], // maxMobs + settings[9], // stackSize + defaultMaxStackSize, // maxStackSize (NEW) + settings[10], // lastSpawnTime + false // isAtCapacity (NEW) + ); + + config.set(settingsPath, newSettings); + migratedCount++; + } + } + } + + // Update version flag + config.set(MIGRATION_FLAG, CURRENT_VERSION); + + // Save the updated config + config.save(dataFile); + + plugin.getLogger().info("Successfully migrated " + migratedCount + " spawners from version 2 to version 3"); + return true; + + } catch (Exception e) { + plugin.getLogger().severe("Failed to migrate from version 2 to version 3: " + e.getMessage()); + e.printStackTrace(); + return false; + } + } } \ No newline at end of file From 0625eaa5c0bc8b4277d67f86145b22abaa63b915 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 25 Oct 2025 06:58:26 +0000 Subject: [PATCH 6/8] Address code review feedback: extract constants and improve string building Co-authored-by: ptthanh02 <73684260+ptthanh02@users.noreply.github.com> --- .../migration/SpawnerDataMigration.java | 38 +++++++++---------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/core/src/main/java/github/nighter/smartspawner/migration/SpawnerDataMigration.java b/core/src/main/java/github/nighter/smartspawner/migration/SpawnerDataMigration.java index 76528b28..db9166f5 100644 --- a/core/src/main/java/github/nighter/smartspawner/migration/SpawnerDataMigration.java +++ b/core/src/main/java/github/nighter/smartspawner/migration/SpawnerDataMigration.java @@ -16,6 +16,9 @@ public class SpawnerDataMigration { private static final String DATA_FILE = "spawners_data.yml"; private static final String BACKUP_FILE = "spawners_data_backup.yml"; private static final String MIGRATION_FLAG = "data_version"; + private static final int DEFAULT_MAX_STACK_SIZE = 10; + private static final int VERSION_3_SETTINGS_FIELD_COUNT = 13; + private static final int VERSION_2_SETTINGS_FIELD_COUNT = 11; private final int CURRENT_VERSION; public SpawnerDataMigration(SmartSpawner plugin) { @@ -73,7 +76,7 @@ public boolean checkAndMigrateData() { if (settingsString != null) { String[] settings = settingsString.split(","); // Version 3 requires 13 fields (including maxStackSize and isAtCapacity) - if (settings.length < 13) { + if (settings.length < VERSION_3_SETTINGS_FIELD_COUNT) { hasNewFormat = false; break; } @@ -158,7 +161,7 @@ private void restoreFromBackup(File dataFile) { private boolean migrateData(FileConfiguration oldConfig, File dataFile) { try { - int oldVersion = oldConfig.getInt(MIGRATION_FLAG, 1); + int oldVersion = oldConfig.getInt(MIGRATION_FLAG, 0); // Check if this is a version 2 to version 3 migration if (oldVersion == 2 && oldConfig.contains("spawners")) { @@ -205,32 +208,25 @@ private boolean migrateVersion2ToVersion3(FileConfiguration config, File dataFil String[] settings = settingsString.split(","); // Version 2 has 11 fields, version 3 has 13 fields - if (settings.length == 11) { + if (settings.length == VERSION_2_SETTINGS_FIELD_COUNT) { // Migrate version 2 (11 fields) to version 3 (13 fields) // Version 2: exp,active,range,stop,delay,maxLoot,maxExp,minMobs,maxMobs,stack,lastSpawnTime // Version 3: exp,active,range,stop,delay,maxLoot,maxExp,minMobs,maxMobs,stack,maxStack,lastSpawnTime,isAtCapacity - // Get the default maxStackSize from config - int defaultMaxStackSize = plugin.getConfig().getInt("spawner.max_stack_size", 10); + // Get the default maxStackSize from config or use constant + int defaultMaxStackSize = plugin.getConfig().getInt("spawner.max_stack_size", DEFAULT_MAX_STACK_SIZE); // Build new settings string with maxStackSize inserted at position 10 - String newSettings = String.format("%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%d,%s,%b", - settings[0], // spawnerExp - settings[1], // spawnerActive - settings[2], // spawnerRange - settings[3], // spawnerStop - settings[4], // spawnDelay - settings[5], // maxSpawnerLootSlots - settings[6], // maxStoredExp - settings[7], // minMobs - settings[8], // maxMobs - settings[9], // stackSize - defaultMaxStackSize, // maxStackSize (NEW) - settings[10], // lastSpawnTime - false // isAtCapacity (NEW) - ); + StringBuilder newSettingsBuilder = new StringBuilder(); + for (int i = 0; i < 10; i++) { + if (i > 0) newSettingsBuilder.append(","); + newSettingsBuilder.append(settings[i]); + } + newSettingsBuilder.append(",").append(defaultMaxStackSize); // maxStackSize (NEW) + newSettingsBuilder.append(",").append(settings[10]); // lastSpawnTime + newSettingsBuilder.append(",").append(false); // isAtCapacity (NEW) - config.set(settingsPath, newSettings); + config.set(settingsPath, newSettingsBuilder.toString()); migratedCount++; } } From cacd29a487233f9aac123d5853f192d17be351a5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 25 Oct 2025 07:00:00 +0000 Subject: [PATCH 7/8] Add explanatory comments and extract loop bound to constant Co-authored-by: ptthanh02 <73684260+ptthanh02@users.noreply.github.com> --- .../smartspawner/migration/SpawnerDataMigration.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/github/nighter/smartspawner/migration/SpawnerDataMigration.java b/core/src/main/java/github/nighter/smartspawner/migration/SpawnerDataMigration.java index db9166f5..9e38f188 100644 --- a/core/src/main/java/github/nighter/smartspawner/migration/SpawnerDataMigration.java +++ b/core/src/main/java/github/nighter/smartspawner/migration/SpawnerDataMigration.java @@ -19,6 +19,8 @@ public class SpawnerDataMigration { private static final int DEFAULT_MAX_STACK_SIZE = 10; private static final int VERSION_3_SETTINGS_FIELD_COUNT = 13; private static final int VERSION_2_SETTINGS_FIELD_COUNT = 11; + // Version 2 has first 10 fields before maxStackSize is inserted + private static final int VERSION_2_FIELDS_BEFORE_MAX_STACK = 10; private final int CURRENT_VERSION; public SpawnerDataMigration(SmartSpawner plugin) { @@ -161,6 +163,8 @@ private void restoreFromBackup(File dataFile) { private boolean migrateData(FileConfiguration oldConfig, File dataFile) { try { + // Default to 0 to indicate no version set (older than version 1) + // This ensures all unversioned data goes through migration int oldVersion = oldConfig.getInt(MIGRATION_FLAG, 0); // Check if this is a version 2 to version 3 migration @@ -218,7 +222,7 @@ private boolean migrateVersion2ToVersion3(FileConfiguration config, File dataFil // Build new settings string with maxStackSize inserted at position 10 StringBuilder newSettingsBuilder = new StringBuilder(); - for (int i = 0; i < 10; i++) { + for (int i = 0; i < VERSION_2_FIELDS_BEFORE_MAX_STACK; i++) { if (i > 0) newSettingsBuilder.append(","); newSettingsBuilder.append(settings[i]); } From d9c13e7afe84a175986dd8d5f113c1c5f0fce6c9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 25 Oct 2025 07:29:25 +0000 Subject: [PATCH 8/8] Fix version 2 migration to handle both 11 and 12 field formats Co-authored-by: ptthanh02 <73684260+ptthanh02@users.noreply.github.com> --- .../migration/SpawnerDataMigration.java | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/github/nighter/smartspawner/migration/SpawnerDataMigration.java b/core/src/main/java/github/nighter/smartspawner/migration/SpawnerDataMigration.java index 9e38f188..39272548 100644 --- a/core/src/main/java/github/nighter/smartspawner/migration/SpawnerDataMigration.java +++ b/core/src/main/java/github/nighter/smartspawner/migration/SpawnerDataMigration.java @@ -18,7 +18,9 @@ public class SpawnerDataMigration { private static final String MIGRATION_FLAG = "data_version"; private static final int DEFAULT_MAX_STACK_SIZE = 10; private static final int VERSION_3_SETTINGS_FIELD_COUNT = 13; - private static final int VERSION_2_SETTINGS_FIELD_COUNT = 11; + // Version 2 can have 11 or 12 fields (12 if allowEquipmentItems was included) + private static final int VERSION_2_MIN_SETTINGS_FIELD_COUNT = 11; + private static final int VERSION_2_MAX_SETTINGS_FIELD_COUNT = 12; // Version 2 has first 10 fields before maxStackSize is inserted private static final int VERSION_2_FIELDS_BEFORE_MAX_STACK = 10; private final int CURRENT_VERSION; @@ -211,11 +213,13 @@ private boolean migrateVersion2ToVersion3(FileConfiguration config, File dataFil if (settingsString != null) { String[] settings = settingsString.split(","); - // Version 2 has 11 fields, version 3 has 13 fields - if (settings.length == VERSION_2_SETTINGS_FIELD_COUNT) { - // Migrate version 2 (11 fields) to version 3 (13 fields) - // Version 2: exp,active,range,stop,delay,maxLoot,maxExp,minMobs,maxMobs,stack,lastSpawnTime - // Version 3: exp,active,range,stop,delay,maxLoot,maxExp,minMobs,maxMobs,stack,maxStack,lastSpawnTime,isAtCapacity + // Version 2 can have 11 or 12 fields (12 if allowEquipmentItems was included) + if (settings.length >= VERSION_2_MIN_SETTINGS_FIELD_COUNT && + settings.length <= VERSION_2_MAX_SETTINGS_FIELD_COUNT) { + // Migrate version 2 (11-12 fields) to version 3 (13 fields) + // Version 2 (11): exp,active,range,stop,delay,maxLoot,maxExp,minMobs,maxMobs,stack,lastSpawnTime + // Version 2 (12): exp,active,range,stop,delay,maxLoot,maxExp,minMobs,maxMobs,stack,lastSpawnTime,allowEquipmentItems + // Version 3 (13): exp,active,range,stop,delay,maxLoot,maxExp,minMobs,maxMobs,stack,maxStack,lastSpawnTime,isAtCapacity // Get the default maxStackSize from config or use constant int defaultMaxStackSize = plugin.getConfig().getInt("spawner.max_stack_size", DEFAULT_MAX_STACK_SIZE); @@ -226,9 +230,11 @@ private boolean migrateVersion2ToVersion3(FileConfiguration config, File dataFil if (i > 0) newSettingsBuilder.append(","); newSettingsBuilder.append(settings[i]); } - newSettingsBuilder.append(",").append(defaultMaxStackSize); // maxStackSize (NEW) - newSettingsBuilder.append(",").append(settings[10]); // lastSpawnTime - newSettingsBuilder.append(",").append(false); // isAtCapacity (NEW) + newSettingsBuilder.append(",").append(defaultMaxStackSize); // maxStackSize (NEW at position 10) + newSettingsBuilder.append(",").append(settings[10]); // lastSpawnTime (moved from 10 to 11) + // For isAtCapacity, use allowEquipmentItems if it exists (position 11 in 12-field version), otherwise false + boolean isAtCapacity = settings.length == 12 ? Boolean.parseBoolean(settings[11]) : false; + newSettingsBuilder.append(",").append(isAtCapacity); // isAtCapacity (NEW at position 12) config.set(settingsPath, newSettingsBuilder.toString()); migratedCount++;