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

fix: fix exception in custom preview layout change #7539

Closed
wants to merge 8 commits into from
Closed

fix: fix exception in custom preview layout change #7539

wants to merge 8 commits into from

Conversation

yinpeiqi
Copy link
Contributor

@yinpeiqi yinpeiqi commented Mar 15, 2021

This problem is cause by unexpected exception trigger that listening to value change. In layoutHelper, this function is call by both button click 'save', init loading and value change of text. Actually the last one do not need trigger this exception. So I add a flag from the separation of these three conditions.

Fixses #7526

  • Change in CHANGELOG.md described in a way that is understandable for the average user (if applicable)
  • Tests created for changes (if applicable)
  • Manually tested changed features in running JabRef (always required)
  • Screenshots added in PR description (for UI changes)
  • Checked documentation: Is the information available and up to date? If not created an issue at https://github.com/JabRef/user-documentation/issues or, even better, submitted a pull request to the documentation repository.

@yinpeiqi
Copy link
Contributor Author

This is just one approach of this issue. Although it works, I still consider that it's a bad implementation and lack of elegance.

@calixtus
Copy link
Member

Hi @yinpeiqi , thanks for your interest in this issue.
I agree, muting an exception is almost never a good idea.
If I understood the issue description correctly this seems to be a problem about a parser, bound to a listener for changes. Maybe there is a way to suspense parsing the code for about half a second around editing and then restart parsing?

@yinpeiqi
Copy link
Contributor Author

I think suspense parsing the code can't solve this problem. It will cause a time-wait when 'save' is clicked which is unnecessary. Besides, only suspense parsing may not work because users may edit it for a long time.
These three places use the same parser to deal with the format. Most of the uses in the parser are the same but only the exception judgment should be different. Is it possible to replace this parser with two parsers, and used by different places?

@calixtus
Copy link
Member

I really think we are talking about two different things...

The original issue never said anything about saving. @Siedlerchr suggested, that changes should only be evaluated when pressing save. As I looked into a code a bit, a saw several problems: first if all, we have two different ways to evaluate the code: One thing is syntax highlighting in the GUI, the other is parsing it in logic to create a usable layout. This is needed to create the preview of the changes, if you switch tabs from Edit to Preview.
Right now, both is triggered on every change. And parsing takes a lot of resources.

So my suggestion would be to change the listener on every text change (PreviewTabViewModel line 94-99) to when someone changes to the preview tab and to parse it when the layout is to be saved.

@yinpeiqi
Copy link
Contributor Author

I think I got what you mean. So the listener should not be bind there and we don't need that listener. We should call the setText function only when we click 'preview' or 'save', am I right?

@calixtus
Copy link
Member

I guess so.

@xiongz-c
Copy link

I think we can add try-catch when we use parse() in getLayoutFromText(). Then we can handle the StringIndexOutOfBoundsException when the text changed. After discussing with yinpeiqi, we think this way can fix this issue.

@calixtus
Copy link
Member

No, please don't touch the TextLayout.
Try first the solution about the overused ChangeListener first.

@Siedlerchr Siedlerchr changed the title fix: for issue 7526 fix: fix exception in custom preview layout change Mar 19, 2021
@xiongz-c
Copy link

xiongz-c commented Mar 21, 2021

You are right. If I use try-catch in getLayoutFromText() just to hide this error from the user, it does not prevent it from happening, and it may affect other places where this method is used. I think the modification logic you mentioned is feasible.

I think we have two different logic to fix this issue: One is to keep the original change monitoring method, but do not call setText when a possible error situation is encountered during monitoring, and the other is to call the setText method when leaving the preview tab or press save button. These two methods have their own problems: the first method will cause the user to discard the changes without prompting the user when an error is encountered. The second method requires additional methods when saving. As I looked into a code a bit, the save of preference is the same method that is reused, which means that we need to call setText once whenever we use the save button. This means that in most cases the save button will execute a meaningless method.

But as a user, I think I would prefer the second way because such repeated calls are not very perceptive, and important error prompts will not be missed. What do you think about it? @calixtus

**update: ** sorry, the second problem does not exist. I can override the method validateSettings() to check the value when saving. So, my way to solve this problem is to add try-catch in the listener to avoid throw error when typing, and add the valid check into the method validateSettings()

@calixtus
Copy link
Member

I believe a try-catch clause isn't necessary. The problem is imho the changelistener parsing the whole text after every text change. This isn't necessary at all, but requires a lot of processing power.
The syntax highlighting is controlled directly by the codearea in the richtextfx-package. So we don't have to care about this.
The other thing is the parsing of the text into the TextLayout, which should only happen if one want's to save the changes (and if the parsing fails there really should be an error message) and if you click on the preview tab, so you can live preview your changes before saving.
Like you said, this first problem (or for you the second) can be solved by calling for this check on validateSettings.
For the other problem I believe a changelistener depending on the visible state of the previewTab should be sufficient.

Copy link
Member

@calixtus calixtus left a comment

Choose a reason for hiding this comment

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

Three small problems

@@ -304,6 +316,7 @@ public void selectedInChosenDown() {

public void resetDefaultLayout() {
PreviewLayout defaultLayout = findLayoutByName("Preview");
System.out.println("resetDefaultLayout");
Copy link
Member

Choose a reason for hiding this comment

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

Please use our logger infrastructure.

Choose a reason for hiding this comment

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

sorry, just for debug. I will remove it.

@@ -110,6 +114,13 @@ public PreviewTabViewModel(DialogService dialogService, PreferencesService prefe
);
}

public void checkParseWhenSave() {
Copy link
Member

Choose a reason for hiding this comment

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

Why public?

Comment on lines 117 to 122
public void checkParseWhenSave() {
var currentLayout = getCurrentLayout();
if (currentLayout instanceof TextBasedPreviewLayout) {
((TextBasedPreviewLayout) currentLayout).setText(sourceTextProperty.getValue().replace("\n", "__NEWLINE__"));
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Code duplication

@xiongz-c
Copy link

I don't know why I failed some fetcher test. I think I have roughly fixed this issue, what else can I do now? @calixtus

@calixtus
Copy link
Member

The failing fetcher tests are currently a known problem. You can ignore them.

@@ -260,6 +260,7 @@ private void parseField() throws IOException {
}
throw new StringIndexOutOfBoundsException(
"Backslash parsing error near \'" + lastFive.toString().replace("\n", " ") + '\'');

Copy link
Member

Choose a reason for hiding this comment

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

Please do not add an empty line

Copy link
Member

@koppor koppor left a comment

Choose a reason for hiding this comment

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

This quick fix fixes the issue, but leads to less maintainble code. Please introduce

public Optional<Layout> getLayoutFromText() {

try {
layoutParse();
} catch (StringIndexOutOfBoundsException e) {
// catch this error but not give to user [when typing]
Copy link
Member

Choose a reason for hiding this comment

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

Catching runtime excpetions is a very bad thing. Where exactly is the StringIndexOutOfBoundsException thrown? Exceptions should be catched as early as possible.

In this case, it is thrown at parse() in LayoutHelper.java line 191.

The real fix is that org.jabref.logic.layout.LayoutHelper#getLayoutFromText should return Optional<Layout> and not throw any exception.

Copy link

@EricLee543 EricLee543 Apr 23, 2021

Choose a reason for hiding this comment

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

Hello, sir. I am also following this issue. Do you think this exception StringIndexOutOfBoundsException is unnecessary? This StringIndexOutOfBoundsException will be thrown when '\' is entered(in editing stage).
What if it's in the saving stage? If user click the save button, and there is only a '\', should the exception still be thrown?
If we do not throw any exception, the user may not be aware that there is a wrong '\'. If you insist that throwing no exception is a better choice, I can help you implement it. Don't worry about @yinpeiqi and @zc-BEAR. I have communicated with them. Looking forward to your reply.

Choose a reason for hiding this comment

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

After my investigation, Layout could not be null. Therefore LayoutHelper#getLayoutFromText seems not effective. In my opinion, we can delete the code
throw new StringIndexOutOfBoundsException( "Backslash parsing error near \'" + lastFive.toString().replace("\n", " ") + '\''); in LayoutHelper#parseField.
I also found that Layout's constructor will warn "Nested field/group entries are not implemented!" in logger if the user input's invalid. In this case, does the error message need to be presented to the user? @koppor

Copy link
Member

Choose a reason for hiding this comment

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

It's not about Layout being null, its about getting rid of the exception. Using exceptions for control flow is a bad thing (see also Java-by-Comparison). Please refactor LayoutHelper not to throw any exception at parse() or getLayoutFromText().

Copy link
Member

@koppor koppor Jun 27, 2021

Choose a reason for hiding this comment

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

@EricLee543 I am not sure how to present that to the user. In the long run, we should switch to Apache Velocity as this shifts maintenance effort from JabRef to another project. See koppor#392 for details.

@koppor koppor added the status: changes required Pull requests that are not yet complete label Apr 12, 2021
@koppor
Copy link
Member

koppor commented Jun 27, 2021

Thank you for working on this and the discussion on Optionals vs. Exceptions. I took the freedom to take over. First of all, I created test cases to really debug the cause and be sure that future changes do not break the contracts.

To free your energy for other work, I take the freedom to close this PR.

Follow-up at #7851.

@koppor koppor closed this Jun 27, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: changes required Pull requests that are not yet complete
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants