package org.jabref.cli;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.prefs.BackingStoreException;
import org.jabref.Globals;
import org.jabref.JabRefException;
import org.jabref.gui.externalfiles.AutoSetLinks;
import org.jabref.gui.importer.fetcher.EntryFetcher;
import org.jabref.gui.importer.fetcher.EntryFetchers;
import org.jabref.logic.bibtexkeypattern.BibtexKeyPatternUtil;
import org.jabref.logic.exporter.BibDatabaseWriter;
import org.jabref.logic.exporter.BibtexDatabaseWriter;
import org.jabref.logic.exporter.ExportFormat;
import org.jabref.logic.exporter.ExportFormats;
import org.jabref.logic.exporter.FileSaveSession;
import org.jabref.logic.exporter.IExportFormat;
import org.jabref.logic.exporter.SaveException;
import org.jabref.logic.exporter.SavePreferences;
import org.jabref.logic.exporter.SaveSession;
import org.jabref.logic.importer.ImportException;
import org.jabref.logic.importer.ImportFormatReader;
import org.jabref.logic.importer.OpenDatabase;
import org.jabref.logic.importer.OutputPrinter;
import org.jabref.logic.importer.ParserResult;
import org.jabref.logic.l10n.Localization;
import org.jabref.logic.layout.LayoutFormatterPreferences;
import org.jabref.logic.logging.JabRefLogger;
import org.jabref.logic.net.URLDownload;
import org.jabref.logic.search.DatabaseSearcher;
import org.jabref.logic.search.SearchQuery;
import org.jabref.logic.util.OS;
import org.jabref.model.Defaults;
import org.jabref.model.EntryTypes;
import org.jabref.model.database.BibDatabase;
import org.jabref.model.database.BibDatabaseContext;
import org.jabref.model.database.BibDatabaseMode;
import org.jabref.model.entry.BibEntry;
import org.jabref.model.metadata.MetaData;
import org.jabref.model.strings.StringUtil;
import org.jabref.preferences.SearchPreferences;
import org.jabref.shared.prefs.SharedDatabasePreferences;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class ArgumentProcessor {
private static final Log LOGGER = LogFactory.getLog(ArgumentProcessor.class);
private final JabRefCLI cli;
private final List<ParserResult> parserResults;
private final Mode startupMode;
private boolean noGUINeeded;
public ArgumentProcessor(String[] args, Mode startupMode) {
cli = new JabRefCLI(args);
this.startupMode = startupMode;
parserResults = processArguments();
}
/**
* Will open a file (like importFile), but will also request JabRef to focus on this database
*
* @param argument See importFile.
* @return ParserResult with setToOpenTab(true)
*/
private static Optional<ParserResult> importToOpenBase(String argument) {
Optional<ParserResult> result = importFile(argument);
result.ifPresent(ParserResult::setToOpenTab);
return result;
}
private static Optional<ParserResult> importFile(String argument) {
String[] data = argument.split(",");
String address = data[0];
Path file;
if (address.startsWith("http://") || address.startsWith("https://") || address.startsWith("ftp://")) {
// Download web resource to temporary file
try {
file = new URLDownload(address).toTemporaryFile();
} catch (IOException e) {
System.err.println(Localization.lang("Problem downloading from %1", address) + e.getLocalizedMessage());
return Optional.empty();
}
} else {
if (OS.WINDOWS) {
file = Paths.get(address);
} else {
file = Paths.get(address.replace("~", System.getProperty("user.home")));
}
}
String importFormat;
if (data.length > 1) {
importFormat = data[1];
} else {
importFormat = "*";
}
Optional<ParserResult> importResult = importFile(file, importFormat);
importResult.ifPresent(result -> {
OutputPrinter printer = new SystemOutputPrinter();
if (result.hasWarnings()) {
printer.showMessage(result.getErrorMessage());
}
});
return importResult;
}
private static Optional<ParserResult> importFile(Path file, String importFormat) {
try {
if (!"*".equals(importFormat)) {
System.out.println(Localization.lang("Importing") + ": " + file);
ParserResult result = Globals.IMPORT_FORMAT_READER.importFromFile(importFormat, file);
return Optional.of(result);
} else {
// * means "guess the format":
System.out.println(Localization.lang("Importing in unknown format") + ": " + file);
ImportFormatReader.UnknownFormatImport importResult = Globals.IMPORT_FORMAT_READER.importUnknownFormat(file);
System.out.println(Localization.lang("Format used") + ": " + importResult.format);
return Optional.of(importResult.parserResult);
}
} catch (ImportException ex) {
System.err
.println(Localization.lang("Error opening file") + " '" + file + "': " + ex.getLocalizedMessage());
return Optional.empty();
}
}
public List<ParserResult> getParserResults() {
return parserResults;
}
public boolean hasParserResults() {
return !parserResults.isEmpty();
}
private List<ParserResult> processArguments() {
if (!cli.isBlank() && cli.isDebugLogging()) {
JabRefLogger.setDebug();
}
if ((startupMode == Mode.INITIAL_START) && cli.isShowVersion()) {
cli.displayVersion();
}
if ((startupMode == Mode.INITIAL_START) && cli.isHelp()) {
cli.printUsage();
noGUINeeded = true;
return Collections.emptyList();
}
// Check if we should reset all preferences to default values:
if (cli.isPreferencesReset()) {
resetPreferences(cli.getPreferencesReset());
}
// Check if we should import preferences from a file:
if (cli.isPreferencesImport()) {
importPreferences();
}
// List to put imported/loaded database(s) in.
List<ParserResult> loaded = importAndOpenFiles();
if (!cli.isBlank() && cli.isFetcherEngine()) {
fetch(cli.getFetcherEngine()).ifPresent(loaded::add);
}
if (cli.isExportMatches()) {
if (!loaded.isEmpty()) {
if (!exportMatches(loaded)) {
return Collections.emptyList();
}
} else {
System.err.println(Localization.lang("The output option depends on a valid input option."));
}
}
if (cli.isGenerateBibtexKeys()) {
regenerateBibtexKeys(loaded);
}
if (cli.isAutomaticallySetFileLinks()) {
automaticallySetFileLinks(loaded);
}
if (cli.isFileExport()) {
if (!loaded.isEmpty()) {
exportFile(loaded, cli.getFileExport().split(","));
} else {
System.err.println(Localization.lang("The output option depends on a valid import option."));
}
}
LOGGER.debug("Finished export");
if (cli.isPreferencesExport()) {
try {
Globals.prefs.exportPreferences(cli.getPreferencesExport());
} catch (JabRefException ex) {
LOGGER.error("Cannot export preferences", ex);
}
}
if (!cli.isBlank() && cli.isAuxImport()) {
doAuxImport(loaded);
}
return loaded;
}
private boolean exportMatches(List<ParserResult> loaded) {
String[] data = cli.getExportMatches().split(",");
String searchTerm = data[0].replace("\\$", " "); //enables blanks within the search term:
//$ stands for a blank
ParserResult pr = loaded.get(loaded.size() - 1);
BibDatabaseContext databaseContext = pr.getDatabaseContext();
BibDatabase dataBase = pr.getDatabase();
SearchPreferences searchPreferences = new SearchPreferences(Globals.prefs);
SearchQuery query = new SearchQuery(searchTerm, searchPreferences.isCaseSensitive(),
searchPreferences.isRegularExpression());
List<BibEntry> matches = new DatabaseSearcher(query, dataBase).getMatches();
//export matches
if (!matches.isEmpty()) {
String formatName;
//read in the export format, take default format if no format entered
switch (data.length) {
case 3:
formatName = data[2];
break;
case 2:
//default ExportFormat: HTML table (with Abstract & BibTeX)
formatName = "tablerefsabsbib";
break;
default:
System.err.println(Localization.lang("Output file missing").concat(". \n \t ")
.concat(Localization.lang("Usage")).concat(": ") + JabRefCLI.getExportMatchesSyntax());
noGUINeeded = true;
return false;
}
//export new database
IExportFormat format = ExportFormats.getExportFormat(formatName);
if (format == null) {
System.err.println(Localization.lang("Unknown export format") + ": " + formatName);
} else {
// We have an ExportFormat instance:
try {
System.out.println(Localization.lang("Exporting") + ": " + data[1]);
format.performExport(databaseContext, data[1],
databaseContext.getMetaData().getEncoding().orElse(Globals.prefs.getDefaultEncoding()),
matches);
} catch (Exception ex) {
System.err.println(Localization.lang("Could not export file") + " '" + data[1] + "': "
+ ExceptionUtils.getStackTrace(ex));
}
}
} else {
System.err.println(Localization.lang("No search matches."));
}
return true;
}
private void doAuxImport(List<ParserResult> loaded) {
boolean usageMsg;
if (!loaded.isEmpty()) {
usageMsg = generateAux(loaded, cli.getAuxImport().split(","));
} else {
usageMsg = true;
}
if (usageMsg) {
System.out.println(Localization.lang("no base-BibTeX-file specified") + "!");
System.out.println(Localization.lang("usage") + " :");
System.out.println("jabref --aux infile[.aux],outfile[.bib] base-BibTeX-file");
}
}
private List<ParserResult> importAndOpenFiles() {
List<ParserResult> loaded = new ArrayList<>();
List<String> toImport = new ArrayList<>();
if (!cli.isBlank() && (!cli.getLeftOver().isEmpty())) {
for (String aLeftOver : cli.getLeftOver()) {
// Leftover arguments that have a "bib" extension are interpreted as
// BIB files to open. Other files, and files that could not be opened
// as bib, we try to import instead.
boolean bibExtension = aLeftOver.toLowerCase(Locale.ENGLISH).endsWith("bib");
ParserResult pr = new ParserResult();
if (bibExtension) {
pr = OpenDatabase.loadDatabase(aLeftOver, Globals.prefs.getImportFormatPreferences());
}
if (!bibExtension || (pr.isEmpty())) {
// We will try to import this file. Normally we
// will import it into a new tab, but if this import has
// been initiated by another instance through the remote
// listener, we will instead import it into the current library.
// This will enable easy integration with web browsers that can
// open a reference file in JabRef.
if (startupMode == Mode.INITIAL_START) {
toImport.add(aLeftOver);
} else {
loaded.add(importToOpenBase(aLeftOver).orElse(new ParserResult()));
}
} else {
loaded.add(pr);
}
}
}
if (!cli.isBlank() && cli.isFileImport()) {
toImport.add(cli.getFileImport());
}
for (String filenameString : toImport) {
importFile(filenameString).ifPresent(loaded::add);
}
if (!cli.isBlank() && cli.isImportToOpenBase()) {
importToOpenBase(cli.getImportToOpenBase()).ifPresent(loaded::add);
}
return loaded;
}
private boolean generateAux(List<ParserResult> loaded, String[] data) {
if (data.length == 2) {
ParserResult pr = loaded.get(0);
AuxCommandLine acl = new AuxCommandLine(data[0], pr.getDatabase());
BibDatabase newBase = acl.perform();
boolean notSavedMsg = false;
// write an output, if something could be resolved
if ((newBase != null) && newBase.hasEntries()) {
String subName = StringUtil.getCorrectFileName(data[1], "bib");
try {
System.out.println(Localization.lang("Saving") + ": " + subName);
SavePreferences prefs = SavePreferences.loadForSaveFromPreferences(Globals.prefs);
BibDatabaseWriter<SaveSession> databaseWriter = new BibtexDatabaseWriter<>(FileSaveSession::new);
Defaults defaults = new Defaults(Globals.prefs.getDefaultBibDatabaseMode());
SaveSession session = databaseWriter.saveDatabase(new BibDatabaseContext(newBase, defaults), prefs);
// Show just a warning message if encoding did not work for all characters:
if (!session.getWriter().couldEncodeAll()) {
System.err.println(Localization.lang("Warning") + ": "
+ Localization.lang(
"The chosen encoding '%0' could not encode the following characters:",
session.getEncoding().displayName())
+ " " + session.getWriter().getProblemCharacters());
}
session.commit(subName);
} catch (SaveException ex) {
System.err.println(Localization.lang("Could not save file.") + "\n" + ex.getLocalizedMessage());
}
notSavedMsg = true;
}
if (!notSavedMsg) {
System.out.println(Localization.lang("no library generated"));
}
return false;
} else {
return true;
}
}
private void exportFile(List<ParserResult> loaded, String[] data) {
if (data.length == 1) {
// This signals that the latest import should be stored in BibTeX
// format to the given file.
if (!loaded.isEmpty()) {
ParserResult pr = loaded.get(loaded.size() - 1);
if (!pr.isInvalid()) {
try {
System.out.println(Localization.lang("Saving") + ": " + data[0]);
SavePreferences prefs = SavePreferences.loadForSaveFromPreferences(Globals.prefs);
Defaults defaults = new Defaults(Globals.prefs.getDefaultBibDatabaseMode());
BibDatabaseWriter<SaveSession> databaseWriter = new BibtexDatabaseWriter<>(
FileSaveSession::new);
SaveSession session = databaseWriter.saveDatabase(
new BibDatabaseContext(pr.getDatabase(), pr.getMetaData(), defaults), prefs);
// Show just a warning message if encoding did not work for all characters:
if (!session.getWriter().couldEncodeAll()) {
System.err.println(Localization.lang("Warning") + ": "
+ Localization.lang(
"The chosen encoding '%0' could not encode the following characters:",
session.getEncoding().displayName())
+ " " + session.getWriter().getProblemCharacters());
}
session.commit(data[0]);
} catch (SaveException ex) {
System.err.println(Localization.lang("Could not save file.") + "\n" + ex.getLocalizedMessage());
}
}
} else {
System.err.println(Localization.lang("The output option depends on a valid import option."));
}
} else if (data.length == 2) {
// This signals that the latest import should be stored in the given
// format to the given file.
ParserResult pr = loaded.get(loaded.size() - 1);
// Set the global variable for this database's file directory before exporting,
// so formatters can resolve linked files correctly.
// (This is an ugly hack!)
File theFile = pr.getFile().get();
if (!theFile.isAbsolute()) {
theFile = theFile.getAbsoluteFile();
}
BibDatabaseContext databaseContext = pr.getDatabaseContext();
databaseContext.setDatabaseFile(theFile);
Globals.prefs.fileDirForDatabase = databaseContext
.getFileDirectories(Globals.prefs.getFileDirectoryPreferences());
System.out.println(Localization.lang("Exporting") + ": " + data[0]);
IExportFormat format = ExportFormats.getExportFormat(data[1]);
if (format == null) {
System.err.println(Localization.lang("Unknown export format") + ": " + data[1]);
} else {
// We have an ExportFormat instance:
try {
format.performExport(pr.getDatabaseContext(), data[0],
pr.getDatabaseContext().getMetaData().getEncoding()
.orElse(Globals.prefs.getDefaultEncoding()),
pr.getDatabaseContext().getDatabase().getEntries());
} catch (Exception ex) {
System.err.println(Localization.lang("Could not export file") + " '" + data[0] + "': "
+ ExceptionUtils.getStackTrace(ex));
}
}
}
}
private void importPreferences() {
try {
Globals.prefs.importPreferences(cli.getPreferencesImport());
EntryTypes.loadCustomEntryTypes(Globals.prefs.loadCustomEntryTypes(BibDatabaseMode.BIBTEX),
Globals.prefs.loadCustomEntryTypes(BibDatabaseMode.BIBLATEX));
Map<String, ExportFormat> customFormats = Globals.prefs.customExports.getCustomExportFormats(Globals.prefs,
Globals.journalAbbreviationLoader);
LayoutFormatterPreferences layoutPreferences = Globals.prefs
.getLayoutFormatterPreferences(Globals.journalAbbreviationLoader);
SavePreferences savePreferences = SavePreferences.loadForExportFromPreferences(Globals.prefs);
ExportFormats.initAllExports(customFormats, layoutPreferences, savePreferences);
} catch (JabRefException ex) {
LOGGER.error("Cannot import preferences", ex);
}
}
private void resetPreferences(String value) {
if ("all".equals(value.trim())) {
try {
System.out.println(Localization.lang("Setting all preferences to default values."));
Globals.prefs.clear();
new SharedDatabasePreferences().clear();
} catch (BackingStoreException e) {
System.err.println(Localization.lang("Unable to clear preferences."));
LOGGER.error("Unable to clear preferences", e);
}
} else {
String[] keys = value.split(",");
for (String key : keys) {
if (Globals.prefs.hasKey(key.trim())) {
System.out.println(Localization.lang("Resetting preference key '%0'", key.trim()));
Globals.prefs.clear(key.trim());
} else {
System.out.println(Localization.lang("Unknown preference key '%0'", key.trim()));
}
}
}
}
private void automaticallySetFileLinks(List<ParserResult> loaded) {
for (ParserResult parserResult : loaded) {
BibDatabase database = parserResult.getDatabase();
LOGGER.info(Localization.lang("Automatically setting file links"));
AutoSetLinks.autoSetLinks(database.getEntries(), parserResult.getDatabaseContext());
}
}
private void regenerateBibtexKeys(List<ParserResult> loaded) {
for (ParserResult parserResult : loaded) {
BibDatabase database = parserResult.getDatabase();
MetaData metaData = parserResult.getMetaData();
if (metaData != null) {
LOGGER.info(Localization.lang("Regenerating BibTeX keys according to metadata"));
for (BibEntry entry : database.getEntries()) {
// try to make a new label
BibtexKeyPatternUtil.makeAndSetLabel(
metaData.getCiteKeyPattern(Globals.prefs.getBibtexKeyPatternPreferences().getKeyPattern()),
database, entry, Globals.prefs.getBibtexKeyPatternPreferences());
}
} else {
LOGGER.info(Localization.lang("No meta data present in BIB_file. Cannot regenerate BibTeX keys"));
}
}
}
/**
* Run an entry fetcher from the command line.
* <p>
* Note that this only works headlessly if the EntryFetcher does not show any GUI.
*
* @param fetchCommand A string containing both the fetcher to use (id of EntryFetcherExtension minus Fetcher) and
* the search query, separated by a :
* @return A parser result containing the entries fetched or null if an error occurred.
*/
private Optional<ParserResult> fetch(String fetchCommand) {
if ((fetchCommand == null) || !fetchCommand.contains(":") || (fetchCommand.split(":").length != 2)) {
System.out.println(Localization.lang("Expected syntax for --fetch='<name of fetcher>:<query>'"));
System.out.println(Localization.lang("The following fetchers are available:"));
return Optional.empty();
}
String[] split = fetchCommand.split(":");
String engine = split[0];
EntryFetchers fetchers = new EntryFetchers(Globals.journalAbbreviationLoader);
EntryFetcher fetcher = null;
for (EntryFetcher e : fetchers.getEntryFetchers()) {
if (engine.equalsIgnoreCase(e.getClass().getSimpleName().replace("Fetcher", ""))) {
fetcher = e;
}
}
if (fetcher == null) {
System.out.println(Localization.lang("Could not find fetcher '%0'", engine));
System.out.println(Localization.lang("The following fetchers are available:"));
for (EntryFetcher e : fetchers.getEntryFetchers()) {
System.out.println(
" " + e.getClass().getSimpleName().replace("Fetcher", "").toLowerCase(Locale.ENGLISH));
}
return Optional.empty();
}
String query = split[1];
System.out.println(Localization.lang("Running query '%0' with fetcher '%1'.", query, engine) + " "
+ Localization.lang("Please wait..."));
Collection<BibEntry> result = new ImportInspectionCommandLine().query(query, fetcher);
if (result.isEmpty()) {
System.out.println(
Localization.lang("Query '%0' with fetcher '%1' did not return any results.", query, engine));
return Optional.empty();
}
return Optional.of(new ParserResult(result));
}
public boolean isBlank() {
return cli.isBlank();
}
public boolean shouldShutDown() {
return cli.isDisableGui() || cli.isShowVersion() || noGUINeeded;
}
public enum Mode {
INITIAL_START, REMOTE_START
}
}