package org.jabref.gui.externalfiletype; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Optional; import java.util.Set; import java.util.TreeSet; import org.jabref.Globals; import org.jabref.gui.IconTheme; import org.jabref.logic.l10n.Localization; import org.jabref.model.entry.FileFieldWriter; import org.jabref.model.strings.StringUtil; import org.jabref.model.util.FileHelper; import org.jabref.preferences.JabRefPreferences; public final class ExternalFileTypes { // This String is used in the encoded list in prefs of external file type // modifications, in order to indicate a removed default file type: private static final String FILE_TYPE_REMOVED_FLAG = "REMOVED"; // The only instance of this class: private static ExternalFileTypes singleton; // Map containing all registered external file types: private final Set<ExternalFileType> externalFileTypes = new TreeSet<>(); private final ExternalFileType HTML_FALLBACK_TYPE = new ExternalFileType("URL", "html", "text/html", "", "www", IconTheme.JabRefIcon.WWW.getSmallIcon()); private ExternalFileTypes() { updateExternalFileTypes(); } public static ExternalFileTypes getInstance() { if (ExternalFileTypes.singleton == null) { ExternalFileTypes.singleton = new ExternalFileTypes(); } return ExternalFileTypes.singleton; } public static List<ExternalFileType> getDefaultExternalFileTypes() { List<ExternalFileType> list = new ArrayList<>(); list.add(new ExternalFileType("PDF", "pdf", "application/pdf", "evince", "pdfSmall", IconTheme.JabRefIcon.PDF_FILE.getSmallIcon())); list.add(new ExternalFileType("PostScript", "ps", "application/postscript", "evince", "psSmall", IconTheme.JabRefIcon.FILE.getSmallIcon())); list.add(new ExternalFileType("Word", "doc", "application/msword", "oowriter", "openoffice", IconTheme.JabRefIcon.FILE_WORD.getSmallIcon())); list.add(new ExternalFileType("Word 2007+", "docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "oowriter", "openoffice", IconTheme.JabRefIcon.FILE_WORD.getSmallIcon())); list.add(new ExternalFileType(Localization.lang("OpenDocument text"), "odt", "application/vnd.oasis.opendocument.text", "oowriter", "openoffice", IconTheme.getImage("openoffice"))); list.add(new ExternalFileType("Excel", "xls", "application/excel", "oocalc", "openoffice", IconTheme.JabRefIcon.FILE_EXCEL.getSmallIcon())); list.add(new ExternalFileType("Excel 2007+", "xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "oocalc", "openoffice", IconTheme.JabRefIcon.FILE_EXCEL.getSmallIcon())); list.add(new ExternalFileType(Localization.lang("OpenDocument spreadsheet"), "ods", "application/vnd.oasis.opendocument.spreadsheet", "oocalc", "openoffice", IconTheme.getImage("openoffice"))); list.add(new ExternalFileType("PowerPoint", "ppt", "application/vnd.ms-powerpoint", "ooimpress", "openoffice", IconTheme.JabRefIcon.FILE_POWERPOINT.getSmallIcon())); list.add(new ExternalFileType("PowerPoint 2007+", "pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation", "ooimpress", "openoffice", IconTheme.JabRefIcon.FILE_POWERPOINT.getSmallIcon())); list.add(new ExternalFileType(Localization.lang("OpenDocument presentation"), "odp", "application/vnd.oasis.opendocument.presentation", "ooimpress", "openoffice", IconTheme.getImage("openoffice"))); list.add(new ExternalFileType("Rich Text Format", "rtf", "application/rtf", "oowriter", "openoffice", IconTheme.JabRefIcon.FILE_TEXT.getSmallIcon())); list.add(new ExternalFileType(Localization.lang("%0 image", "PNG"), "png", "image/png", "gimp", "picture", IconTheme.JabRefIcon.PICTURE.getSmallIcon())); list.add(new ExternalFileType(Localization.lang("%0 image", "GIF"), "gif", "image/gif", "gimp", "picture", IconTheme.JabRefIcon.PICTURE.getSmallIcon())); list.add(new ExternalFileType(Localization.lang("%0 image", "JPG"), "jpg", "image/jpeg", "gimp", "picture", IconTheme.JabRefIcon.PICTURE.getSmallIcon())); list.add(new ExternalFileType("Djvu", "djvu", "image/vnd.djvu", "evince", "psSmall", IconTheme.JabRefIcon.FILE.getSmallIcon())); list.add(new ExternalFileType("Text", "txt", "text/plain", "emacs", "emacs", IconTheme.JabRefIcon.FILE_TEXT.getSmallIcon())); list.add(new ExternalFileType("LaTeX", "tex", "application/x-latex", "emacs", "emacs", IconTheme.JabRefIcon.FILE_TEXT.getSmallIcon())); list.add(new ExternalFileType("CHM", "chm", "application/mshelp", "gnochm", "www", IconTheme.JabRefIcon.WWW.getSmallIcon())); list.add(new ExternalFileType(Localization.lang("%0 image", "TIFF"), "tiff", "image/tiff", "gimp", "picture", IconTheme.JabRefIcon.PICTURE.getSmallIcon())); list.add(new ExternalFileType("URL", "html", "text/html", "firefox", "www", IconTheme.JabRefIcon.WWW.getSmallIcon())); list.add(new ExternalFileType("MHT", "mht", "multipart/related", "firefox", "www", IconTheme.JabRefIcon.WWW.getSmallIcon())); list.add(new ExternalFileType("ePUB", "epub", "application/epub+zip", "firefox", "www", IconTheme.JabRefIcon.WWW.getSmallIcon())); // On all OSes there is a generic application available to handle file opening, // so we don't need the default application settings anymore: for (ExternalFileType type : list) { type.setOpenWith(""); } return list; } public Set<ExternalFileType> getExternalFileTypeSelection() { return externalFileTypes; } /** * Look up the external file type registered with this name, if any. * * @param name The file type name. * @return The ExternalFileType registered, or null if none. */ public Optional<ExternalFileType> getExternalFileTypeByName(String name) { for (ExternalFileType type : externalFileTypes) { if (type.getName().equals(name)) { return Optional.of(type); } } // Return an instance that signifies an unknown file type: return Optional.of(new UnknownExternalFileType(name)); } /** * Look up the external file type registered for this extension, if any. * * @param extension The file extension. * @return The ExternalFileType registered, or null if none. */ public Optional<ExternalFileType> getExternalFileTypeByExt(String extension) { for (ExternalFileType type : externalFileTypes) { if (type.getExtension().equalsIgnoreCase(extension)) { return Optional.of(type); } } return Optional.empty(); } /** * Returns true if there is an external file type registered for this extension. * * @param extension The file extension. * @return true if an ExternalFileType with the extension exists, false otherwise */ public boolean isExternalFileTypeByExt(String extension) { for (ExternalFileType type : externalFileTypes) { if (type.getExtension().equalsIgnoreCase(extension)) { return true; } } return false; } /** * Look up the external file type name registered for this extension, if any. * * @param extension The file extension. * @return The name of the ExternalFileType registered, or null if none. */ public String getExternalFileTypeNameByExt(String extension) { for (ExternalFileType type : externalFileTypes) { if (type.getExtension().equalsIgnoreCase(extension)) { return type.getName(); } } return ""; } /** * Look up the external file type registered for this filename, if any. * * @param filename The name of the file whose type to look up. * @return The ExternalFileType registered, or null if none. */ public Optional<ExternalFileType> getExternalFileTypeForName(String filename) { int longestFound = -1; ExternalFileType foundType = null; for (ExternalFileType type : externalFileTypes) { if (!type.getExtension().isEmpty() && filename.toLowerCase(Locale.ROOT).endsWith(type.getExtension().toLowerCase(Locale.ROOT)) && (type.getExtension().length() > longestFound)) { longestFound = type.getExtension().length(); foundType = type; } } return Optional.ofNullable(foundType); } /** * Look up the external file type registered for this MIME type, if any. * * @param mimeType The MIME type. * @return The ExternalFileType registered, or null if none. For the mime type "text/html", a valid file type is * guaranteed to be returned. */ public Optional<ExternalFileType> getExternalFileTypeByMimeType(String mimeType) { for (ExternalFileType type : externalFileTypes) { if (type.getMimeType().equalsIgnoreCase(mimeType)) { return Optional.of(type); } } if ("text/html".equalsIgnoreCase(mimeType)) { return Optional.of(HTML_FALLBACK_TYPE); } else { return Optional.empty(); } } /** * Reset the List of external file types after user customization. * * @param types The new List of external file types. This is the complete list, not just new entries. */ public void setExternalFileTypes(List<ExternalFileType> types) { // First find a list of the default types: List<ExternalFileType> defTypes = getDefaultExternalFileTypes(); // Make a list of types that are unchanged: List<ExternalFileType> unchanged = new ArrayList<>(); externalFileTypes.clear(); for (ExternalFileType type : types) { externalFileTypes.add(type); // See if we can find a type with matching name in the default type list: ExternalFileType found = null; for (ExternalFileType defType : defTypes) { if (defType.getName().equals(type.getName())) { found = defType; break; } } if (found != null) { // Found it! Check if it is an exact match, or if it has been customized: if (found.equals(type)) { unchanged.add(type); } else { // It was modified. Remove its entry from the defaults list, since // the type hasn't been removed: defTypes.remove(found); } } } // Go through unchanged types. Remove them from the ones that should be stored, // and from the list of defaults, since we don't need to mention these in prefs: for (ExternalFileType type : unchanged) { defTypes.remove(type); types.remove(type); } // Now set up the array to write to prefs, containing all new types, all modified // types, and a flag denoting each default type that has been removed: String[][] array = new String[types.size() + defTypes.size()][]; int i = 0; for (ExternalFileType type : types) { array[i] = type.getStringArrayRepresentation(); i++; } for (ExternalFileType type : defTypes) { array[i] = new String[] {type.getName(), FILE_TYPE_REMOVED_FLAG}; i++; } Globals.prefs.put(JabRefPreferences.EXTERNAL_FILE_TYPES, FileFieldWriter.encodeStringArray(array)); } /** * Set up the list of external file types, either from default values, or from values recorded in Preferences. */ private void updateExternalFileTypes() { // First get a list of the default file types as a starting point: List<ExternalFileType> types = getDefaultExternalFileTypes(); // If no changes have been stored, simply use the defaults: if (Globals.prefs.get(JabRefPreferences.EXTERNAL_FILE_TYPES, null) == null) { externalFileTypes.clear(); externalFileTypes.addAll(types); return; } // Read the prefs information for file types: String[][] vals = StringUtil .decodeStringDoubleArray(Globals.prefs.get(JabRefPreferences.EXTERNAL_FILE_TYPES, "")); for (String[] val : vals) { if ((val.length == 2) && val[1].equals(FILE_TYPE_REMOVED_FLAG)) { // This entry indicates that a default entry type should be removed: ExternalFileType toRemove = null; for (ExternalFileType type : types) { if (type.getName().equals(val[0])) { toRemove = type; break; } } // If we found it, remove it from the type list: if (toRemove != null) { types.remove(toRemove); } } else { // A new or modified entry type. Construct it from the string array: ExternalFileType type = ExternalFileType.buildFromArgs(val); // Check if there is a default type with the same name. If so, this is a // modification of that type, so remove the default one: ExternalFileType toRemove = null; for (ExternalFileType defType : types) { if (type.getName().equals(defType.getName())) { toRemove = defType; break; } } // If we found it, remove it from the type list: if (toRemove != null) { types.remove(toRemove); } // Then add the new one: types.add(type); } } // Finally, build the list of types based on the modified defaults list: externalFileTypes.addAll(types); } public Optional<ExternalFileType> getExternalFileTypeByFile(Path file) { final String filePath = file.toString(); final Optional<String> extension = FileHelper.getFileExtension(filePath); return extension.flatMap(this::getExternalFileTypeByExt); } }