Skip to content
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

Abbreviate action update #5441

Closed
wants to merge 8 commits into from
Closed

Abbreviate action update #5441

wants to merge 8 commits into from

Conversation

Brainsucker92
Copy link
Contributor


REQUIRES TESTING!

I removed unnecessary field dependencies from the AbbreviateAction class to make it much more flexible to use. There is no longer a BasePanel required to abbreviate BibEntries.
Also I split up the relatively big abbreviation task into a lot of smaller tasks, which increase concurrency a lot (divide and conquer FTW!)

Important events during the abbreviation process can now be listened to via the AbbreviateActionListener interface or AbbreviateActionAdapter class.

Unfortunately I cannot test any of the changes since I don't have any big database to run an abbreviation on.
Is there any testing data available?

  • Change in CHANGELOG.md described
  • Tests created for changes
  • Manually tested changed features in running JabRef
  • Screenshots added in PR description (for bigger UI changes)
  • Ensured that the git commit message is a good one
  • Check documentation status (Issue created for outdated help page at help.jabref.org?)

public <V> Future<?> schedule(BackgroundTask<V> task, long delay, TimeUnit unit) {
return scheduledExecutor.schedule(getJavaFXTask(task), delay, unit);
public <V> Future<V> schedule(BackgroundTask<V> task, long delay, TimeUnit unit) {
return scheduledExecutor.schedule(task::call, delay, unit);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you remove the getJavaFX call?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delegating less tasks to the JavaFX thread helps to prevent any possible GUI freezes. The JavaFX Thread should be used for UI related stuff only. This helps to keep the GUI interactive and improve user experience when working with JabRef. Actually there are way too many methods that delegate tasks to the UI thread. One of them should be sufficient (at most). Everything else should be executed by another ExecutorService.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update: I added the method call again and managed to generify the Method so it no longer returns Future<?> which looks horrible to me.

Copy link
Member

@tobiasdiez tobiasdiez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your contribution. It's really good that somebody works on the parallelization. However, the current code needs more work.

@@ -56,7 +57,7 @@ public void execute() {
Localization.lang("Copy linked files to folder..."),
Localization.lang("Copy linked files to folder..."),
exportTask);
Globals.TASK_EXECUTOR.execute(exportTask);
BackgroundTask.wrap(exportTask).executeWith(Globals.TASK_EXECUTOR);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem to be equivalent to the previous code as the completion listerners of the CopyFilesTask are no longer invoked. Wrap exportTask just takes the run command from exportTask.

@@ -118,6 +129,7 @@ public BackgroundProgress getProgress() {
* Sets the {@link Consumer} that is invoked after the task is successfully finished.
* The consumer always runs on the JavaFX thread.
*/
@Deprecated
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why did you deprecated the onSuccess and onFailure method?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This functionality is now handled by the CompletableFuture object. See here. I really don't like the fact that the chained task is always executed on the JavaFX thread, which potentially leads to GUI freezes. See the java doc of Platform.runLater:

* NOTE: applications should avoid flooding JavaFX with too many * pending Runnables. Otherwise, the application may become unresponsive. * Applications are encouraged to batch up multiple operations into fewer * runLater calls. * Additionally, long-running operations should be done on a background * thread where possible, freeing up the JavaFX Application Thread for GUI * operations.

Therefore this method has to be deprecated as long as it's 'flooding' the JavaFX thread.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These background tasks are not executed on the javafx thread! Only the onsucess/onfailure methods are, which is really nice since you usually want to display messages there which should be done on the javafx thread. Thus this functionality is absolutely not covered by the completetable future.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The javafx Task Api is similar to the old swing worker.
Running stuff on a background thread, but having the possibility to have updates etc on the FX thread:
https://docs.oracle.com/javase/8/javafx/interoperability-tutorial/concurrency.htm

@@ -70,7 +73,7 @@ public void activeReferenceChanged(ReferenceViewModel reference) {
* Search and import unknown references from associated BIB files.
*/
public void importButtonClicked() {
ImportEntriesDialog dialog = new ImportEntriesDialog(databaseContext, BackgroundTask.wrap(() ->
ImportEntriesDialog dialog = new ImportEntriesDialog(databaseContext, BackgroundTask.wrap((Callable<List<BibEntry>>) () ->
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is it now necessary to add all these Callable statements. This is really ugly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's because of the Supplier method. Both the Supplier and Callable are functional interfaces, which require explicit type casting now. But I do agree, it's kind of ugly.

@@ -49,6 +51,15 @@ protected V call() throws Exception {
};
}

public static <V> BackgroundTask<V> wrap(Supplier<V> supplier) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reason for this addition? Callable and Supplier are essentially equivalent, just that callable allows checked exceptions (which is what we want).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When working with CompletableFuture it's always handy to have a method available that accepts the Supplier interface. The CompletableFuture only accepts either Runnable or Supplier types when submitting a new task. Check out the static methods here

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, but there shouldn't ever be a need to use these static methods directly as everything should be wrapped by a background task. Please remove this for the moment. In case, we need it later it's easy to add.

public <V> Future<?> schedule(BackgroundTask<V> task, long delay, TimeUnit unit) {
return scheduledExecutor.schedule(getJavaFXTask(task), delay, unit);
public <V> Future<V> schedule(BackgroundTask<V> task, long delay, TimeUnit unit) {
return scheduledExecutor.schedule(() -> getJavaFXTask(task).get(), delay, unit);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above, this is not equivalent. You really need to submit the task, otherwise it doesn't trigger the handlers.

executor.submit(task);
return task;
public <V> CompletableFuture<V> execute(Task<V> task) {
CompletableFuture<V> future = new CompletableFuture<>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The future is never completed. There seems to be something off here

public AbbreviateAction(BasePanel panel, boolean iso) {
this.panel = panel;
this.iso = iso;
public AbbreviateAction(Supplier<List<BibEntry>> bibEntryListSupplier, Supplier<UndoableAbbreviator> undoableAbbreviatorSupplier, Supplier<BibDatabase> databaseSupplier, Supplier<NamedCompound> compoundSupplier, AbbreviateActionListener listener) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The point of these action classes is that they represent a single user interaction. Thus, there should be no (almost no) outside configuration possible. For this reason, I'm not a big fan of your changes.
For example, designing wise it's not bad that the AbbreviateAction handles logging and submitting of changes (although DialogService and UndoManager should be used directly insdead of BasePanel).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of the 'outside configuration' has been passed into the class via the BasePanel object previously. This field dependecy to require a certain part of the GUI when you're actually processing data is completely dubious in my opinion. Therefore I simply removed this GUI related requirement, which makes this class a lot more flexible but still doing pretty much the same job as before.

Plase keep in mind not to 'weave' GUI components and data processing components into each other as this is really bad practice from an architectural point of view. But this is exactly what happenend in this case.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that there should be a clear separation of logic and gui, and most of JabRef's code sucks in this regard (although its getting better). However, the abbreviation class is not an example in my opinion. In contrast, it's actually quite good. Its prepares the input (get selected entries), invokes the logic class for the main action (UndoableAbbreviator) and then cleans up (submit changes to undo manager). That's relatively clean and straightforward (you could argue that the line for (Field journalField : FieldFactory.getJournalNameFields()) { should be moved to UndoableAbbreviator as it is business logic). You have created a flexible interface around the already pretty flexible UndoableAbbreviator - and I don't see the need for this honestly.

One should keep in mind that the AbbreviateAction is a GUI class and thus should handle the UI interaction part of the feature. The base panel is definitely not the right place to do this as it is too big even now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its prepares the input (get selected entries), invokes the logic class for the main action (UndoableAbbreviator) and then cleans up (submit changes to undo manager).

Everything you described here is a clear indication of a data processing object and has to be strictly separated from any GUI related stuff. Therefore this class cannot be part of the GUI.
Even the JavaDoc of the class itself clearly indicates the manipulation of data which is not supposed to happen inside a GUI related object at all.
Converts journal full names to either iso or medline abbreviations for all selected entries.

Following this model shown below, there has to be a strict separation of the three main layers
(data, controller, UI) to provide a clean software architecture. (as we agreed already)
http://web.mit.edu/6.813/www/sp18/classes/05-ui-sw-arch/figures/05.png

These layers are supposed to communicate via interfaces to exchange data/information as needed.
I'm afraid to say, that there is currently a lot of data management/processing going on within the BasePanel class, which should be part of the data layer instead of the GUI layer.
Have a look at BibDatabaseContext for example. Usually a GUI does not need such information to work. Instead this class is used to initialize even more data related stuff like SuggestionProviders and MainTableDataModel

I'm very sorry to say, but in conclusion there is no doubt, that there is currently a huge mess going on in these GUI classes. And that's exactly what I was trying to achieve with this pull request - decouple data processing from GUI.

Best way to exchange the required data between data layer and GUI layer would be to create an interface that allows the BasePanel to retrieve exactly the data it requires or another interface to set the data into the GUI as it's available (that's by the way exactly what I programmed in this PR. Have a look at AbbreviateActionListener)

This way we'll achieve clean code and best maintainability for the whole project.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, I totally agree. I think you just misunderstood how this class fits into JabRef's general architecture. Roughly speaking, data = model package, logic = logic package and gui = gui package. There does not need to be a further separation of "logic" and "data" in the gui as you cannot separate user input ("data") from initial pre-progressing ("logic") in a meaningful way.

If you want to see a real bad example: https://github.com/JabRef/jabref/blob/master/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseAction.java
There the code is doing a lot of actual business logic that should be moved to the logic package. (But please don't start to refactor this code, it's so integral to JabRef that we do not want to touch it before the 5.0 release.)

@@ -96,6 +101,24 @@ public void executeAndWait(Runnable command) {
}
}

public <T> CompletableFuture<T> execute(Supplier<T> supplier) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove these methods (they are not used).

@koppor
Copy link
Member

koppor commented Oct 15, 2019

I think, this is highly controversal.

I would ask to leave everything as is for now. We should discuss that at JabCon 2020. - Currently, we need our energy to fix other issues. See for instance the high priority issues at https://github.com/JabRef/jabref/projects/5.

@koppor koppor closed this Oct 15, 2019
@koppor koppor added the freeze label Oct 15, 2019
@koppor
Copy link
Member

koppor commented Oct 15, 2019

What we do need architecture-wise is a focus on #110. This should not touch the GUI code that much.

I especially need net.sf.jabref.bst.VM, because then bst files are directly supported by JabRef!

@koppor koppor mentioned this pull request Oct 15, 2019
6 tasks
@Siedlerchr
Copy link
Member

Thanks for your contribution to JabRef. We appreciate that. However, in this case it's a bit dificult. We understand your good intention, however, this is a crucial part of JabRef's logic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants