package net.sf.jabref.gui; import java.util.ArrayList; import java.util.Iterator; import javax.swing.JLabel; import javax.swing.SwingUtilities; import javax.swing.event.TableModelEvent; import javax.swing.table.AbstractTableModel; import net.sf.jabref.Globals; import net.sf.jabref.external.ExternalFileType; import net.sf.jabref.external.UnknownExternalFileType; /** * 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 ArrayList<FileListEntry> list = new ArrayList<FileListEntry>(); public FileListTableModel() { } public int getRowCount() { synchronized (list) { return list.size(); } } public int getColumnCount() { return 3; } public Class<String> getColumnClass(int columnIndex) { return String.class; } 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() != null ? entry.getType().getName() : ""; } } } public FileListEntry getEntry(int index) { synchronized (list) { return list.get(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()) { SwingUtilities.invokeLater(new Runnable() { public void run() { fireTableRowsInserted(index, index); } }); } else fireTableRowsInserted(index, index); } } public void setValueAt(Object aValue, int rowIndex, int columnIndex) { } /** * 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 value, boolean firstOnly, boolean deduceUnknownTypes) { if (value == null) value = ""; ArrayList<FileListEntry> newList = new ArrayList<FileListEntry>(); StringBuilder sb = new StringBuilder(); ArrayList<String> thisEntry = new ArrayList<String>(); boolean inXmlChar = false; boolean escaped = false; for (int i=0; i<value.length(); i++) { char c = value.charAt(i); if (!escaped && (c == '\\')) { escaped = true; continue; } // Check if we are entering an XML special character construct such // as ",", because we need to know in order to ignore the semicolon. else if (!escaped && (c == '&') && !inXmlChar) { sb.append(c); if ((value.length() > i+1) && (value.charAt(i+1) == '#')) inXmlChar = true; } // Check if we are exiting an XML special character construct: else if (!escaped && inXmlChar && (c == ';')) { sb.append(c); inXmlChar = false; } else if (!escaped && (c == ':')) { thisEntry.add(sb.toString()); sb = new StringBuilder(); } else if (!escaped && (c == ';') && !inXmlChar) { thisEntry.add(sb.toString()); sb = new StringBuilder(); if (firstOnly) return decodeEntry(thisEntry, deduceUnknownTypes); else { newList.add(decodeEntry(thisEntry, deduceUnknownTypes)); thisEntry.clear(); } } else sb.append(c); escaped = false; } if (sb.length() > 0) thisEntry.add(sb.toString()); if (thisEntry.size() > 0) { if (firstOnly) return decodeEntry(thisEntry, deduceUnknownTypes); else newList.add(decodeEntry(thisEntry, deduceUnknownTypes)); } synchronized (list) { list.clear(); list.addAll(newList); } 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()==null ) return null; return entry.getType().getIconLabel(); } private FileListEntry decodeEntry(ArrayList<String> contents, boolean deduceUnknownType) { ExternalFileType type = Globals.prefs.getExternalFileTypeByName (getElementIfAvailable(contents, 2)); if (deduceUnknownType && (type instanceof UnknownExternalFileType)) { // No file type was recognized. Try to find a usable file type based // on the extension: ExternalFileType typeGuess = null; String link = getElementIfAvailable(contents, 1); int index = link.lastIndexOf('.'); if ((index >= 0) && (index < link.length()-1)) { String extension = link.substring(index+1); typeGuess = Globals.prefs.getExternalFileTypeByExt(extension); } if (typeGuess != null) type = typeGuess; } return new FileListEntry(getElementIfAvailable(contents, 0), getElementIfAvailable(contents, 1), type); } private String getElementIfAvailable(ArrayList<String> contents, int index) { if (index < contents.size()) return contents.get(index); else return ""; } /** * Transform the file list shown in the table into a flat string representable * as a BibTeX field: * @return String representation. */ public String getStringRepresentation() { StringBuilder sb = new StringBuilder(); for (Iterator<FileListEntry> iterator = list.iterator(); iterator.hasNext();) { FileListEntry entry = iterator.next(); sb.append(encodeEntry(entry)); if (iterator.hasNext()) sb.append(';'); } return sb.toString(); } /** * 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() { StringBuilder sb = new StringBuilder("<html>"); for (Iterator<FileListEntry> iterator = list.iterator(); iterator.hasNext();) { FileListEntry entry = iterator.next(); sb.append(entry.getDescription()).append(" (").append(entry.getLink()).append(')'); if (iterator.hasNext()) sb.append("<br>"); } return sb.append("</html>").toString(); } private String encodeEntry(FileListEntry entry) { StringBuilder sb = new StringBuilder(); sb.append(encodeString(entry.getDescription())); sb.append(':'); sb.append(encodeString(entry.getLink())); sb.append(':'); sb.append(encodeString(entry.getType() != null ? entry.getType().getName() : "")); return sb.toString(); } private String encodeString(String s) { StringBuilder sb = new StringBuilder(); for (int i=0; i<s.length(); i++) { char c = s.charAt(i); if ((c == ';') || (c == ':') || (c == '\\')) sb.append('\\'); sb.append(c); } return sb.toString(); } public void print() { System.out.println("----"); for (Iterator<FileListEntry> iterator = list.iterator(); iterator.hasNext();) { FileListEntry fileListEntry = iterator.next(); System.out.println(fileListEntry); } System.out.println("----"); } }