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;