-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fixes #3472: Files starting with BibTeX key of a similar entry are no longer found by mistake #3509
Changes from 2 commits
26c4462
456241c
3784d90
18a6688
fb207b2
88cdb43
8f30b75
c45de80
e661b76
9d03922
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ | |
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
import java.util.ArrayList; | ||
import java.util.Collection; | ||
import java.util.List; | ||
import java.util.Optional; | ||
import java.util.stream.Collectors; | ||
|
@@ -26,22 +27,33 @@ | |
public class AutoSetFileLinksUtil { | ||
|
||
private static final Log LOGGER = LogFactory.getLog(AutoSetLinks.class); | ||
private List<Path> directories; | ||
private AutoLinkPreferences autoLinkPreferences; | ||
private ExternalFileTypes externalFileTypes; | ||
|
||
public List<LinkedFile> findassociatedNotLinkedFiles(BibEntry entry, BibDatabaseContext databaseContext, FileDirectoryPreferences fileDirPrefs, AutoLinkPreferences autoLinkPrefs, ExternalFileTypes externalFileTypes) { | ||
public AutoSetFileLinksUtil(BibDatabaseContext databaseContext, FileDirectoryPreferences fileDirectoryPreferences, AutoLinkPreferences autoLinkPreferences, ExternalFileTypes externalFileTypes) { | ||
this(databaseContext.getFileDirectoriesAsPaths(fileDirectoryPreferences), autoLinkPreferences, externalFileTypes); | ||
} | ||
|
||
public AutoSetFileLinksUtil(List<Path> directories, AutoLinkPreferences autoLinkPreferences, ExternalFileTypes externalFileTypes) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am against creating a second constructor. It was my initial idea in #3368 and guess who supposed the current alternative? 😆 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand what you mean. The linked comment is about the list of entries and not about how the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah sorry, I could not see all code in the line on my mobile, so I was a b9it confused. Forget about my remark, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No problem! |
||
this.directories = directories; | ||
this.autoLinkPreferences = autoLinkPreferences; | ||
this.externalFileTypes = externalFileTypes; | ||
} | ||
|
||
public List<LinkedFile> findAssociatedNotLinkedFiles(BibEntry entry) { | ||
List<LinkedFile> linkedFiles = new ArrayList<>(); | ||
|
||
List<Path> dirs = databaseContext.getFileDirectoriesAsPaths(fileDirPrefs); | ||
List<String> extensions = externalFileTypes.getExternalFileTypeSelection().stream().map(ExternalFileType::getExtension).collect(Collectors.toList()); | ||
|
||
// Run the search operation: | ||
FileFinder fileFinder = FileFinders.constructFromConfiguration(autoLinkPrefs); | ||
List<Path> result = fileFinder.findAssociatedFiles(entry, dirs, extensions); | ||
|
||
// Iterate over the entries: | ||
// Run the search operation | ||
FileFinder fileFinder = FileFinders.constructFromConfiguration(autoLinkPreferences); | ||
Collection<Path> result = fileFinder.findAssociatedFiles(entry, directories, extensions); | ||
|
||
// Collect the found files that are not yet linked | ||
for (Path foundFile : result) { | ||
boolean existingSameFile = entry.getFiles().stream() | ||
.map(file -> file.findIn(dirs)) | ||
boolean fileAlreadyLinked = entry.getFiles().stream() | ||
.map(file -> file.findIn(directories)) | ||
.anyMatch(file -> { | ||
try { | ||
return file.isPresent() && Files.isSameFile(file.get(), foundFile); | ||
|
@@ -50,8 +62,7 @@ public List<LinkedFile> findassociatedNotLinkedFiles(BibEntry entry, BibDatabase | |
} | ||
return false; | ||
}); | ||
if (!existingSameFile) { | ||
|
||
if (!fileAlreadyLinked) { | ||
Optional<ExternalFileType> type = FileHelper.getFileExtension(foundFile) | ||
.map(externalFileTypes::getExternalFileTypeByExt) | ||
.orElse(Optional.of(new UnknownExternalFileType(""))); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,21 +4,22 @@ | |
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
import java.nio.file.attribute.BasicFileAttributes; | ||
import java.util.ArrayList; | ||
import java.util.HashMap; | ||
import java.util.HashSet; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Objects; | ||
import java.util.Optional; | ||
import java.util.Set; | ||
import java.util.function.BiPredicate; | ||
import java.util.stream.Collectors; | ||
import java.util.stream.Stream; | ||
|
||
import org.jabref.logic.bibtexkeypattern.BibtexKeyPatternUtil; | ||
import org.jabref.model.entry.BibEntry; | ||
import org.jabref.model.strings.StringUtil; | ||
import org.jabref.model.util.FileHelper; | ||
|
||
import com.google.common.collect.ArrayListMultimap; | ||
import com.google.common.collect.Multimap; | ||
import org.apache.commons.logging.Log; | ||
import org.apache.commons.logging.LogFactory; | ||
|
||
|
@@ -32,40 +33,34 @@ class CiteKeyBasedFileFinder implements FileFinder { | |
} | ||
|
||
@Override | ||
public Map<BibEntry, List<Path>> findAssociatedFiles(List<BibEntry> entries, List<Path> directories, List<String> extensions) { | ||
public Multimap<BibEntry, Path> findAssociatedFiles(List<BibEntry> entries, List<Path> directories, List<String> extensions) { | ||
Objects.requireNonNull(directories); | ||
Objects.requireNonNull(entries); | ||
|
||
Map<BibEntry, List<Path>> result = new HashMap<>(); | ||
Multimap<BibEntry, Path> result = ArrayListMultimap.create(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you structure the code similar to the one in AutoSetLinks, you won't need a multimap:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks! This is really good feedback. I just realized that the finder never gets a list of entries as argument but always a single entry. Thus the design can be simplified drastically without mulitmaps or maps to collections. |
||
|
||
// First scan directories | ||
Set<Path> filesWithExtension = findFilesByExtension(directories, extensions); | ||
|
||
// Initialize Result-Set | ||
for (BibEntry entry : entries) { | ||
result.put(entry, new ArrayList<>()); | ||
} | ||
|
||
// Now look for keys | ||
nextFile: | ||
for (Path file : filesWithExtension) { | ||
String name = file.getFileName().toString(); | ||
int dot = name.lastIndexOf('.'); | ||
// First, look for exact matches: | ||
String nameWithoutExtension = FileUtil.getBaseName(name); | ||
|
||
// First, look for exact matches | ||
for (BibEntry entry : entries) { | ||
Optional<String> citeKey = entry.getCiteKeyOptional(); | ||
if ((citeKey.isPresent()) && !citeKey.get().isEmpty() && (dot > 0) | ||
&& name.substring(0, dot).equals(citeKey.get())) { | ||
if (StringUtil.isNotBlank(citeKey) && nameWithoutExtension.equals(citeKey.get())) { | ||
result.get(entry).add(file); | ||
continue nextFile; | ||
} | ||
} | ||
// If we get here, we did not find any exact matches. If non-exact | ||
// matches are allowed, try to find one: | ||
// If we get here, we did not find any exact matches. If non-exact matches are allowed, try to find one | ||
if (!exactKeyOnly) { | ||
for (BibEntry entry : entries) { | ||
Optional<String> citeKey = entry.getCiteKeyOptional(); | ||
if ((citeKey.isPresent()) && !citeKey.get().isEmpty() && name.startsWith(citeKey.get())) { | ||
if (StringUtil.isNotBlank(citeKey) && matches(name, citeKey.get())) { | ||
result.get(entry).add(file); | ||
continue nextFile; | ||
} | ||
|
@@ -76,10 +71,21 @@ public Map<BibEntry, List<Path>> findAssociatedFiles(List<BibEntry> entries, Lis | |
return result; | ||
} | ||
|
||
private boolean matches(String filename, String citeKey) { | ||
boolean startsWithKey = filename.startsWith(citeKey); | ||
if (startsWithKey) { | ||
// The file name starts with the key, that's already a good start | ||
// However, we do not want to match "JabRefa" for "JabRef" since this is probably a file belonging to another entry published in the same time / same name | ||
char charAfterKey = filename.charAt(citeKey.length()); | ||
return !StringUtil.contains(BibtexKeyPatternUtil.CHARS, charAfterKey); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Better direclty use the java method: String.contains(Character.toString(c)) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And also take a look at the codacy |
||
} | ||
return false; | ||
} | ||
|
||
/** | ||
* Returns a list of all files in the given directories which have one of the given extension. | ||
*/ | ||
public Set<Path> findFilesByExtension(List<Path> directories, List<String> extensions) { | ||
private Set<Path> findFilesByExtension(List<Path> directories, List<String> extensions) { | ||
Objects.requireNonNull(extensions, "Extensions must not be null!"); | ||
|
||
BiPredicate<Path, BasicFileAttributes> isFileWithCorrectExtension = (path, attributes) -> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,14 @@ | ||
package org.jabref.logic.util.io; | ||
|
||
import java.nio.file.Path; | ||
import java.util.Collection; | ||
import java.util.Collections; | ||
import java.util.List; | ||
import java.util.Map; | ||
|
||
import org.jabref.model.entry.BibEntry; | ||
|
||
import com.google.common.collect.Multimap; | ||
|
||
public interface FileFinder { | ||
|
||
/** | ||
|
@@ -17,10 +19,9 @@ public interface FileFinder { | |
* @param directories The root directories to search. | ||
* @param extensions The extensions that are acceptable. | ||
*/ | ||
Map<BibEntry, List<Path>> findAssociatedFiles(List<BibEntry> entries, List<Path> directories, List<String> extensions); | ||
Multimap<BibEntry, Path> findAssociatedFiles(List<BibEntry> entries, List<Path> directories, List<String> extensions); | ||
|
||
default List<Path> findAssociatedFiles(BibEntry entry, List<Path> directories, List<String> extensions) { | ||
Map<BibEntry, List<Path>> associatedFiles = findAssociatedFiles(Collections.singletonList(entry), directories, extensions); | ||
return associatedFiles.getOrDefault(entry, Collections.emptyList()); | ||
default Collection<Path> findAssociatedFiles(BibEntry entry, List<Path> directories, List<String> extensions) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do you use the collection interface here? List interface defines a somehow ordered collection There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The multimap only returns a collection because it actually is not implemented as a list (the sorting is random). But now we don't need multimaps anymore so we can go back to lists! |
||
return findAssociatedFiles(Collections.singletonList(entry), directories, extensions).get(entry); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see no advantage in using Collection here