package org.jabref.gui.filelist;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.StringJoiner;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
import javax.swing.event.TableModelEvent;
import javax.swing.table.AbstractTableModel;
import org.jabref.gui.externalfiletype.ExternalFileType;
import org.jabref.gui.externalfiletype.ExternalFileTypes;
import org.jabref.gui.externalfiletype.UnknownExternalFileType;
import org.jabref.model.entry.FileFieldParser;
import org.jabref.model.entry.FileFieldWriter;
import org.jabref.model.entry.LinkedFile;
import org.jabref.model.util.FileHelper;
/**
* Data structure to contain a list of file links, parseable from a coded string.
* Doubles as a table model for the file list editor.
*/
public class FileListTableModel extends AbstractTableModel {
private final List<FileListEntry> list = new ArrayList<>();
@Override
public int getRowCount() {
synchronized (list) {
return list.size();
}
}
@Override
public int getColumnCount() {
return 3;
}
@Override
public Class<String> getColumnClass(int columnIndex) {
return String.class;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
synchronized (list) {
FileListEntry entry = list.get(rowIndex);
switch (columnIndex) {
case 0:
return entry.getDescription();
case 1:
return entry.getLink();
default:
return entry.getType().isPresent() ? entry.getType().get().getName() : "";
}
}
}
public FileListEntry getEntry(int index) {
synchronized (list) {
return list.get(index);
}
}
public void setEntry(int index, FileListEntry entry) {
synchronized (list) {
list.set(index, entry);
fireTableRowsUpdated(index, index);
}
}
public void removeEntry(int index) {
synchronized (list) {
list.remove(index);
fireTableRowsDeleted(index, index);
}
}
/**
* Add an entry to the table model, and fire a change event. The change event
* is fired on the event dispatch thread.
* @param index The row index to insert the entry at.
* @param entry The entry to insert.
*/
public void addEntry(final int index, final FileListEntry entry) {
synchronized (list) {
list.add(index, entry);
if (SwingUtilities.isEventDispatchThread()) {
fireTableRowsInserted(index, index);
} else {
SwingUtilities.invokeLater(() -> fireTableRowsInserted(index, index));
}
}
}
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
// Do nothing
}
/**
* Set up the table contents based on the flat string representation of the file list
* @param value The string representation
*/
public void setContent(String value) {
setContent(value, false, true);
}
public void setContentDontGuessTypes(String value) {
setContent(value, false, false);
}
private FileListEntry setContent(String val, boolean firstOnly, boolean deduceUnknownTypes) {
String value = val;
if (value == null) {
value = "";
}
List<LinkedFile> fields = FileFieldParser.parse(value);
List<FileListEntry> files = new ArrayList<>();
for (LinkedFile entry : fields) {
if (entry.isEmpty()) {
continue;
}
if (firstOnly) {
return decodeEntry(entry, deduceUnknownTypes);
} else {
files.add(decodeEntry(entry, deduceUnknownTypes));
}
}
synchronized (list) {
list.clear();
list.addAll(files);
}
fireTableChanged(new TableModelEvent(this));
return null;
}
/**
* Convenience method for finding a label corresponding to the type of the
* first file link in the given field content. The difference between using
* this method and using setContent() on an instance of FileListTableModel
* is a slight optimization: with this method, parsing is discontinued after
* the first entry has been found.
* @param content The file field content, as fed to this class' setContent() method.
* @return A JLabel set up with no text and the icon of the first entry's file type,
* or null if no entry was found or the entry had no icon.
*/
public static JLabel getFirstLabel(String content) {
FileListTableModel tm = new FileListTableModel();
FileListEntry entry = tm.setContent(content, true, true);
if ((entry == null) || (!entry.getType().isPresent())) {
return null;
}
return entry.getType().get().getIconLabel();
}
private FileListEntry decodeEntry(LinkedFile entry, boolean deduceUnknownType) {
Optional<ExternalFileType> type = ExternalFileTypes.getInstance().getExternalFileTypeByName(entry.getFileType());
if (deduceUnknownType && (type.get() instanceof UnknownExternalFileType)) {
// No file type was recognized. Try to find a usable file type based
// on mime type:
type = ExternalFileTypes.getInstance().getExternalFileTypeByMimeType(entry.getFileType());
if (!type.isPresent()) {
// No type could be found from mime type on the extension:
Optional<String> extension = FileHelper.getFileExtension(entry.getLink());
if (extension.isPresent()) {
Optional<ExternalFileType> typeGuess = ExternalFileTypes.getInstance()
.getExternalFileTypeByExt(extension.get());
if (typeGuess.isPresent()) {
type = typeGuess;
}
}
}
}
return new FileListEntry(entry.getDescription(), entry.getLink(), type);
}
/**
* Transform the file list shown in the table into a flat string representable
* as a BibTeX field:
* @return String representation.
*/
public String getStringRepresentation() {
synchronized (list) {
String[][] array = new String[list.size()][];
int i = 0;
for (FileListEntry entry : list) {
array[i] = entry.getStringArrayRepresentation();
i++;
}
return FileFieldWriter.encodeStringArray(array);
}
}
/**
* Transform the file list shown in the table into a HTML string representation
* suitable for displaying the contents in a tooltip.
* @return Tooltip representation.
*/
public String getToolTipHTMLRepresentation() {
StringJoiner sb = new StringJoiner("<br>", "<html>", "</html>");
synchronized (list) {
for (FileListEntry entry : list) {
sb.add(String.format("%s (%s)", entry.getDescription(), entry.getLink()));
}
}
return sb.toString();
}
}