/*
* $Id$
*
* Copyright (c) 2008 by Michael Kiefte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License (LGPL) as published by the Free Software Foundation.
*
* 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, copies are available
* at http://www.opensource.org.
*/
package VASSAL.tools.imports;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.io.File;
import java.io.IOException;
import javax.swing.JOptionPane;
import VASSAL.build.GameModule;
import VASSAL.build.module.metadata.AbstractMetaData;
import VASSAL.build.module.metadata.ImportMetaData;
import VASSAL.configure.DirectoryConfigurer;
import VASSAL.i18n.Resources;
import VASSAL.launch.BasicModule;
import VASSAL.launch.EditModuleAction;
import VASSAL.launch.ModuleEditorWindow;
import VASSAL.preferences.Prefs;
import VASSAL.tools.ArchiveWriter;
import VASSAL.tools.ErrorDialog;
import VASSAL.tools.filechooser.ExtensionFileFilter;
import VASSAL.tools.filechooser.FileChooser;
import VASSAL.tools.filechooser.FileFilter;
import VASSAL.tools.imports.adc2.ADC2Module;
import VASSAL.tools.imports.adc2.ADC2Utils;
import VASSAL.tools.imports.adc2.MapBoard;
import VASSAL.tools.imports.adc2.SymbolSet;
/**
* Action for importing foreign modules into VASSAL.
*
* @author Michael Kiefte
* @since 3.1.0
*/
/*
* To add more capabilities, see the static fields DESCRIPTIONS, EXTENSIONS, and IMPORTERS.
*/
public final class ImportAction extends EditModuleAction {
private static final long serialVersionUID = 1L;
public ImportAction(Component comp) {
super(comp);
putValue(NAME, Resources.getString("Editor.import_module"));
}
/*
* The following three arrays describe the import file types that we can handle.
* They should be ordered in priority from most likely to least likely. File formats
* with complex file headers should take greater priority.
*/
private static final String[] EXTENSIONS = {
ADC2Utils.MODULE_EXTENSION,
ADC2Utils.MAP_EXTENSION,
ADC2Utils.SET_EXTENSION,
};
private static final String[] DESCRIPTIONS = {
ADC2Utils.MODULE_DESCRIPTION,
ADC2Utils.MAP_DESCRIPTION,
ADC2Utils.SET_DESCRIPTION,
};
/*
* These classes must descend from Importer.
*/
private static final Class<?>[] IMPORTERS = {
ADC2Module.class,
MapBoard.class,
SymbolSet.class,
};
public static FileChooser getFileChooser(Component c) {
final FileChooser chooser = FileChooser.createFileChooser(c,
(DirectoryConfigurer)
Prefs.getGlobalPrefs().getOption(Prefs.MODULES_DIR_KEY));
chooser.resetChoosableFileFilters();
for (int i = IMPORTERS.length-1; i >= 0; --i) {
chooser.addChoosableFileFilter(new ExtensionFileFilter(
DESCRIPTIONS[i] + " (*" + EXTENSIONS[i].toLowerCase()
+ ";*" + EXTENSIONS[i].toUpperCase() + ")",
new String[] {EXTENSIONS[i]})
);
}
return chooser;
}
public static Class<?> getImporterClass(File f) throws IOException {
final int[] indices = new int[IMPORTERS.length];
for (int i = 0; i < indices.length; ++i) {
indices[i] = i;
}
final String s = '.' + Importer.getExtension(f.getName());
for (int i = 0; i < EXTENSIONS.length; ++i) {
if (EXTENSIONS[i].compareToIgnoreCase(s) == 0) {
indices[0] = i;
indices[i] = 0;
break;
}
}
for (int i = 0; i < indices.length; ++i) {
try {
if (((Importer) (IMPORTERS[indices[i]].newInstance())).isValidImportFile(f)) {
return IMPORTERS[indices[i]];
}
}
catch (InstantiationException e) {
ErrorDialog.bug(e);
}
catch (IllegalAccessException e) {
ErrorDialog.bug(e);
}
}
return null;
}
public void performAction(ActionEvent e) throws IOException {
actionCancelled = true;
fc.resetChoosableFileFilters();
for (int i = IMPORTERS.length-1; i >= 0; --i) {
fc.addChoosableFileFilter(new ExtensionFileFilter(
DESCRIPTIONS[i] + " (*" + EXTENSIONS[i].toLowerCase() + ";*" + EXTENSIONS[i].toUpperCase() + ")",
new String[] {EXTENSIONS[i]})
);
}
if (fc.showOpenDialog() == FileChooser.APPROVE_OPTION) {
File f = fc.getSelectedFile();
if (f != null && f.exists()) {
loadModule(f);
actionCancelled = false;
}
}
}
public void loadModule(File f) throws IOException {
final Class<?> impClass = getImporterClass(f);
if (impClass == null) {
throw new FileFormatException("Unrecognized file format");
}
final GameModule module = new BasicModule(new ArchiveWriter((String) null));
GameModule.init(module);
final Importer imp;
try {
imp = (Importer) (impClass.newInstance());
imp.importFile(this, f);
imp.writeToArchive();
}
// these should never happen
catch (IllegalAccessException e) {
ErrorDialog.bug(e);
}
catch (InstantiationException e) {
ErrorDialog.bug(e);
}
module.getFrame().setVisible(true);
new ModuleEditorWindow(module).setVisible(true);
}
/**
* Find case-insensitive, cross-platform match for a given Windows file. Will
* ask the user if unable to locate the specified file.
*/
// public File getCaseInsensitiveFile(File f) {
// return getCaseInsensitiveFile(f, null, true, null);
// }
// public File getCaseInsensitiveFile(File f, boolean queryIfNotFound) {
// return getCaseInsensitiveFile(f, null, queryIfNotFound, null);
// }
/**
* Find case-insensitive, cross-platform match for a given Windows file. If unable
* to find a match, will then search the directory of the second file for a match.
* Will ask the user if still unable to locate the specified file.
*
* @param f File to match with a Windows-specific file-name format.
* @param base Another file whose directory to search for a match if unable to find otherwise.
* @return Local match
*/
// public File getCaseInsensitiveFile(File f, File base) {
// return getCaseInsensitiveFile(f, base, true, null);
// }
// File getCaseInsensitiveFile(File f, File base, boolean queryIfNotFound) {
// return getCaseInsensitiveFile(f, base, queryIfNotFound, null);
// }
/**
* Find case-insensitive, cross-platform match for a given Windows file. If unable
* to find a match, will then search the directory of the second file for a match.
* If still unable to locate the specified file will ask the user to locate the file
* using the specified file filter.
*
* @param f File to match with a Windows-specific file-name format.
* @param base Another file whose directory to search for a match.
* @param filter <code>FileFilter</code> to use when asking the user to locate the match.
* @return Local match
*/
// public File getCaseInsensitiveFile(File f, File base, FileFilter filter) {
// return getCaseInsensitiveFile(f, base, true, filter);
// }
/**
* Find case-insensitive, cross-platform match for a given Windows file.
* If unable to locate the specified file will ask the user to locate the file
* using the specified file filter.
*
* @param f File to match with a Windows-specific file-name format.
* @param filter <code>FileFilter</code> to use when asking the user to locate the match.
* @return Local match
*/
// public File getCaseInsensitiveFile(File f, FileFilter filter) {
// return getCaseInsensitiveFile(f, null, true, filter);
// }
/**
* Find case-insensitive, cross-platform match for a given file specified for a Windows directory structure.
*
* @param f File to search for formatted for Windows. E.g., "C:Dir\File.txt". The method will first
* check to see if the file exists as formatted. It will then search the path that this
* file is in if that exists.
* @param base If unable to find the file, will look in the directory of <code>base</code> which
* be localised to the current OS. Typically this is some default directory to look
* for files. This must be a full file name -- not just the path. The file name itself
* doesn't matter. In the case of importing files, if a base file requires another file,
* <code>base</code> is typically the base file under the assumption that the files on
* which it depends are found in the same directory. If <code>base</code> is <code>null</code>,
* this will be skipped.
* @param queryIfNotFound If the file still cannot be found, after searching <code>f</code> and <code>base</code>,
* should we ask the user to find it for us.
* @param filter The <code>FileFilter</code> that the file dialog can use to locate the file. If this is <code>null</code>,
* then no filter is used.
* @return If it exists, the file itself, otherwise null.
*/
/*
* This needs to be here as it uses the action's file chooser.
*/
public File getCaseInsensitiveFile(File f, File base,
boolean queryIfNotFound, FileFilter filter) {
// Easy case
if (f.exists())
return f;
final String name = Importer.getFileName(f.getName());
// check files in same directory ignoring case
final File parent = f.getParentFile();
if (parent != null) {
final File[] peers = parent.listFiles();
if (peers != null) {
for (File p : peers) {
if (p.getName().equalsIgnoreCase(name))
return p;
}
}
}
// if that doesn't work, check the files in the same directory as base
if (base != null) {
final File[] peers = base.getParentFile().listFiles();
if (peers != null) {
for (File p : peers) {
if (p.getName().equalsIgnoreCase(name))
return p;
}
}
}
// no luck so far. Ask the user.
if (queryIfNotFound) {
// FIXME: I18N!
JOptionPane.showMessageDialog(comp, "Unable to locate file:\n"
+ f.getPath() + "\nPlease locate it in the following dialog.",
"File Warning", JOptionPane.WARNING_MESSAGE);
if (fc == null) {
fc = getFileChooser(comp);
}
fc.resetChoosableFileFilters();
if (filter != null) fc.setFileFilter(filter);
fc.setSelectedFile(new File(f.getName()));
if (fc.showOpenDialog() == FileChooser.APPROVE_OPTION) {
final File p = fc.getSelectedFile();
if (p.exists()) return p;
}
}
// total failure
return null;
}
public static AbstractMetaData buildMetaData(File module) {
try {
if (getImporterClass(module) == null) {
// not a recognized file
return null;
}
}
catch (IOException e) {
return null;
}
return new ImportMetaData();
}
}