package org.jabref.gui.importer;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.EnumSet;
import java.util.Optional;
import java.util.zip.ZipFile;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.BorderFactory;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.ScrollPaneConstants;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableColumnModel;
import org.jabref.Globals;
import org.jabref.gui.DialogService;
import org.jabref.gui.FXDialogService;
import org.jabref.gui.JabRefDialog;
import org.jabref.gui.JabRefFrame;
import org.jabref.gui.help.HelpAction;
import org.jabref.gui.keyboard.KeyBinding;
import org.jabref.gui.util.DefaultTaskExecutor;
import org.jabref.gui.util.FileDialogConfiguration;
import org.jabref.logic.help.HelpFile;
import org.jabref.logic.importer.fileformat.CustomImporter;
import org.jabref.logic.l10n.Localization;
import org.jabref.logic.util.FileExtensions;
import org.jabref.preferences.JabRefPreferences;
import com.jgoodies.forms.builder.ButtonBarBuilder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Dialog to manage custom importers.
*/
public class ImportCustomizationDialog extends JabRefDialog {
private static final Log LOGGER = LogFactory.getLog(ImportCustomizationDialog.class);
// Column widths for import customization dialog table:
private static final int COL_0_WIDTH = 200;
private static final int COL_1_WIDTH = 80;
private static final int COL_2_WIDTH = 200;
private static final int COL_3_WIDTH = 200;
private final JTable customImporterTable;
public ImportCustomizationDialog(final JabRefFrame frame) {
super(frame, Localization.lang("Manage custom imports"), false, ImportCustomizationDialog.class);
ImportTableModel tableModel = new ImportTableModel();
customImporterTable = new JTable(tableModel);
TableColumnModel cm = customImporterTable.getColumnModel();
cm.getColumn(0).setPreferredWidth(COL_0_WIDTH);
cm.getColumn(1).setPreferredWidth(COL_1_WIDTH);
cm.getColumn(2).setPreferredWidth(COL_2_WIDTH);
cm.getColumn(3).setPreferredWidth(COL_3_WIDTH);
JScrollPane sp = new JScrollPane(customImporterTable, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
customImporterTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
customImporterTable.setPreferredScrollableViewportSize(getSize());
if (customImporterTable.getRowCount() > 0) {
customImporterTable.setRowSelectionInterval(0, 0);
}
JButton addFromFolderButton = new JButton(Localization.lang("Add from folder"));
addFromFolderButton.addActionListener(e -> {
FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder()
.addExtensionFilter(FileExtensions.CLASS)
.withDefaultExtension(FileExtensions.JAR)
.withInitialDirectory(Globals.prefs.get(JabRefPreferences.WORKING_DIRECTORY)).build();
DialogService ds = new FXDialogService();
Optional<Path> selectedFile = DefaultTaskExecutor
.runInJavaFXThread(() -> ds.showFileOpenDialog(fileDialogConfiguration));
if (selectedFile.isPresent() && (selectedFile.get().getParent() != null)) {
String chosenFileStr = selectedFile.get().toString();
try {
String basePath = selectedFile.get().getParent().toString();
String className = pathToClass(basePath, new File(chosenFileStr));
CustomImporter importer = new CustomImporter(basePath, className);
addOrReplaceImporter(importer);
customImporterTable.revalidate();
customImporterTable.repaint();
} catch (Exception exc) {
JOptionPane.showMessageDialog(frame, Localization.lang("Could not instantiate %0", chosenFileStr));
} catch (NoClassDefFoundError exc) {
JOptionPane.showMessageDialog(frame, Localization.lang(
"Could not instantiate %0. Have you chosen the correct package path?", chosenFileStr));
}
}
});
addFromFolderButton
.setToolTipText(Localization.lang("Add a (compiled) custom Importer class from a class path.")
+ "\n" + Localization.lang("The path need not be on the classpath of JabRef."));
JButton addFromJarButton = new JButton(Localization.lang("Add from JAR"));
addFromJarButton.addActionListener(e -> {
FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder()
.addExtensionFilters(EnumSet.of(FileExtensions.ZIP, FileExtensions.JAR))
.withDefaultExtension(FileExtensions.JAR)
.withInitialDirectory(Globals.prefs.get(JabRefPreferences.WORKING_DIRECTORY)).build();
DialogService ds = new FXDialogService();
Optional<Path> jarZipFile = DefaultTaskExecutor
.runInJavaFXThread(() -> ds.showFileOpenDialog(fileDialogConfiguration));
if (jarZipFile.isPresent()) {
try (ZipFile zipFile = new ZipFile(jarZipFile.get().toFile(), ZipFile.OPEN_READ)) {
ZipFileChooser zipFileChooser = new ZipFileChooser(this, zipFile);
zipFileChooser.setVisible(true);
customImporterTable.revalidate();
customImporterTable.repaint(10);
} catch (IOException exc) {
LOGGER.info("Could not open ZIP-archive.", exc);
JOptionPane.showMessageDialog(frame,
Localization.lang("Could not open %0", jarZipFile.get().toString()) + "\n"
+ Localization.lang("Have you chosen the correct package path?"));
} catch (NoClassDefFoundError exc) {
LOGGER.info("Could not instantiate ZIP-archive reader.", exc);
JOptionPane.showMessageDialog(frame,
Localization.lang("Could not instantiate %0", jarZipFile.get().toString()) + "\n"
+ Localization.lang("Have you chosen the correct package path?"));
}
}
});
addFromJarButton
.setToolTipText(Localization.lang("Add a (compiled) custom Importer class from a ZIP-archive.")
+ "\n" + Localization.lang("The ZIP-archive need not be on the classpath of JabRef."));
JButton showDescButton = new JButton(Localization.lang("Show description"));
showDescButton.addActionListener(e -> {
int row = customImporterTable.getSelectedRow();
if (row == -1) {
JOptionPane.showMessageDialog(frame, Localization.lang("Please select an importer."));
} else {
CustomImporter importer = ((ImportTableModel) customImporterTable.getModel()).getImporter(row);
JOptionPane.showMessageDialog(frame, importer.getDescription());
}
});
JButton removeButton = new JButton(Localization.lang("Remove"));
removeButton.addActionListener(e -> {
int row = customImporterTable.getSelectedRow();
if (row == -1) {
JOptionPane.showMessageDialog(frame, Localization.lang("Please select an importer."));
} else {
customImporterTable.removeRowSelectionInterval(row, row);
Globals.prefs.customImports
.remove(((ImportTableModel) customImporterTable.getModel()).getImporter(row));
Globals.IMPORT_FORMAT_READER.resetImportFormats(Globals.prefs.getImportFormatPreferences(),
Globals.prefs.getXMPPreferences());
customImporterTable.revalidate();
customImporterTable.repaint();
}
});
Action closeAction = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
dispose();
}
};
JButton closeButton = new JButton(Localization.lang("Close"));
closeButton.addActionListener(closeAction);
JButton helpButton = new HelpAction(HelpFile.CUSTOM_IMPORTS).getHelpButton();
// Key bindings:
JPanel mainPanel = new JPanel();
ActionMap am = mainPanel.getActionMap();
InputMap im = mainPanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
im.put(Globals.getKeyPrefs().getKey(KeyBinding.CLOSE_DIALOG), "close");
am.put("close", closeAction);
mainPanel.setLayout(new BorderLayout());
mainPanel.add(sp, BorderLayout.CENTER);
JPanel buttons = new JPanel();
ButtonBarBuilder bb = new ButtonBarBuilder(buttons);
buttons.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
bb.addGlue();
bb.addButton(addFromFolderButton);
bb.addButton(addFromJarButton);
bb.addButton(showDescButton);
bb.addButton(removeButton);
bb.addButton(closeButton);
bb.addUnrelatedGap();
bb.addButton(helpButton);
bb.addGlue();
getContentPane().add(mainPanel, BorderLayout.CENTER);
getContentPane().add(buttons, BorderLayout.SOUTH);
this.setSize(getSize());
pack();
this.setLocationRelativeTo(frame);
customImporterTable.requestFocus();
}
/*
* (non-Javadoc)
* @see java.awt.Component#getSize()
*/
@Override
public Dimension getSize() {
int width = COL_0_WIDTH + COL_1_WIDTH + COL_2_WIDTH + COL_3_WIDTH;
return new Dimension(width, width / 2);
}
/**
* Converts a path relative to a base-path into a class name.
*
* @param basePath base path
* @param path path that includes base-path as a prefix
* @return class name
*/
private static String pathToClass(String basePath, File path) {
String className = null;
File actualPath = path;
// remove leading basepath from path
while (!actualPath.equals(new File(basePath))) {
className = actualPath.getName() + (className == null ? "" : "." + className);
actualPath = actualPath.getParentFile();
}
if (className != null) {
int lastDot = className.lastIndexOf('.');
if (lastDot < 0) {
return className;
}
className = className.substring(0, lastDot);
}
return className;
}
/**
* Adds an importer to the model that underlies the custom importers.
*
* @param importer importer
*/
public void addOrReplaceImporter(CustomImporter importer) {
Globals.prefs.customImports.replaceImporter(importer);
Globals.IMPORT_FORMAT_READER.resetImportFormats(Globals.prefs.getImportFormatPreferences(),
Globals.prefs.getXMPPreferences());
((ImportTableModel) customImporterTable.getModel()).fireTableDataChanged();
}
/**
* Table model for the custom importer table.
*/
private class ImportTableModel extends AbstractTableModel {
private final String[] columnNames = new String[] {Localization.lang("Import name"),
Localization.lang("Command line id"), Localization.lang("Importer class"),
Localization.lang("Contained in")};
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
Object value = null;
CustomImporter importer = getImporter(rowIndex);
if (columnIndex == 0) {
value = importer.getName();
} else if (columnIndex == 1) {
value = importer.getName();
} else if (columnIndex == 2) {
value = importer.getClassName();
} else if (columnIndex == 3) {
value = importer.getBasePath();
}
return value;
}
@Override
public int getColumnCount() {
return columnNames.length;
}
@Override
public int getRowCount() {
return Globals.prefs.customImports.size();
}
@Override
public String getColumnName(int col) {
return columnNames[col];
}
public CustomImporter getImporter(int rowIndex) {
CustomImporter[] importers = Globals.prefs.customImports
.toArray(new CustomImporter[Globals.prefs.customImports.size()]);
return importers[rowIndex];
}
}
}