diff --git a/megamek/build.gradle b/megamek/build.gradle index bbb8e64ecf5..555cf547851 100644 --- a/megamek/build.gradle +++ b/megamek/build.gradle @@ -366,6 +366,13 @@ task filteredUnitListTool(type: JavaExec, dependsOn: jar) { mainClass = 'megamek.utilities.FilteredUnitListTool' } +task entityVerifier(type: JavaExec, dependsOn: jar) { + description = 'Entity Verifier' + group = 'utility' + classpath = sourceSets.main.runtimeClasspath + mainClass = 'megamek.common.verifier.EntityVerifier' +} + task iconTest(type: JavaExec, dependsOn: jar) { description = 'Icon Tool' group = 'utility' diff --git a/megamek/src/megamek/common/verifier/EntityVerifier.java b/megamek/src/megamek/common/verifier/EntityVerifier.java index baa30edbfb8..7889dec21c4 100755 --- a/megamek/src/megamek/common/verifier/EntityVerifier.java +++ b/megamek/src/megamek/common/verifier/EntityVerifier.java @@ -3,28 +3,25 @@ * Copyright (C) 2000-2005 Ben Mazur (bmazur@sev.org) * Copyright © 2013 Edward Cullen (eddy@obsessedcomputers.co.uk) * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation; either version 2 of the License, or (at your option) - * any later version. + * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * for more details. + * This file is part of MegaMek. + * + * MegaMek is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MegaMek is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MegaMek. If not, see . */ package megamek.common.verifier; -import jakarta.xml.bind.JAXBContext; -import jakarta.xml.bind.Unmarshaller; -import jakarta.xml.bind.annotation.XmlAccessType; -import jakarta.xml.bind.annotation.XmlAccessorType; -import jakarta.xml.bind.annotation.XmlElement; -import jakarta.xml.bind.annotation.XmlRootElement; -import megamek.common.*; -import megamek.utilities.xml.MMXMLUtility; -import org.apache.logging.log4j.LogManager; - import java.io.File; import java.io.FileInputStream; import java.io.InputStream; @@ -33,6 +30,23 @@ import java.util.Locale; import java.util.Map; +import org.apache.logging.log4j.LogManager; + +import jakarta.xml.bind.JAXBContext; +import jakarta.xml.bind.Unmarshaller; +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlElement; +import jakarta.xml.bind.annotation.XmlRootElement; +import megamek.common.Configuration; +import megamek.common.Entity; +import megamek.common.MechFileParser; +import megamek.common.MechSummary; +import megamek.common.MechSummaryCache; +import megamek.common.UnitType; +import megamek.logging.MMLogger; +import megamek.utilities.xml.MMXMLUtility; + /** * Performs verification of the validity of different types of * Entity subclasses. Most of the actual validation is performed @@ -45,18 +59,27 @@ public class EntityVerifier implements MechSummaryCache.Listener { public static final String CONFIG_FILENAME = "UnitVerifierOptions.xml"; + private static EntityVerifier instance = null; + private static MechSummaryCache mechSummaryCache = null; + private static final MMLogger logger = MMLogger.create(EntityVerifier.class); + @XmlElement(name = "mech") public TestXMLOption mechOption = new TestXMLOption(); + @XmlElement(name = "protomech") public TestXMLOption protomechOption = new TestXMLOption(); + @XmlElement(name = "tank") public TestXMLOption tankOption = new TestXMLOption(); + @XmlElement(name = "aero") public TestXMLOption aeroOption = new TestXMLOption(); + @XmlElement(name = "ba") public TestXMLOption baOption = new TestXMLOption(); + @XmlElement(name = "infantry") public TestXMLOption infOption = new TestXMLOption(); @@ -72,73 +95,84 @@ private EntityVerifier() { /** * Creates and return a new instance of EntityVerifier. * - * @param config a File that contains an XML representation of the configuration settings + * @param config a File that contains an XML representation of the configuration + * settings * @return an EntityVerifier with the configuration loaded from XML */ public static EntityVerifier getInstance(final File config) { - EntityVerifier ev; + if (instance != null) { + return instance; + } + + instance = new EntityVerifier(); try { JAXBContext jc = JAXBContext.newInstance(EntityVerifier.class); Unmarshaller um = jc.createUnmarshaller(); InputStream is = new FileInputStream(config); - ev = (EntityVerifier) um.unmarshal(MMXMLUtility.createSafeXmlSource(is)); + instance = (EntityVerifier) um.unmarshal(MMXMLUtility.createSafeXmlSource(is)); } catch (Exception e) { - LogManager.getLogger().error("Error loading XML for entity verifier: " + e.getMessage(), e); - - ev = new EntityVerifier(); + String message = String.format("Error loading XML for entity verifier: %s", e.getMessage()); + logger.error(e, message); } - return ev; + return instance; } public boolean checkEntity(Entity entity, String fileString, boolean verbose) { return checkEntity(entity, fileString, verbose, entity.getTechLevel()); } - public boolean checkEntity(Entity entity, String fileString, - boolean verbose, int ammoTechLvl) { + public boolean checkEntity(Entity entity, String fileString, boolean verbose, int ammoTechLvl) { return checkEntity(entity, fileString, verbose, ammoTechLvl, false); } - public boolean checkEntity(Entity entity, String fileString, - boolean verbose, int ammoTechLvl, boolean failsOnly) { - final NumberFormat FMT = NumberFormat.getNumberInstance(Locale.getDefault()); + public boolean checkEntity(Entity entity, String fileString, boolean verbose, int ammoTechLvl, boolean failsOnly) { + final NumberFormat numberFormat = NumberFormat.getNumberInstance(Locale.getDefault()); boolean retVal = false; - TestEntity testEntity = TestEntity.getEntityVerifier(entity); + TestEntity testEntity = TestEntity.getEntityVerifier(entity); + String message = ""; if (testEntity == null) { - System.err.println("UnknownType: " + entity.getDisplayName()); - System.err.println("Found in: " + fileString); + message = String.format("Unknown Type: %s%nFound in: %s", entity.getDisplayName(), fileString); + logger.error(message); return false; } if (verbose) { StringBuffer buff = new StringBuffer(); boolean valid = testEntity.correctEntity(buff, ammoTechLvl); + if (!valid || !failsOnly) { if (valid) { - System.out.println("---Entity is valid---"); + logger.info("---Entity is valid---"); } else { - System.out.println("---Entity INVALID---"); + logger.info("---Entity INVALID---"); } - System.out.print(testEntity.printEntity()); - System.out.println("BV: " + entity.calculateBattleValue() - + " Cost: " + FMT.format(entity.getCost(false))); + message = String.format("%s%nBV: %s Cost: %s", testEntity.printEntity(), + entity.calculateBattleValue(), numberFormat.format(entity.getCost(false))); + logger.info(message); } } else { StringBuffer buff = new StringBuffer(); if (testEntity.correctEntity(buff, ammoTechLvl)) { retVal = true; } else { - System.out.println(testEntity.getName()); - System.out.println("Found in: " + testEntity.fileString); - System.out.println("Intro year: " + entity.getYear()); - System.out.println("BV: " + entity.calculateBattleValue() - + " Cost: " + FMT.format(entity.getCost(false))); - System.out.println(buff); - + message = String.format(""" + %s + Found in: %s + Intro year: %d + BV: %d Cost: %s + %s + """, + testEntity.getName(), + testEntity.fileString, + entity.getYear(), + entity.calculateBattleValue(), + numberFormat.format(entity.getCost(false)), + buff.toString()); + logger.info(message); } } @@ -147,39 +181,45 @@ public boolean checkEntity(Entity entity, String fileString, public Entity loadEntity(File f, String entityName) { Entity entity = null; + try { entity = new MechFileParser(f, entityName).getEntity(); } catch (Exception ex) { - LogManager.getLogger().error("", ex); + logger.error(ex, "Unable to load entity."); } + return entity; } - // This is the listener method that MechSummaryCache calls when it - // finishes loading all the mechs. This should only happen if no - // specific files were passed to main() as arguments (which implies - // all units that are loaded when MegaMek normally runs should be - // checked). + // This is the listener method that MechSummaryCache calls when it finishes + // loading all the mechs. This should only happen if no specific files were + // passed to main() as arguments (which implies all units that are loaded when + // MegaMek normally runs should be checked). @Override public void doneLoading() { + String message = ""; MechSummary[] ms = mechSummaryCache.getAllMechs(); - System.out.println("\n"); - - System.out.println("Mech Options:"); - System.out.println(mechOption.printOptions()); - System.out.println("Protomech Options:"); - System.out.println(protomechOption.printOptions()); - System.out.println("\nTank Options:"); - System.out.println(tankOption.printOptions()); - System.out.println("\nAero Options:"); - System.out.println(aeroOption.printOptions()); - System.out.println("\nBattleArmor Options:"); - System.out.println(baOption.printOptions()); - System.out.println("\nInfantry Options:"); - System.out.println(infOption.printOptions()); + + message = String.format(""" + + Mech Options: %s + Protomech Options: %s + Tank Options: %s + Aero Options: %s + BattleArmor Options: %s + Infantry Options: %s + """, + mechOption.printOptions(), + protomechOption.printOptions(), + tankOption.printOptions(), + aeroOption.printOptions(), + baOption.printOptions(), + infOption.printOptions()); + logger.info(message); int failures = 0; Map failedByType = new HashMap<>(); + for (int i = 0; i < ms.length; i++) { int unitType = UnitType.determineUnitTypeCode(ms[i].getUnitType()); if (unitType != UnitType.GUN_EMPLACEMENT) { @@ -187,29 +227,50 @@ public void doneLoading() { if (entity == null) { continue; } - if (!checkEntity(entity, ms[i].getSourceFile().toString(), - loadingVerbosity, entity.getTechLevel(), failsOnly)) { + + if (!checkEntity(entity, ms[i].getSourceFile().toString(), loadingVerbosity, entity.getTechLevel(), + failsOnly)) { failures++; failedByType.merge(unitType, 1, Integer::sum); } } } - System.out.println("Total Failures: " + failures); - System.out.println("\t Failed Meks: " + failedByType.getOrDefault(UnitType.MEK, 0)); - System.out.println("\t Failed ProtoMeks: " + failedByType.getOrDefault(UnitType.PROTOMEK, 0)); - System.out.println("\t Failed Tanks: " + failedByType.getOrDefault(UnitType.TANK, 0)); - System.out.println("\t Failed VTOLs: " + failedByType.getOrDefault(UnitType.VTOL, 0)); - System.out.println("\t Failed Naval: " + failedByType.getOrDefault(UnitType.NAVAL, 0)); - System.out.println("\t Failed ASFs: " + failedByType.getOrDefault(UnitType.AEROSPACEFIGHTER, 0)); - System.out.println("\t Failed Aerospaces: " + failedByType.getOrDefault(UnitType.AERO, 0)); - System.out.println("\t Failed CFs: " + failedByType.getOrDefault(UnitType.CONV_FIGHTER, 0)); - System.out.println("\t Failed Small Craft: " + failedByType.getOrDefault(UnitType.SMALL_CRAFT, 0)); - System.out.println("\t Failed DropShips: " + failedByType.getOrDefault(UnitType.DROPSHIP, 0)); - System.out.println("\t Failed JumpShips: " + failedByType.getOrDefault(UnitType.JUMPSHIP, 0)); - System.out.println("\t Failed WarShips: " + failedByType.getOrDefault(UnitType.WARSHIP, 0)); - System.out.println("\t Failed Space Stations: " + failedByType.getOrDefault(UnitType.SPACE_STATION, 0)); - System.out.println("\t Failed BA: " + failedByType.getOrDefault(UnitType.BATTLE_ARMOR, 0)); - System.out.println("\t Failed Infantry: " + failedByType.getOrDefault(UnitType.INFANTRY, 0)); + + message = String.format(""" + Total Failures: %d + Failed Meks: %d + Failed ProtoMeks: %d + Failed Tanks: %d + Failed VTOLs: %d + Failed Naval: %d + Failed ASFs: %d + Failed AeroSpaces: %d + Failed CFs: %d + Failed Small Craft: %d + Failed DropShips: %d + Failed JumpShips: %d + Failed WarShips: %d + Failed Space Stations: %d + Failed BA: %d + Failed Infantry: %d + """, + failures, + failedByType.getOrDefault(UnitType.MEK, 0), + failedByType.getOrDefault(UnitType.PROTOMEK, 0), + failedByType.getOrDefault(UnitType.TANK, 0), + failedByType.getOrDefault(UnitType.VTOL, 0), + failedByType.getOrDefault(UnitType.NAVAL, 0), + failedByType.getOrDefault(UnitType.AEROSPACEFIGHTER, 0), + failedByType.getOrDefault(UnitType.AERO, 0), + failedByType.getOrDefault(UnitType.CONV_FIGHTER, 0), + failedByType.getOrDefault(UnitType.SMALL_CRAFT, 0), + failedByType.getOrDefault(UnitType.DROPSHIP, 0), + failedByType.getOrDefault(UnitType.JUMPSHIP, 0), + failedByType.getOrDefault(UnitType.WARSHIP, 0), + failedByType.getOrDefault(UnitType.SPACE_STATION, 0), + failedByType.getOrDefault(UnitType.BATTLE_ARMOR, 0), + failedByType.getOrDefault(UnitType.INFANTRY, 0)); + logger.info(message); } public static void main(String[] args) { @@ -219,22 +280,27 @@ public static void main(String[] args) { boolean verbose = false; boolean ignoreUnofficial = true; boolean failsOnly = true; + for (int i = 0; i < args.length; i++) { if (args[i].equals("-file")) { i++; if (i >= args.length) { - System.out.println("Missing argument filename!"); + logger.error("Missing argument filename!"); return; } + f = new File(args[i]); + if (!f.exists()) { - System.out.println("Can't find: " + args[i] + "!"); + String message = String.format("Can't find %s!", args[i]); + logger.info(message); return; } + if (args[i].endsWith(".zip")) { i++; if (i >= args.length) { - System.out.println("Missing Entity Name!"); + logger.info("Missing Entity Name!"); return; } entityName = args[i]; @@ -246,30 +312,37 @@ public static void main(String[] args) { } else if (args[i].equals("-unofficial")) { ignoreUnofficial = false; } else { - System.err.println("Error: Invalid argument.\n"); - System.err.println("Usage:\n\tEntityVerifier [flags] \n\n" + - "Valid Flags: \n" + - "-file \t Specify a file to validate,\n"+ - " \t else the data directory is checked\n" + - "-v \t Verbose -- print detailed report\n" + - "-unofficial \t Consider unofficial units in data dir\n"+ - "-valid \t Print verbose reports for valid units\n"); + logger.error(""" + Error: Invalid argument. + Usage: + + EntityVerifier [flags] + + Valid Flags: + -file Specify a file to validate, + else the data directory is checked + -v Verbose -- print detailed report + -unofficial Consider unofficial units in data dir + -valid Print verbose reports for valid units + """); return; } } if (f != null) { Entity entity; + try { entity = new MechFileParser(f, entityName).getEntity(); } catch (Exception ex) { LogManager.getLogger().error("", ex); return; } + EntityVerifier.getInstance(config).checkEntity(entity, f.toString(), true); } else { - // No specific file passed, so have MegaMek load all the mechs it - // normally would, then verify all of them. + // No specific file passed, so have MegaMek load all the mechs it normally + // would, then verify all of them. EntityVerifier ev = EntityVerifier.getInstance(config); ev.loadingVerbosity = verbose; ev.failsOnly = failsOnly;