update findAssociatedFiles to find all instead of the first one#14700
Conversation
|
|
||
| for (BibEntry entry : entries) { | ||
| List<LinkedFile> linkedFiles = new ArrayList<>(); | ||
| List<LinkedFile> associatedNotLinkedFiles = new ArrayList<>(); |
There was a problem hiding this comment.
reformatted code with better naming
| List<Path> linkedFiles = entry.getFiles().stream() | ||
| .map(file -> file.findIn(directories)) | ||
| .filter(Optional::isPresent) | ||
| .map(Optional::get) | ||
| .toList(); | ||
| for (Path foundFile : result) { | ||
| boolean fileAlreadyLinked = entry.getFiles().stream() | ||
| .map(file -> file.findIn(directories)) | ||
| .anyMatch(linked -> linked.filter(path -> { | ||
| try { | ||
| return Files.isSameFile(path, foundFile); | ||
| } catch (IOException e) { | ||
| LOGGER.debug("Unable to check file identity, assuming no identity", e); | ||
| return false; | ||
| } | ||
| }).isPresent()); | ||
| boolean fileAlreadyLinked = linkedFiles.stream() | ||
| .anyMatch(linked -> { | ||
| try { | ||
| return Files.isSameFile(linked, foundFile); | ||
| } catch (IOException e) { | ||
| LOGGER.debug("Unable to check file identity, assuming no identity", e); | ||
| return false; | ||
| } | ||
| }); |
There was a problem hiding this comment.
eliminated one performance issue here by avoiding a loop in another loop
| private List<Path> findAssociatedFilesByBrokenLinkedFile(BibEntry entry) throws IOException { | ||
| List<Path> matches = new ArrayList<>(); | ||
|
|
||
| for (LinkedFile brokenLink : entry.getFiles()) { | ||
| if (brokenLink.findIn(directories).isPresent()) { | ||
| continue; | ||
| } | ||
|
|
||
| String wantedBase = FileUtil.getBaseName(brokenLink.getLink()); | ||
|
|
||
| for (Path directory : directories) { | ||
| try (Stream<Path> walk = Files.walk(directory)) { | ||
| walk.filter(path -> !Files.isDirectory(path)) | ||
| .filter(path -> FileUtil.getBaseName(path).equalsIgnoreCase(wantedBase)) | ||
| .findFirst() | ||
| .ifPresent(matches::add); | ||
| for (LinkedFile linkedFile : entry.getFiles()) { | ||
| boolean isLinkedFileBroken = linkedFile.findIn(directories).isEmpty(); | ||
| if (isLinkedFileBroken) { | ||
| String linkedFileBaseName = FileUtil.getBaseName(linkedFile.getLink()); | ||
|
|
||
| for (Path directory : directories) { | ||
| try (Stream<Path> walk = Files.walk(directory)) { | ||
| List<Path> found = walk.filter(path -> !Files.isDirectory(path)) | ||
| .filter(path -> FileUtil.getBaseName(path).equalsIgnoreCase(linkedFileBaseName)) | ||
| .toList(); | ||
| matches.addAll(found); | ||
| } |
There was a problem hiding this comment.
updated the find associated files by broken linked file name logic to return all files instead of only the first one
PR Compliance Guide 🔍Below is a summary of compliance checks for this PR:
Compliance status legend🟢 - Fully Compliant🟡 - Partial Compliant 🔴 - Not Compliant ⚪ - Requires Further Human Verification 🏷️ - Compliance label |
||||||||||||||||||||||||||||
PR Code Suggestions ✨Explore these optional code suggestions:
|
|||||||||
|
@koppor Hi, I finished this PR. Feel free to review the changes here. I will avoid |
| assertEquals(2, matches.size()); | ||
| assertTrue(matches.stream().anyMatch(file -> { | ||
| return FileUtil.relativize(newPath1, List.of(directory)).equals(Path.of(file.getLink())); | ||
| })); | ||
| assertTrue(matches.stream().anyMatch(file -> { | ||
| return FileUtil.relativize(newPath2, List.of(directory)).equals(Path.of(file.getLink())); | ||
| })); |
There was a problem hiding this comment.
I think, FileUtil is used, but not scope of the test
Just test matches with a List.of(...) w/o calling FileUtil. I think, it should be easy to hard code the expected result?
There was a problem hiding this comment.
That makes sense. Now the FileUtil is avoided.
koppor
left a comment
There was a problem hiding this comment.
Thank you for the comments in the PR. This makes reviewing easier.
I am not able to fully test and review; therefore a small comment on the test setup. (I think, the PR is fine otherwise; need to check later)
| assertEquals(2, matches.size()); | ||
| assertTrue(matches.stream().anyMatch(file -> | ||
| Path.of(newPath1String).equals(Path.of(file.getLink())))); | ||
| assertTrue(matches.stream().anyMatch(file -> | ||
| Path.of(newPath2String).equals(Path.of(file.getLink())))); |
There was a problem hiding this comment.
Have
List<LinkedFile> expected = List.of(new LinkedFile(...), ...)
| assertTrue(matches.stream().anyMatch(file -> | ||
| Path.of(newPath2String).equals(Path.of(file.getLink())))); | ||
| List<String> expected = List.of(newPath1String, newPath2String); | ||
| // findAssociatedNotLinkedFiles does not guarantee how the returned files are ordered |
There was a problem hiding this comment.
Then we found a flaw, don't we?
- Stabilize findAssociatedNotLinkedFiles to return the files in the order they appear in the BibEntry
- Use
Collectionas return value
I prefer the former.
There was a problem hiding this comment.
I was about to comment
The function that accumulates associated files says nothing about how the returned file is ordered, so I used the HashSet trick to check.
Since multiple files (with the same name) might be found here for one broken linked file in a BibEntry, it seems there is no way to arrange the found files according to their order in BibEntry.
There was a problem hiding this comment.
Then I should go with option 2. How do you think?
There was a problem hiding this comment.
OK for me - one can sort "later"
(I haven't checked all the code, just my gut feeling)
| List<LinkedFile> matches = util.findAssociatedNotLinkedFiles(entry); | ||
| List<String> matchedFiles = util.findAssociatedNotLinkedFiles(entry) | ||
| .stream().map(LinkedFile::getLink).toList(); | ||
| assertEquals(2, matchedFiles.size()); |
There was a problem hiding this comment.
No need, will be done with assertEquals below
| ); | ||
| assertEquals(newFile, | ||
| matches.stream().findFirst().map(LinkedFile::getLink).orElse("")); | ||
| } |
There was a problem hiding this comment.
fix previous test to avoid FIleUtil here
|
|
||
| for (BibEntry entry : entries) { | ||
| List<LinkedFile> linkedFiles = new ArrayList<>(); | ||
| Collection<LinkedFile> associatedNotLinkedFiles = new ArrayList<>(); |
There was a problem hiding this comment.
| Collection<LinkedFile> associatedNotLinkedFiles = new ArrayList<>(); | |
| Collection<LinkedFile> associatedNotLinkedFiles = Set.of(); |
Because you overwrite?
Maybe even move the assignment to the catch block?
| /// 1. This method does not check if the file is already linked to another entry. | ||
| /// 2. This method does not guarantee how the returned files are ordered. | ||
| /// Order by how they appear in BibEntry does not work since findAssociatedFilesByBrokenLinkedFile may return | ||
| /// multiple files (with the same name) for one broken linked file in the entry. |
There was a problem hiding this comment.
fixed. Let me know if the fix is still not standard
|
|
||
| Collection<String> matchedFiles = util.findAssociatedNotLinkedFiles(entry) | ||
| .stream().map(LinkedFile::getLink).toList(); | ||
| List<String> expected = List.of(newPath1String, newPath2String); |
| Set<String> expected = Set.of(newPath1String, newPath2String); | ||
| // findAssociatedNotLinkedFiles does not guarantee how the returned files are ordered | ||
| // so here we compare equality without considering order | ||
| assertEquals(new HashSet<>(matchedFiles), expected); |
There was a problem hiding this comment.
Still not OK
I cannot push my updates to your main
To github.com:LangInteger/jabref.git
! [remote rejected] LangInteger/main -> LangInteger/main (permission denied)
error: failed to push some refs to 'github.com:LangInteger/jabref.git'
Please, in future, allow updates from maintainers!
Please apply following diff:
diff --git a/jabgui/src/test/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtilTest.java b/jabgui/src/test/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtilTest.java
index 8db83b9be1..4ac2825da6 100644
--- a/jabgui/src/test/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtilTest.java
+++ b/jabgui/src/test/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtilTest.java
@@ -4,7 +4,6 @@ import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
-import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
@@ -104,18 +103,15 @@ class AutoSetFileLinksUtilTest {
void findAllAssociatedNotLinkedFilesInsteadOfTheFirstOne(@TempDir Path tempDir) throws IOException {
Path directory = tempDir.resolve("files");
Path oldPath = directory.resolve("old/minimal.pdf");
- Files.createDirectories(oldPath.getParent());
- Files.createFile(oldPath);
- LinkedFile stale = new LinkedFile("", oldPath.toString(), "PDF");
- BibEntry entry = new BibEntry(StandardEntryType.Misc);
- entry.addFile(stale);
+ BibEntry entry = new BibEntry(StandardEntryType.Misc)
+ .withFiles(List.of(new LinkedFile("", oldPath.toString(), "PDF")));
// Simulate a file move
String newPath1String = "new1/minimal.pdf";
Path newPath1 = directory.resolve(newPath1String);
Files.createDirectories(newPath1.getParent());
- Files.move(oldPath, newPath1);
+ Files.createFile(newPath1);
// Create a second copy of the file
String newPath2String = "new2/minimal.pdf";
@@ -132,11 +128,10 @@ class AutoSetFileLinksUtilTest {
filePreferences,
autoLinkPrefs);
- Collection<String> matchedFiles = util.findAssociatedNotLinkedFiles(entry)
- .stream().map(LinkedFile::getLink).toList();
- Set<String> expected = Set.of(newPath1String, newPath2String);
- // findAssociatedNotLinkedFiles does not guarantee how the returned files are ordered
- // so here we compare equality without considering order
- assertEquals(new HashSet<>(matchedFiles), expected);
+ Collection<LinkedFile> matchedFiles = util.findAssociatedNotLinkedFiles(entry);
+ Set<LinkedFile> expected = Set.of(
+ new LinkedFile("", newPath1String, "PDF"),
+ new LinkedFile("", newPath2String, "PDF"));
+ assertEquals(expected, Set.copyOf(matchedFiles));
}
}
``´There was a problem hiding this comment.
Oh, thx for the PR. I also applied the diff.
|
You committed your code on the For this pull request, this is OK. For subsequent pull requests, please start with a different branch with a proper branch name. See CONTRIBUTING.md for more details. |
User description
Closes #14697
find associated files by broken linked file namelogic to return all files instead of only the first oneSteps to test
minimal.pdfunder the same directory to an entry via the “General” tab../minimal.pdfto./sub1/minimal.pdf../sub1/minimal.pdfto./sub2/minimal.pdf.Screenshot of my manual test
Before this PR, only one of the two associated files are displayed
After fixing in this PR, the two associated files are displayed
Mandatory checks
CHANGELOG.mdin a way that is understandable for the average user (if change is visible to the user)PR Type
Enhancement, Tests
Description
Modified
findAssociatedFilesByBrokenLinkedFileto return all matching files instead of just the first oneRefactored variable naming for clarity and improved code readability
Eliminated nested loop performance issue by pre-computing linked files list
Added comprehensive unit test for finding multiple associated files
Diagram Walkthrough
File Walkthrough
AutoSetFileLinksUtil.java
Find all associated files instead of first onejabgui/src/main/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtil.java
linkedFiles→associatedNotLinkedFiles,linkedFile→associateNotLinkedFilefindByBrokenLinkNamemethod tofindAssociatedFilesByBrokenLinkedFilewith improved logic.findFirst()to.toList()to collect all matching filesinstead of only the first one
linkedFileslist outside theloop to avoid redundant stream operations
readability
AutoSetFileLinksUtilTest.java
Add test for multiple associated files discoveryjabgui/src/test/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtilTest.java
findAllAssociatedNotLinkedFilesInsteadOfTheFirstOneto verify multiplefile discovery
matches are returned
assertTrueto support new test validationsrelative paths
CHANGELOG.md
Document unlinked files feature improvementCHANGELOG.md
files" feature
now shown instead of just the first one