/*
* RunConvertPanel.java
* Copyright 2009 (C) James Dempsey
*
* 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 18/01/2009 11:31:57 AM
*
* $Id$
*/
package pcgen.gui2.converter.panel;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.logging.Handler;
import java.util.logging.LogRecord;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import pcgen.cdom.base.CDOMObject;
import pcgen.cdom.enumeration.IntegerKey;
import pcgen.cdom.enumeration.ListKey;
import pcgen.cdom.enumeration.ObjectKey;
import pcgen.core.Campaign;
import pcgen.core.GameMode;
import pcgen.core.Globals;
import pcgen.core.SettingsHandler;
import pcgen.gui2.converter.ConversionDecider;
import pcgen.gui2.converter.LSTConverter;
import pcgen.gui2.converter.ObjectInjector;
import pcgen.gui2.converter.event.ProgressEvent;
import pcgen.gui2.converter.event.TaskStrategyMessage;
import pcgen.gui2.tools.Utility;
import pcgen.io.PCGFile;
import pcgen.persistence.lst.CampaignSourceEntry;
import pcgen.rules.context.EditorLoadContext;
import pcgen.system.LanguageBundle;
import pcgen.system.PCGenPropBundle;
import pcgen.system.PCGenSettings;
import pcgen.util.Logging;
/**
* The Class {@code RunConvertPanel} provides a display while
* the conversion is being run.
*
*
* @author James Dempsey <jdempsey@users.sourceforge.net>
*/
public class RunConvertPanel extends ConvertSubPanel implements Observer, ConversionDecider
{
int totalFileCount = 0;
int currentFileCount = 0;
private JProgressBar progressBar;
private ArrayList<Campaign> totalCampaigns;
private final EditorLoadContext context;
private JTextArea messageArea;
private JScrollPane messageAreaContainer;
private boolean errorState = false;
private String lastNotifiedFilename = "";
private String currFilename = "";
private Component statusField;
private File changeLogFile;
public RunConvertPanel(Component statusField)
{
context = new EditorLoadContext();
this.statusField = statusField;
PCGenSettings context = PCGenSettings.getInstance();
String dataLogFileName =
context.initProperty(PCGenSettings.CONVERT_DATA_LOG_FILE,
"dataChanges.log");
changeLogFile = new File(dataLogFileName);
}
/* (non-Javadoc)
* @see pcgen.gui2.converter.panel.ConvertSubPanel#autoAdvance(pcgen.cdom.base.CDOMObject)
*/
@Override
public boolean autoAdvance(CDOMObject pc)
{
return false;
}
/* (non-Javadoc)
* @see pcgen.gui2.converter.panel.ConvertSubPanel#performAnalysis(pcgen.cdom.base.CDOMObject)
*/
@Override
public boolean performAnalysis(final CDOMObject pc)
{
logSummary(pc);
final File rootDir = pc.get(ObjectKey.DIRECTORY);
final File outDir = pc.get(ObjectKey.WRITE_DIRECTORY);
totalCampaigns = new ArrayList<>(pc.getSafeListFor(ListKey.CAMPAIGN));
for (Campaign campaign : pc.getSafeListFor(ListKey.CAMPAIGN))
{
// Add all sub-files to the main campaign, regardless of exclusions
for (CampaignSourceEntry fName : campaign
.getSafeListFor(ListKey.FILE_PCC))
{
URI uri = fName.getURI();
if (PCGFile.isPCGenCampaignFile(uri))
{
Campaign c = Globals.getCampaignByURI(uri, false);
if (c != null)
{
totalCampaigns.add(c);
}
}
}
}
sortCampaignsByRank(totalCampaigns);
new Thread(new Runnable()
{
@Override
public void run()
{
Logging.registerHandler( getHandler() );
SettingsHandler.setGame(pc.get(ObjectKey.GAME_MODE).getName());
GameMode mode = SettingsHandler.getGame();
//Necessary for "good" behavior
mode.resolveInto(context.getReferenceContext());
//Necessary for those still using Globals.getContext
mode.resolveInto(mode.getContext().getReferenceContext());
LSTConverter converter;
Writer changeLogWriter;
try
{
changeLogWriter = new FileWriter(changeLogFile);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
String startTime = simpleDateFormat.format(new Date());
changeLogWriter.append("PCGen Data Converter v"
+ PCGenPropBundle.getVersionNumber()
+ " - conversion started at " + startTime + "\n");
changeLogWriter.append("Outputting files to " + outDir.getAbsolutePath() + "\n");
}
catch (IOException e1)
{
Logging.errorPrint("Failed to initialise LSTConverter", e1);
return;
}
converter = new LSTConverter(context, rootDir,
outDir.getAbsolutePath(), RunConvertPanel.this, changeLogWriter);
converter.addObserver(RunConvertPanel.this);
int numFiles = 0;
for (Campaign campaign : totalCampaigns)
{
numFiles += converter.getNumFilesInCampaign(campaign);
}
setTotalFileCount(numFiles);
converter.initCampaigns(totalCampaigns);
for (Campaign campaign : totalCampaigns)
{
converter.processCampaign(campaign);
}
ObjectInjector oi = new ObjectInjector(context, outDir,
rootDir, converter);
try
{
oi.writeInjectedObjects(totalCampaigns);
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
try
{
changeLogWriter.close();
}
catch (IOException e)
{
Logging.errorPrint("LSTConverter.wrapUp failed", e);
}
converter.deleteObserver( RunConvertPanel.this );
Logging.removeHandler( getHandler() );
try
{
// Wait for any left over messages to catch up
Thread.sleep(1000);
}
catch (InterruptedException e)
{
// Ignore exception
}
setCurrentFilename("");
addMessage("\nConversion complete.");
if (getHandler().getNumErrors() > 0)
{
JOptionPane.showMessageDialog(null, LanguageBundle
.getFormattedString("in_lstConvErrorsFound", //$NON-NLS-1$
getHandler().getNumErrors()), LanguageBundle
.getString("in_lstConvErrorsTitle"), //$NON-NLS-1$
JOptionPane.ERROR_MESSAGE);
}
progressBar.setValue(progressBar.getMaximum());
fireProgressEvent(ProgressEvent.AUTO_ADVANCE);
}
}).start();
return true;
}
/* (non-Javadoc)
* @see pcgen.gui2.converter.panel.ConvertSubPanel#setupDisplay(javax.swing.JPanel, pcgen.cdom.base.CDOMObject)
*/
@Override
public void setupDisplay(JPanel panel, CDOMObject pc)
{
panel.setLayout(new GridBagLayout());
JLabel introLabel = new JLabel("Conversion in progress");
GridBagConstraints gbc = new GridBagConstraints();
Utility.buildRelativeConstraints(gbc, GridBagConstraints.REMAINDER, 1, 1.0, 0);
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.insets = new Insets(0, 10, 5, 10);
panel.add(introLabel, gbc);
JLabel explainLabel = new JLabel("<html>The LST data is being converted. In the log, " +
"LSTERROR rows are errors that need to be manually corrected in the source data. " +
"LSTWARN rows indicate changes the converter is making. " +
"See " + changeLogFile.getAbsolutePath() + " for a log of all data changes.</html>");
explainLabel.setFocusable(true);
Utility.buildRelativeConstraints(gbc, GridBagConstraints.REMAINDER, 1, 1.0, 0);
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.insets = new Insets(0, 10, 5, 10);
panel.add(explainLabel, gbc);
progressBar = getProgressBar();
Dimension d = progressBar.getPreferredSize();
d.width = 400;
progressBar.setPreferredSize(d);
progressBar.setStringPainted(true);
Utility.buildRelativeConstraints(gbc, GridBagConstraints.REMAINDER, 1, 1.0, 0);
gbc.fill = GridBagConstraints.HORIZONTAL;
panel.add(progressBar, gbc);
messageAreaContainer = new JScrollPane(getMessageArea());
Utility.buildRelativeConstraints(gbc, GridBagConstraints.REMAINDER, GridBagConstraints.REMAINDER, 1.0, 1.0);
gbc.fill = GridBagConstraints.BOTH;
panel.add(messageAreaContainer, gbc);
panel.setPreferredSize(new Dimension(800, 500));
}
private LoadHandler handler = null;
private LoadHandler getHandler()
{
if (handler == null)
{
handler = new LoadHandler();
}
return handler;
}
public void setCurrentFilename(String filename)
{
Graphics g = statusField.getGraphics();
FontMetrics fm = g.getFontMetrics();
String message =
(filename == null || filename.length() == 0) ? ""
: "Converting " + filename;
int width = fm.stringWidth(message);
if (width >= statusField.getWidth())
{
message =
Utility.shortenString(fm, message, statusField.getWidth());
}
TaskStrategyMessage.sendStatus(this, message);
currFilename = filename;
}
public void addMessage(String message)
{
if (currFilename.length() > 0 && !currFilename.equals(lastNotifiedFilename))
{
getMessageArea().append("\n" + currFilename + "\n");
lastNotifiedFilename = currFilename;
}
getMessageArea().append(message + "\n");
}
/**
* Keeps track if there has been set an error message.
*
* @param errorState <code>true</code> if there was an error message
*/
public void setErrorState(boolean errorState)
{
this.errorState = errorState;
}
public boolean getErrorState()
{
return errorState;
}
/**
* This method initializes progressBar
*
* @return javax.swing.JProgressBar
*/
private JProgressBar getProgressBar()
{
if (progressBar == null)
{
progressBar = new JProgressBar();
progressBar.setValue(0);
progressBar.setStringPainted(true);
}
return progressBar;
}
/**
* This method initializes messageArea
*
* @return javax.swing.JTextArea
*/
private JTextArea getMessageArea()
{
if (messageArea == null)
{
messageArea = new JTextArea();
messageArea.setName("errorMessageBox");
messageArea.setEditable(false);
messageArea.setTabSize(8);
}
return messageArea;
}
@Override
public void update(Observable o, Object arg)
{
if (arg instanceof URI)
{
setCurrentFileCount(currentFileCount + 1);
final URI uri = (URI) arg;
setCurrentFilename(uri.toString());
}
else if (arg instanceof Exception)
{
final Exception e = (Exception) arg;
Runnable doWork = new Runnable()
{
@Override
public void run()
{
addMessage(e.getMessage());
setErrorState(true);
}
};
SwingUtilities.invokeLater(doWork);
System.out.println("Persistence Observer: ERROR: " + e.getMessage());
}
else
{
System.out.println("Persistence Observer: UNKNOWN: " + arg);
}
}
/**
* @param iFileCount The totalFileCount to set.
*/
protected void setTotalFileCount(final int iFileCount)
{
totalFileCount = iFileCount;
Runnable doWork = new Runnable() {
@Override
public void run()
{
getProgressBar().setMaximum(iFileCount);
}
};
SwingUtilities.invokeLater(doWork);
}
public void setCurrentFileCount(int curr)
{
currentFileCount = curr;
getProgressBar().setValue(curr);
}
/**
* A log handler to capture load errors and warnings and
* display them in the message section of the panel.
*/
private class LoadHandler extends Handler
{
int numErrors = 0;
int numWarnings = 0;
public LoadHandler()
{
setLevel(Logging.LST_WARNING);
}
@Override
public void close() throws SecurityException
{
// Nothing to do
}
@Override
public void flush()
{
// Nothing to do
}
@Override
public void publish(final LogRecord logRecord)
{
Runnable doWork = new Runnable()
{
@Override
public void run()
{
if (logRecord.getLevel().intValue() > Logging.WARNING.intValue())
{
numErrors++;
}
else if (logRecord.getLevel().intValue() > Logging.INFO.intValue())
{
numWarnings++;
}
addMessage(logRecord.getLevel() + " " + logRecord.getMessage());
setErrorState(true);
}
};
SwingUtilities.invokeLater(doWork);
}
/**
* @return the numErrors
*/
public int getNumErrors()
{
return numErrors;
}
/**
* @return the numWarnings
*/
public int getNumWarnings()
{
return numWarnings;
}
}
/* (non-Javadoc)
* @see pcgen.gui2.converter.ConversionDecider#getConversionDecision(java.lang.String, java.util.List, java.util.List)
*/
@Override
public String getConversionDecision(String overallDescription,
List<String> choiceDescriptions, List<String> choiceTokenResults,
int defaultChoice)
{
final ConversionChoiceDialog ccd =
new ConversionChoiceDialog(null, overallDescription,
choiceDescriptions, defaultChoice);
int result = 0;
Runnable showDialog = new Runnable()
{
@Override
public void run()
{
ccd.setVisible(true);
}
};
try
{
SwingUtilities.invokeAndWait(showDialog);
}
catch (InterruptedException | InvocationTargetException e)
{
Logging.errorPrint("Failed to display user choice, due to: ", e);
}
result = ccd.getResult();
return choiceTokenResults.get(result);
}
@Override
public String getConversionInput(String overallDescription)
{
final ConversionInputDialog ccd = new ConversionInputDialog(null,
overallDescription);
Runnable showDialog = new Runnable()
{
@Override
public void run()
{
ccd.setVisible(true);
}
};
try
{
SwingUtilities.invokeAndWait(showDialog);
}
catch (InterruptedException | InvocationTargetException e)
{
Logging.errorPrint("Failed to display user choice, due to: ", e);
}
return ccd.getResult();
}
/**
* This method sorts the provided listof Campaign objects by rank.
*
* @param aSelectedCampaignsList List of Campaign objects to sort
*/
private void sortCampaignsByRank(final List<Campaign> aSelectedCampaignsList)
{
Collections.sort(aSelectedCampaignsList, new Comparator<Campaign>()
{
@Override
public int compare(Campaign c1, Campaign c2)
{
return c1.getSafe(IntegerKey.CAMPAIGN_RANK) - c2.getSafe(IntegerKey.CAMPAIGN_RANK);
}
});
}
private void logSummary(final CDOMObject pc)
{
Logging.log(Logging.INFO, "Running data conversion using the following settings:");
Logging.log(Logging.INFO, "Source Folder: " + pc.get(ObjectKey.DIRECTORY).getAbsolutePath());
Logging.log(Logging.INFO, "Destination Folder: " + pc.get(ObjectKey.WRITE_DIRECTORY).getAbsolutePath());
Logging.log(Logging.INFO, "Game mode: " + pc.get(ObjectKey.GAME_MODE).getDisplayName());
List<Campaign> campaigns = pc.getSafeListFor(ListKey.CAMPAIGN);
StringBuilder campDisplay = new StringBuilder("");
for (int i = 0; i < campaigns.size(); i++)
{
campDisplay.append(campaigns.get(i).getDisplayName());
campDisplay.append("\n");
}
Logging.log(Logging.INFO, "Sources: " + campDisplay.toString());
Logging.log(Logging.INFO, "Logging changes to " + changeLogFile.getAbsolutePath());
}
}