package org.jabref.model.database; import java.io.File; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; import org.jabref.model.Defaults; import org.jabref.model.bibtexkeypattern.GlobalBibtexKeyPattern; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.FieldName; import org.jabref.model.metadata.FileDirectoryPreferences; import org.jabref.model.metadata.MetaData; import org.jabref.shared.DBMSSynchronizer; /** * Represents everything related to a BIB file. * <p> * The entries are stored in BibDatabase, the other data in MetaData and the options relevant for this file in Defaults. */ public class BibDatabaseContext { private final BibDatabase database; private final Defaults defaults; private MetaData metaData; /** The file where this database was last saved to. */ private File file; private DBMSSynchronizer dbmsSynchronizer; private DatabaseLocation location; public BibDatabaseContext() { this(new Defaults()); } public BibDatabaseContext(Defaults defaults) { this(new BibDatabase(), defaults); } public BibDatabaseContext(BibDatabase database) { this(database, new Defaults()); } public BibDatabaseContext(BibDatabase database, Defaults defaults) { this(database, new MetaData(), defaults); } public BibDatabaseContext(BibDatabase database, MetaData metaData, Defaults defaults) { this.defaults = Objects.requireNonNull(defaults); this.database = Objects.requireNonNull(database); this.metaData = Objects.requireNonNull(metaData); this.location = DatabaseLocation.LOCAL; } public BibDatabaseContext(BibDatabase database, MetaData metaData) { this(database, metaData, new Defaults()); } public BibDatabaseContext(BibDatabase database, MetaData metaData, File file, Defaults defaults, DatabaseLocation location) { this(database, metaData, defaults); Objects.requireNonNull(location); this.setDatabaseFile(file); if (location == DatabaseLocation.LOCAL) { convertToLocalDatabase(); } } public BibDatabaseContext(BibDatabase database, MetaData metaData, File file, Defaults defaults) { this(database, metaData, file, defaults, DatabaseLocation.LOCAL); } public BibDatabaseContext(BibDatabase database, MetaData metaData, File file) { this(database, metaData, file, new Defaults()); } public BibDatabaseContext(Defaults defaults, DatabaseLocation location, Character keywordSeparator, GlobalBibtexKeyPattern globalCiteKeyPattern) { this(new BibDatabase(), new MetaData(), defaults); if (location == DatabaseLocation.SHARED) { convertToSharedDatabase(keywordSeparator, globalCiteKeyPattern); } } public BibDatabaseMode getMode() { Optional<BibDatabaseMode> mode = metaData.getMode(); if (!mode.isPresent()) { BibDatabaseMode inferredMode = BibDatabaseModeDetection.inferMode(database); BibDatabaseMode newMode = BibDatabaseMode.BIBTEX; if ((defaults.mode == BibDatabaseMode.BIBLATEX) || (inferredMode == BibDatabaseMode.BIBLATEX)) { newMode = BibDatabaseMode.BIBLATEX; } this.setMode(newMode); return newMode; } return mode.get(); } public void setMode(BibDatabaseMode bibDatabaseMode) { metaData.setMode(bibDatabaseMode); } /** * Get the file where this database was last saved to or loaded from, if any. * * @return Optional of the relevant File, or Optional.empty() if none is defined. * @deprecated use {@link #getDatabasePath()} instead */ @Deprecated public Optional<File> getDatabaseFile() { return Optional.ofNullable(file); } public void setDatabaseFile(File file) { this.file = file; } public Optional<Path> getDatabasePath() { return Optional.ofNullable(file).map(File::toPath); } public void clearDatabaseFile() { this.file = null; } public BibDatabase getDatabase() { return database; } public MetaData getMetaData() { return metaData; } public void setMetaData(MetaData metaData) { this.metaData = Objects.requireNonNull(metaData); } public boolean isBiblatexMode() { return getMode() == BibDatabaseMode.BIBLATEX; } public List<Path> getFileDirectoriesAsPaths(FileDirectoryPreferences preferences) { // Filter for empty string, as this would be expanded to the jar-directory with Paths.get() return getFileDirectories(preferences).stream().filter(s -> !s.isEmpty()).map(Paths::get).collect(Collectors.toList()); } /** * @deprecated use {@link #getFileDirectoriesAsPaths(FileDirectoryPreferences)} instead */ @Deprecated public List<String> getFileDirectories(FileDirectoryPreferences preferences) { return getFileDirectories(FieldName.FILE, preferences); } /** * Returns the first existing file directory from {@link #getFileDirectories(FileDirectoryPreferences)} * @param preferences The FileDirectoryPreferences * @return Optional of Path */ public Optional<Path> getFirstExistingFileDir(FileDirectoryPreferences preferences) { return getFileDirectoriesAsPaths(preferences).stream().filter(Files::exists).findFirst(); } /** * Look up the directories set up for the given field type for this database. * If no directory is set up, return that defined in global preferences. * There can be up to three directory definitions for these files: * the database's metadata can specify a general directory and/or a user-specific directory * or the preferences can specify one. * <p> * The settings are prioritized in the following order and the first defined setting is used: * 1. metadata user-specific directory * 2. metadata general directory * 3. preferences directory * 4. BIB file directory * * @param fieldName The field type * @param preferences The fileDirectory preferences * @return The default directory for this field type. */ public List<String> getFileDirectories(String fieldName, FileDirectoryPreferences preferences) { List<String> fileDirs = new ArrayList<>(); // 1. metadata user-specific directory Optional<String> userFileDirectory = metaData.getUserFileDirectory(preferences.getUser()); if (userFileDirectory.isPresent()) { fileDirs.add(getFileDirectoryPath(userFileDirectory.get())); } // 2. metadata general directory Optional<String> metaDataDirectory = metaData.getDefaultFileDirectory(); if (metaDataDirectory.isPresent()) { fileDirs.add(getFileDirectoryPath(metaDataDirectory.get())); } // 3. preferences directory preferences.getFileDirectory(fieldName).ifPresent(path -> fileDirs.add(path.toAbsolutePath().toString())); // 4. BIB file directory getDatabasePath().ifPresent(dbPath -> { Objects.requireNonNull(dbPath, "dbPath is null"); Path parentPath = dbPath.getParent(); if (parentPath == null) { parentPath = Paths.get(System.getProperty("user.dir")); } Objects.requireNonNull(parentPath, "BibTex database parent path is null"); String parentDir = parentPath.toAbsolutePath().toString(); // Check if we should add it as primary file dir (first in the list) or not: if (preferences.isBibLocationAsPrimary()) { fileDirs.add(0, parentDir); } else { fileDirs.add(parentDir); } }); return fileDirs; } private String getFileDirectoryPath(String directoryName) { String dir = directoryName; // If this directory is relative, we try to interpret it as relative to // the file path of this BIB file: Optional<File> databaseFile = getDatabaseFile(); if (!new File(dir).isAbsolute() && databaseFile.isPresent()) { String relDir; if (".".equals(dir)) { // if dir is only "current" directory, just use its parent (== real current directory) as path relDir = databaseFile.get().getParent(); } else { relDir = databaseFile.get().getParent() + File.separator + dir; } // If this directory actually exists, it is very likely that the // user wants us to use it: if (new File(relDir).exists()) { dir = relDir; } } return dir; } public DBMSSynchronizer getDBMSSynchronizer() { return this.dbmsSynchronizer; } public void clearDBMSSynchronizer() { this.dbmsSynchronizer = null; } public DatabaseLocation getLocation() { return this.location; } public void convertToSharedDatabase(Character keywordSeparator, GlobalBibtexKeyPattern globalCiteKeyPattern) { this.dbmsSynchronizer = new DBMSSynchronizer(this, keywordSeparator, globalCiteKeyPattern); this.database.registerListener(dbmsSynchronizer); this.metaData.registerListener(dbmsSynchronizer); this.location = DatabaseLocation.SHARED; } @Override public String toString() { return "BibDatabaseContext{" + "file=" + file + ", location=" + location + '}'; } public void convertToLocalDatabase() { if (Objects.nonNull(dbmsSynchronizer) && (location == DatabaseLocation.SHARED)) { this.database.unregisterListener(dbmsSynchronizer); this.metaData.unregisterListener(dbmsSynchronizer); } this.location = DatabaseLocation.LOCAL; } public List<BibEntry> getEntries() { return database.getEntries(); } }