/*
* Main.java
* Copyright 2009 Connor Petty <cpmeister@users.sourceforge.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Created on Sep 1, 2009, 6:17:59 PM
*/
package pcgen.system;
import java.awt.Font;
import java.awt.FontFormatException;
import java.awt.GraphicsEnvironment;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Locale;
import java.util.Properties;
import java.util.Set;
import java.util.logging.Level;
import javax.swing.JOptionPane;
import pcgen.cdom.base.Constants;
import pcgen.cdom.formula.PluginFunctionLibrary;
import pcgen.core.CustomData;
import pcgen.core.prereq.PrerequisiteTestFactory;
import pcgen.facade.core.UIDelegate;
import pcgen.gui2.PCGenUIManager;
import pcgen.gui2.SplashScreen;
import pcgen.gui2.UIPropertyContext;
import pcgen.gui2.converter.TokenConverter;
import pcgen.gui2.dialog.OptionsPathDialog;
import pcgen.gui2.plaf.LookAndFeelManager;
import pcgen.gui2.tools.Utility;
import pcgen.io.ExportHandler;
import pcgen.persistence.CampaignFileLoader;
import pcgen.persistence.GameModeFileLoader;
import pcgen.persistence.PersistenceLayerException;
import pcgen.persistence.lst.TokenStore;
import pcgen.persistence.lst.output.prereq.PrerequisiteWriterFactory;
import pcgen.persistence.lst.prereq.PreParserFactory;
import pcgen.pluginmgr.PluginManager;
import pcgen.rules.persistence.TokenLibrary;
import pcgen.util.Logging;
import pcgen.util.PJEP;
import net.sourceforge.argparse4j.ArgumentParsers;
import net.sourceforge.argparse4j.impl.Arguments;
import net.sourceforge.argparse4j.inf.ArgumentParser;
import net.sourceforge.argparse4j.inf.MutuallyExclusiveGroup;
import net.sourceforge.argparse4j.inf.Namespace;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;
/**
* Main entry point for pcgen.
*/
public final class Main
{
private static PropertyContextFactory configFactory;
private static boolean startGMGen;
private static boolean startNPCGen;
private static boolean ignoreJavaVer;
private static String settingsDir;
private static String campaignMode;
private static String characterSheet;
private static String exportSheet;
private static String partyFile;
private static String characterFile;
private static String outputFile;
private static final Logger LOGGER = LoggerFactory.getLogger(Main.class);
private Main()
{
}
public static boolean shouldStartInGMGen()
{
return startGMGen;
}
public static boolean shouldStartInNPCGen()
{
return startNPCGen;
}
public static boolean shouldStartInCharacterSheet()
{
return characterSheet != null;
}
public static String getStartupCampaign()
{
return campaignMode;
}
public static String getStartupCharacterFile()
{
return characterFile;
}
private static void logSystemProps()
{
Properties props = System.getProperties();
StringWriter writer = new StringWriter();
PrintWriter pwriter = new PrintWriter(writer);
pwriter.println();
pwriter.println("-- listing properties --"); //$NON-NLS-1$
// Manually output the property values to avoid them being cut off at 40 characters
Set<String> keys = props.stringPropertyNames();
//$NON-NLS-1$
keys.forEach(key ->
{
pwriter.println(key + "=" + props.getProperty(key)); //$NON-NLS-1$
});
Logging.log(Level.CONFIG, writer.toString());
}
/**
* @param args the command line arguments
*/
public static void main(String[] args)
{
Marker versionMarker = MarkerFactory.getMarker("VERSION");
LOGGER.info(versionMarker, "Starting PCGen v {} {}",
PCGenPropBundle.getVersionNumber(),
PCGenPropBundle.getAutobuildString());
Thread.setDefaultUncaughtExceptionHandler(new PCGenUncaughtExceptionHandler());
logSystemProps();
configFactory = new PropertyContextFactory(getConfigPath());
configFactory.registerAndLoadPropertyContext(ConfigurationSettings.getInstance());
parseCommands(args);
if (exportSheet == null)
{
startupWithGUI();
}
else
{
startupWithoutGUI();
shutdown();
}
}
private static String getConfigPath()
{
//TODO: convert to a proper command line argument instead of a -D java property
// First see if it was specified on the command line
String aPath = System.getProperty("pcgen.config"); //$NON-NLS-1$
if (aPath != null)
{
File testPath = new File(aPath);
// Then make sure it's an existing folder
if (testPath.exists() && testPath.isDirectory())
{
return aPath;
}
}
// Otherwise return user dir
return SystemUtils.USER_DIR;
}
public static boolean loadCharacterAndExport(String characterFile, String exportSheet, String outputFile, String configFile)
{
Main.characterFile = characterFile;
Main.exportSheet = exportSheet;
Main.outputFile = outputFile;
configFactory = new PropertyContextFactory(SystemUtils.USER_DIR);
configFactory.registerAndLoadPropertyContext(ConfigurationSettings.getInstance(configFile));
return startupWithoutGUI();
}
/**
* Initialize Main - must be called before any other getter can be used.
*
* @param argv the command line arguments to be parsed
*/
private static void parseCommands(String[] argv)
{
Namespace args = getParser().parseArgsOrFail(argv);
if (args.getInt("verbose") > 0)
{
Logging.setCurrentLoggingLevel(Logging.DEBUG);
}
startGMGen = args.getBoolean("gmgen");
startNPCGen = args.getBoolean("npc");
ignoreJavaVer = args.getBoolean("J");
settingsDir = args.getString("settingsdir");
campaignMode = args.getString("campaignmode");
characterSheet = args.get("D");
exportSheet = args.get("E");
partyFile = args.get("p");
characterFile = args.get("c");
outputFile = args.get("o");
}
private static void startupWithGUI()
{
// configure the UI before any type of user prompting may take place
configureUI();
validateEnvironment(true);
loadProperties(true);
initPrintPreviewFonts();
boolean showSplash = Boolean.parseBoolean(ConfigurationSettings.initSystemProperty("showSplash", "true"));
//TODO: allow commandline override of spash property
SplashScreen splash = null;
if (showSplash)
{
splash = new SplashScreen();
splash.setVisible(true);
}
PCGenTaskExecutor executor = new PCGenTaskExecutor();
executor.addPCGenTask(createLoadPluginTask());
executor.addPCGenTask(new GameModeFileLoader());
executor.addPCGenTask(new CampaignFileLoader());
if (splash != null)
{
executor.addPCGenTaskListener(splash);
}
executor.execute();
if (splash != null)
{
splash.setMessage(LanguageBundle.getString("in_taskInitUi")); //$NON-NLS-1$
}
FacadeFactory.initialize();
PCGenUIManager.initializeGUI();
if (splash != null)
{
splash.dispose();
}
PCGenUIManager.startGUI();
}
private static void configureUI()
{
String language = ConfigurationSettings.getLanguage();
String country = ConfigurationSettings.getCountry();
if (StringUtils.isNotEmpty(language) && StringUtils.isNotEmpty(country))
{
Locale.setDefault(new Locale(language, country));
}
LanguageBundle.init();
LookAndFeelManager.initLookAndFeel();
Utility.setApplicationTitle(Constants.APPLICATION_NAME);
}
/**
* Check that the runtime environment is suitable for PCGen to run.
* e.g. correct Java version
*/
private static void validateEnvironment(boolean useGui)
{
String javaVerString = System.getProperty("java.version");
String[] javaVer = javaVerString.split("\\.");
int majorVar = Integer.parseInt(javaVer[0]);
int minorVar = Integer.parseInt(javaVer[1]);
if (!ignoreJavaVer)
{
if ((majorVar < 1) || ((majorVar == 1) && (minorVar < 6)))
{
String message =
"Java version "
+ javaVerString
+ " is too old. PCGen requires at least Java 1.6 to run.";
Logging.errorPrint(message);
if (useGui)
{
JOptionPane.showMessageDialog(null, message,
Constants.APPLICATION_NAME, JOptionPane.ERROR_MESSAGE);
}
System.exit(1);
}
if ((majorVar > 1) || ((majorVar == 1) && (minorVar > 8)))
{
String message =
"Java version "
+ javaVerString
+ " is newer than PCGen supports. The program may not\n"
+ "work correctly. Java versions up to 1.8 are supported.";
Logging.errorPrint(message);
if (useGui)
{
int result =
JOptionPane.showConfirmDialog(null, message
+ "\n\nDo you wish to continue?",
Constants.APPLICATION_NAME,
JOptionPane.OK_CANCEL_OPTION);
if (result != JOptionPane.OK_OPTION)
{
System.exit(1);
}
}
}
}
// Check our main folders are present
String[] neededDirs =
{ConfigurationSettings.getSystemsDir(),
ConfigurationSettings.getPccFilesDir(),
ConfigurationSettings.getPluginsDir(),
ConfigurationSettings.getPreviewDir(),
ConfigurationSettings.getOutputSheetsDir()};
StringBuilder missingDirs = new StringBuilder();
for (final String dirPath : neededDirs)
{
File dir = new File(dirPath);
if (!dir.exists())
{
String path = dirPath;
try
{
path = dir.getCanonicalPath();
}
catch (IOException e)
{
Logging.errorPrint("Unable to find canonical path for "
+ dir);
}
missingDirs.append(" ").append(path).append("\n");
}
}
if (missingDirs.length() > 0)
{
String message;
message =
"This installation of PCGen is missing the following required folders:\n"
+ missingDirs;
Logging.errorPrint(message);
if (useGui)
{
JOptionPane.showMessageDialog(null, message
+ "\nPlease reinstall PCGen.", Constants.APPLICATION_NAME,
JOptionPane.ERROR_MESSAGE);
}
System.exit(1);
}
}
public static void loadProperties(boolean useGui)
{
if ((settingsDir == null) && (
ConfigurationSettings.getSystemProperty(ConfigurationSettings.SETTINGS_FILES_PATH) == null
))
{
if (!useGui)
{
Logging.errorPrint("No settingsDir specified via -s in batch mode and no default exists.");
System.exit(1);
}
String filePath = OptionsPathDialog.promptSettingsPath();
ConfigurationSettings.setSystemProperty(ConfigurationSettings.SETTINGS_FILES_PATH, filePath);
}
PropertyContextFactory.setDefaultFactory(settingsDir);
//Existing PropertyContexts are registered here
PropertyContextFactory defaultFactory = PropertyContextFactory.getDefaultFactory();
defaultFactory.registerPropertyContext(PCGenSettings.getInstance());
defaultFactory.registerPropertyContext(UIPropertyContext.getInstance());
defaultFactory.registerPropertyContext(LegacySettings.getInstance());
defaultFactory.loadPropertyContexts();
}
/**
* Create a task to load all system plugins.
*
* @return The task to load plugins.
*/
public static PCGenTask createLoadPluginTask()
{
String pluginsDir = ConfigurationSettings.getPluginsDir();
PluginClassLoader loader = new PluginClassLoader(new File(pluginsDir));
loader.addPluginLoader(TokenLibrary.getInstance());
loader.addPluginLoader(TokenStore.inst());
try
{
loader.addPluginLoader(PreParserFactory.getInstance());
} catch (PersistenceLayerException ex)
{
Logging.errorPrint("createLoadPluginTask failed", ex);
}
loader.addPluginLoader(PrerequisiteTestFactory.getInstance());
loader.addPluginLoader(PrerequisiteWriterFactory.getInstance());
loader.addPluginLoader(PJEP.getJepPluginLoader());
loader.addPluginLoader(ExportHandler.getPluginLoader());
loader.addPluginLoader(TokenConverter.getPluginLoader());
loader.addPluginLoader(PluginManager.getInstance());
loader.addPluginLoader(PluginFunctionLibrary.getInstance());
return loader;
}
private static boolean startupWithoutGUI()
{
loadProperties(false);
validateEnvironment(false);
PCGenTaskExecutor executor = new PCGenTaskExecutor();
executor.addPCGenTask(createLoadPluginTask());
executor.addPCGenTask(new GameModeFileLoader());
executor.addPCGenTask(new CampaignFileLoader());
executor.execute();
UIDelegate uiDelegate = new ConsoleUIDelegate();
BatchExporter exporter = new BatchExporter(exportSheet, uiDelegate);
boolean result = true;
if (partyFile != null)
{
result = exporter.exportParty(partyFile, outputFile);
}
if (characterFile != null)
{
result = exporter.exportCharacter(characterFile, outputFile);
}
return result;
}
public static void shutdown()
{
configFactory.savePropertyContexts();
BatchExporter.removeTemporaryFiles();
PropertyContextFactory.getDefaultFactory().savePropertyContexts();
// Need to (possibly) write customEquipment.lst
if (PCGenSettings.OPTIONS_CONTEXT
.getBoolean(PCGenSettings.OPTION_SAVE_CUSTOM_EQUIPMENT))
{
CustomData.writeCustomItems();
}
System.exit(0);
}
private static void initPrintPreviewFonts()
{
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
String fontDir = ConfigurationSettings.getOutputSheetsDir() + File.separator
+ "fonts" + File.separator + "NotoSans" + File.separator;
try
{
ge.registerFont(Font.createFont(Font.TRUETYPE_FONT, new File(fontDir + "NotoSans-Regular.ttf")));
ge.registerFont(Font.createFont(Font.TRUETYPE_FONT, new File(fontDir + "NotoSans-Bold.ttf")));
ge.registerFont(Font.createFont(Font.TRUETYPE_FONT, new File(fontDir + "NotoSans-Italic.ttf")));
ge.registerFont(Font.createFont(Font.TRUETYPE_FONT, new File(fontDir + "NotoSans-BoldItalic.ttf")));
}
catch (IOException | FontFormatException ex)
{
Logging.errorPrint("Unexpected exception loading fonts fo print p", ex);
}
}
/**
* @return an ArgumentParser used to peform argument parsing
*/
private static ArgumentParser getParser()
{
ArgumentParser parser = ArgumentParsers
.newArgumentParser(Constants.APPLICATION_NAME)
.defaultHelp(false)
.description("RPG Character Generator")
.version(PCGenPropBundle.getVersionNumber());
parser.addArgument("-v", "--verbose")
.help("verbose logging")
.type(Boolean.class)
.action(Arguments.count());
parser.addArgument("-V", "--version")
.action(Arguments.version());
parser.addArgument("-J")
.help("ignore java version checks")
.action(Arguments.storeTrue());
MutuallyExclusiveGroup startupMode = parser
.addMutuallyExclusiveGroup()
.description("start up on a specific mode");
startupMode.addArgument("-G", "--gmgen")
.help("GMGen mode")
.type(Boolean.class)
.action(Arguments.storeTrue());
startupMode.addArgument("-N", "--npc")
.help("NPC generation mode")
.type(Boolean.class)
.action(Arguments.storeTrue());
startupMode.addArgument("-D", "--tab").nargs(1);
parser.addArgument("-s", "--settingsdir")
.nargs(1)
.type(
Arguments.fileType()
.verifyIsDirectory()
.verifyCanRead()
.verifyExists()
);
parser.addArgument("-m", "--campaignmode")
.nargs(1)
.type(String.class)
;
parser.addArgument("-E", "--exportsheet")
.nargs(1)
.type(
Arguments.fileType()
.verifyCanRead()
.verifyExists()
.verifyIsFile()
);
parser.addArgument("-o", "--outputfile")
.nargs(1)
.type(
Arguments.fileType()
.verifyCanCreate()
.verifyCanWrite()
.verifyNotExists()
);
parser.addArgument("-c", "--character")
.nargs(1)
.type(
Arguments.fileType()
.verifyCanRead()
.verifyExists()
.verifyIsFile()
);
parser.addArgument("-p", "--party")
.nargs(1)
.type(
Arguments.fileType()
.verifyCanRead()
.verifyExists()
.verifyIsFile()
);
return parser;
}
/**
* The Class {@code PCGenUncaughtExceptionHandler} reports any
* exceptions that are not otherwise handled by the program.
*/
private static class PCGenUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler
{
@Override
public void uncaughtException(Thread t, Throwable e)
{
Logging.errorPrint("Uncaught error - ignoring", e);
}
}
}