Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

fix: print each resource in CtTryWithResource exactly once and retain separator #4309

Merged
merged 10 commits into from
Dec 1, 2021

Conversation

algomaster99
Copy link
Contributor

@algomaster99 algomaster99 commented Nov 25, 2021

Reference: ASSERT-KTH/sorald#600

The second resource in the catch block, BufferedWriter writer = newBufferedWriter(outputFilePath, charset), is printed twice. For example,

        try (ZipFile zf = new ZipFile(zipFileName);
             BufferedWriter writer = newBufferedWriter(outputFilePath, charset)) { }

is changed to

        try (ZipFile zf = new ZipFile(zipFileName);
             BufferedWriter writer = newBufferedWriter(outputFilePath, charset);BufferedWriter writer = newBufferedWriter(outputFilePath, charset)) { }

when printed using the sniper printer.

I think it is happening because we add a separator, ;, after first BufferedWriter writer = newBufferedWriter(outputFilePath, charset) which changes muted from true to false. Although the separator is not needed here, it is still printed. This may be similar to #4306 .

Please note that muted should remain true till we print the second resource because all resources are printed at once because of this line here. Basically, if a collection fragment is not modified, we print it at once and set muted to true for upcoming elements in the collection.

@algomaster99
Copy link
Contributor Author

The context is pushed before printing the resources, however that same context is not used when we are printing the separator. Apparently, sfc.knowsHowToPrint returns false when we are printing ;. Probably, the problem lies there.

@algomaster99
Copy link
Contributor Author

algomaster99 commented Nov 26, 2021

The context is pushed before printing the resources, however that same context is not used when we are printing the separator. Apparently, sfc.knowsHowToPrint returns false when we are printing ;. Probably, the problem lies there.

I went along these lines and pushed a fix for it.

I debugged where the context was lost and it was in knowsHowToPrint method. With the changes, I am ensuring that context doesn't get lost if the sniper printer is printing a ; while it is printing the resources. I have a feeling that such a fix will also help patch #4306 .

One major problem I see in this patch is that I am hard-coding ; in an abstract code that doesn't sound logically coherent. There is a probably a better way to do it and I hope to discover that through reviews.

@slarse @MartinWitt @monperrus , could you please review it?

@algomaster99 algomaster99 marked this pull request as ready for review November 26, 2021 14:40
@algomaster99 algomaster99 changed the title fix: print each resource in CtTryWithResource exactly once fix: print each resource in CtTryWithResource exactly once and retain separator Nov 26, 2021
Comment on lines 77 to 85
List<SourceFragment> elementSourceFragments = childFragments.stream()
.filter(fragment -> fragment instanceof ElementSourceFragment)
.collect(Collectors.toList());
for (SourceFragment sourceFragment: elementSourceFragments) {
ElementSourceFragment elementSourceFragment = ((ElementSourceFragment) sourceFragment);
return elementSourceFragment.getRoleInParent() == role;
}
return false;
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is really hard to read and should be refactored in a single loop. A loop that always returns and doesn't loop is kinda unexpected.

Comment on lines 56 to 57
} else if (tpe.getToken().equals(";")) {
if (checkIfPartOfRole(CtRole.TRY_RESOURCE)) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Collapse

Suggested change
} else if (tpe.getToken().equals(";")) {
if (checkIfPartOfRole(CtRole.TRY_RESOURCE)) {
} else if (tpe.getToken().equals(";") && checkIfPartOfRole(CtRole.TRY_RESOURCE)) {

@@ -68,6 +73,17 @@ public boolean knowsHowToPrint(PrinterEvent event) {
throw new SpoonException("Unexpected PrintEvent: " + event.getClass());
}

private boolean checkIfPartOfRole(CtRole role) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Just reading the invocation of this method, I have no idea of what its intention is. Check if what is part of a role? What does it mean to be part of a role?

check is also usually reserved for methods that throw. For methods that are meant to return a boolean, I'd recommend starting with is, has etc.

Looking at the method body, this method returns true if the first ElementSourceFragment of childFragments matches the provided role. Otherwise it returns false. Can you explain those semantics? I also agree with @MartinWitt that the method needs refactoring, but first I want to try to understand its intent.

@algomaster99
Copy link
Contributor Author

@MartinWitt @slarse The intent of the method is to check if the role of the current ElementSourceFragments of childFragments is the passed role (in our case - CtRole.TRY_RESOURCE).

Can you explain those semantics?

I assumed that if the first element has the role CtRole.TRY_RESOURCE, then the rest elements will also have the same role and I guess it's safe to do so because you can only used objects inside it which implement java.lang.AutoCloseable. Thus, making all of them TRY_RESOURCE.

I have explained why this code was required in the first place here.

@algomaster99
Copy link
Contributor Author

algomaster99 commented Nov 29, 2021

I have refactored the method according to what @slarse and @MartinWitt suggested. I named the method childFragmentHasSpecifiedRoleInParent because I think it describes the intent clearly although it doesn't start with has, does, or is.

Copy link
Collaborator

@slarse slarse left a comment

Choose a reason for hiding this comment

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

Added a further suggestion for clarification now that I understand the intent.

Comment on lines 74 to 83
private boolean childFragmentHasSpecifiedRoleInParent(CtRole roleInParent) {
Optional<SourceFragment> optionSourceFragment = childFragments.stream()
.filter(fragment -> fragment instanceof ElementSourceFragment)
.findFirst();
if (optionSourceFragment.isPresent()) {
ElementSourceFragment elementSourceFragment = (ElementSourceFragment) optionSourceFragment.get();
return elementSourceFragment.getRoleInParent() == roleInParent;
}
return false;
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

I assumed that if the first element has the role CtRole.TRY_RESOURCE, then the rest elements will also have the same role and I guess it's safe to do so because you can only used objects inside it which implement

That's probably safe, yes. The thingy that builds the CollectionSourceFragment (which is later fed into the collection context) groups the children by role. I don't have a great understanding of how often this check will run, but I don't think a solitary ; is printed very often. In the entire sniper printer unit test suite, it only happens 4 times, 2 of which are in your tests. So probably this is fine from a performance perspective, but it bears keeping in mind.

Reading the method title, I'm still thinking "which child fragment?". And the answer to that is of course any child fragment. I would therefore suggest a renaming like below, and also further idiomatic use of the stream.

Suggested change
private boolean childFragmentHasSpecifiedRoleInParent(CtRole roleInParent) {
Optional<SourceFragment> optionSourceFragment = childFragments.stream()
.filter(fragment -> fragment instanceof ElementSourceFragment)
.findFirst();
if (optionSourceFragment.isPresent()) {
ElementSourceFragment elementSourceFragment = (ElementSourceFragment) optionSourceFragment.get();
return elementSourceFragment.getRoleInParent() == roleInParent;
}
return false;
}
private boolean anyChildFragmentHasRole(CtRole role) {
return childFragments.stream()
.filter(ElementSourceFragment.class::isInstance)
.map(ElementSourceFragment.class::cast)
.map(ElementSourceFragment::getRoleInParent)
.map(role::equals)
.findAny()
.orElse(false);
}

I'm using findAny() here to emphasize that there's nothing special about the first element, we just assume that if any element has a given role, the others do too.

@@ -52,6 +53,8 @@ public boolean knowsHowToPrint(PrinterEvent event) {
return false;
} else if (tpe.getType() == TokenType.IDENTIFIER) {
return findIndexOfNextChildTokenByType(TokenType.IDENTIFIER) >= 0;
} else if (tpe.getToken().equals(";") && childFragmentHasSpecifiedRoleInParent(CtRole.TRY_RESOURCE)) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

With the suggested renaming, this becomes somewhat easier to interpret, I think:

Suggested change
} else if (tpe.getToken().equals(";") && childFragmentHasSpecifiedRoleInParent(CtRole.TRY_RESOURCE)) {
} else if (tpe.getToken().equals(";") && anyChildFragmentHasRole(CtRole.TRY_RESOURCE)) {

@algomaster99
Copy link
Contributor Author

So probably this is fine from a performance perspective, but it bears keeping in mind.

We can add a comment for this because it might confound future developers to see such a thing. It's true for future-me at least.

And the answer to that is of course any child fragment.

Right. We could do this with anyMatch. Thank you for refactoring the stream. I often get confused about how to club the intermediate operations. I am working on it.

I will push the changes.

@algomaster99
Copy link
Contributor Author

@slarse @MartinWitt incorporated your changes. I have also added a short comment explaining the else-if block. Please review.

@MartinWitt
Copy link
Collaborator

LGTM, if @slarse is fine with it, we can merge.

@algomaster99
Copy link
Contributor Author

@slarse reminder to merge this PR, if you are okay with the changes. :)

@slarse slarse merged commit cc935df into INRIA:master Dec 1, 2021
@algomaster99 algomaster99 deleted the try-with-resource branch December 21, 2021 14:16
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants