Bug 430303 - import group sorting is broken
Summary: import group sorting is broken
Status: VERIFIED FIXED
Alias: None
Product: JDT
Classification: Eclipse Project
Component: Core (show other bugs)
Version: 4.4   Edit
Hardware: PC Linux
: P3 normal (vote)
Target Milestone: 4.5 M6   Edit
Assignee: John Glassmyer CLA
QA Contact: Manoj N Palat CLA
URL:
Whiteboard:
Keywords: greatfix
: 423202 (view as bug list)
Depends on: 71761 318437 360789 412929 457051 457089
Blocks: 459320 460484 494691 506391
  Show dependency tree
 
Reported: 2014-03-13 12:47 EDT by John Glassmyer CLA
Modified: 2017-04-12 09:40 EDT (History)
8 users (show)

See Also:
manoj.palat: review+


Attachments
Modified Patch set 4 (286.06 KB, patch)
2015-01-30 03:09 EST, Manoj N Palat CLA
no flags Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description John Glassmyer CLA 2014-03-13 12:47:29 EDT
The sorting of imports according to configured "import groups" during Import Rewrite is broken; it produces inconsistent and incorrect results.

The problem is with how imports not matching any defined "import group" are sorted, in the absence of a match-all ("*") import group.

The current behavior (not documented in user documentation or UI, but referred to within internal code as "best match") attempts to place each unmatched import adjacent to or between the lexicographically closest import group (e.g. by placing "import java.new;" after a "java.net" import group). However good this idea may or may not sound in theory, it does not work in practice, and I have identified three different ways in which the existing implementation produces clearly undesirable results.

-----

1. An example of one of the ways in which this behavior is broken:

Configured import order:
java.net
com.google

Types referenced in source file:
com.acme.BirdSeed
com.acme.Dynamite
com.google.Tgif
java.net.Socket
java.new.Bar
org.linux.Kernel

Output of Organize imports, given the above:
import java.net.Socket;

import java.new.Bar;
import com.acme.BirdSeed;
import com.acme.Dynamite;

import com.google.Tgif;

import org.linux.Kernel;

The "best match" import sorting places java.new after java.net, and places com.acme before com.google, but it places java.new before com.acme within one block. This partially ordered block of imports is clearly not desirable.

-----

Two other ways in which the existing behavior is clearly wrong (I can provide examples):
- Because of the way the existing code buckets imports within import groups, the order of unmatched imports produced by Organize Imports is dependent upon the order of source file contents (e.g. reordering fields declared to be of various imported types will change the output of Organize Imports).
- "Best match" sorting of unmatched imports sometimes places static and non-static unmatched imports (which have nothing in common and have no rational reason to be placed with each other) together in one block of imports.

I have nearly completed a re-implementation of ImportRewriteAnalyzer which fixes this and several other issues.

(I previously filed Bug 423202 asking either to change the existing sorting behavior or to better document it, but have since come to see that the existing behavior is broken in more ways than I had previously realized and is not worth documenting as-is.)
Comment 1 John Glassmyer CLA 2014-03-13 12:47:51 EDT
*** Bug 423202 has been marked as a duplicate of this bug. ***
Comment 2 John Glassmyer CLA 2014-12-01 10:35:10 EST
A question for JDT experts:

I have noticed that Organize Imports preserves the comments of deleted imports (by moving them the end of the imports region), but that an incremental import rewrite (e.g. as part of a refactoring) simply deletes such comments.

I cannot find a justification for this difference in behavior and would like to make the behavior consistent (i.e., either preserve comments of removed imports in both cases or delete comments of removed imports in both cases) unless there is a good reason for the difference.
Comment 3 Manoj N Palat CLA 2014-12-02 23:30:15 EST
(In reply to John Glassmyer from comment #2)
> A question for JDT experts:
> 
> I have noticed that Organize Imports preserves the comments of deleted
> imports (by moving them the end of the imports region), but that an
> incremental import rewrite (e.g. as part of a refactoring) simply deletes
> such comments.
> 
> I cannot find a justification for this difference in behavior and would like
> to make the behavior consistent (i.e., either preserve comments of removed
> imports in both cases or delete comments of removed imports in both cases)
> unless there is a good reason for the difference.

Organize imports is expected to preserve the comments - Please see the related discussions in bug 24804, bug 378024. For incremental deletion, the trailing comments are deleted now - I would say we can keep the comments to keep the same behavior for incremental deletions as well.
Dani: Any comments on/ issues with the above?
Comment 4 Dani Megert CLA 2014-12-03 04:11:51 EST
(In reply to Manoj Palat from comment #3)
> (In reply to John Glassmyer from comment #2)
> > A question for JDT experts:
> > 
> > I have noticed that Organize Imports preserves the comments of deleted
> > imports (by moving them the end of the imports region), but that an
> > incremental import rewrite (e.g. as part of a refactoring) simply deletes
> > such comments.
> > 
> > I cannot find a justification for this difference in behavior and would like
> > to make the behavior consistent (i.e., either preserve comments of removed
> > imports in both cases or delete comments of removed imports in both cases)
> > unless there is a good reason for the difference.
> 
> Organize imports is expected to preserve the comments - Please see the
> related discussions in bug 24804, bug 378024. For incremental deletion, the
> trailing comments are deleted now - I would say we can keep the comments to
> keep the same behavior for incremental deletions as well.
> Dani: Any comments on/ issues with the above?

Right, not just "can keep", but "must keep".
Comment 5 John Glassmyer CLA 2014-12-03 14:46:16 EST
(In reply to Manoj Palat from comment #3)
> Organize imports is expected to preserve the comments - Please see the
> related discussions in bug 24804, bug 378024.

I have looked at 24804 and 378024, and those bugs seem to address different issues than the one I am asking about; specifically, they address the loss and/or relocation of comments associated with preserved imports (i.e. imports present before and after the import rewrite).

There is no question that the comments of preserved/non-removed imports must be preserved with their imports.

What I am asking about, on the other hand, is what should be done with the comments associated with imports removed by the import rewrite. Currently, Organize Imports keeps the comments of removed imports and moves them to the end of the import region, but an incremental import rewrite discards the comments of removed imports altogether. I am wondering whether this difference in behavior is accidental.
Comment 6 Dani Megert CLA 2014-12-03 15:17:31 EST
(In reply to John Glassmyer from comment #5)
> (In reply to Manoj Palat from comment #3)
> > Organize imports is expected to preserve the comments - Please see the
> > related discussions in bug 24804, bug 378024.
> 
> I have looked at 24804 and 378024, and those bugs seem to address different
> issues than the one I am asking about; specifically, they address the loss
> and/or relocation of comments associated with preserved imports (i.e.
> imports present before and after the import rewrite).
> 
> There is no question that the comments of preserved/non-removed imports must
> be preserved with their imports.
> 
> What I am asking about, on the other hand, is what should be done with the
> comments associated with imports removed by the import rewrite. Currently,
> Organize Imports keeps the comments of removed imports and moves them to the
> end of the import region, but an incremental import rewrite discards the
> comments of removed imports altogether. I am wondering whether this
> difference in behavior is accidental.

Personally, I'd like them to be preserved in their (previous) relative position in both cases.
Comment 7 John Glassmyer CLA 2014-12-03 16:09:48 EST
(In reply to Dani Megert from comment #6)
> Personally, I'd like them to be preserved in their (previous) relative
> position in both cases.

Probably this has not been done before because guessing the correct position for a comment (when the associated import is removed) is not generally possible. See #17 on bug 24804.

What I have done so far (based on observation of existing behavior and tests) is to relocate comments of single imports to corresponding on-demand ones, when single imports are folded into on-demands, and vice-versa, when on-demands are expanded into single imports, and to relocate comments of otherwise removed imports to the end of the import region. This much can be done predictably and consistently, at least.

Alternatively, perhaps it's more elegant to do in the case of Organize Imports what is already done in the case of an incremental rewrite, and simply discard the comments associated with removed import declarations. This mirrors what is done with the javadoc and comments of types and class members removed from a compilation unit (i.e. the comments are simply discarded along with their associated elements). Is there a reason to treat the comments of import declarations differently than the comments of other Java elements in this respect?
Comment 8 John Glassmyer CLA 2014-12-03 17:41:50 EST
Actually, thinking more about what I've just written, perhaps it's not even responsible to relocate comments following the folding and expansion of on-demand imports, as comments that are meaningful for an on-demand won't necessarily be meaningful for corresponding single imports, and vice-versa.

Now I'm thinking that comments of removed imports should always either be moved to the end of the import region, or be discarded altogether, with the latter option being more consistent with what's done for the comments of other removed Java elements.
Comment 9 John Glassmyer CLA 2015-01-14 13:22:22 EST
Change to jdt.core are here:
https://git.eclipse.org/r/39615

Change to jdt.ui (fixing tests) is here:
https://git.eclipse.org/r/39616
Comment 10 Manoj N Palat CLA 2015-01-15 22:40:42 EST
(In reply to John Glassmyer from comment #9)
> Change to jdt.core are here:
> https://git.eclipse.org/r/39615
> 
John: Thanks for the patch! I am looking at the test cases in the patch and see that you have addressed several issues/bugs in the patch. For the sake of clarity and to make the review more effective, please 

a) list down here what all are the changes (item-wise) that have been addressed by this patch.

b) For all the bugs that you have addressed in this patch, add the bug number in the "Depends on" entry of this bug.
Comment 11 Manoj N Palat CLA 2015-01-16 00:10:16 EST
John: Here is the first glance comments based on test file:

- The following tests are added new in the patch. However, they pass even without the rest of the patch. If they are additional tests checking the existing functionality, please document it here; else if these test new functionality please correct and repost:

- testOrganizeNoImportsWithOneLineDelim
- testOrganizeNoImportsWithTwoLinesDelim
- testOrganizeNoImportsWithJavadoc
- testNoEdits
- testAddImportWithCommentBetweenImportsAndType


In testAddImports_bug121428(), "package pack1;" statement is removed both in the input and the expected output. 1) Is there a reason for this removal? 2)If this is not relevant, please revert this 3) There are similar changes elsewhere in the file - please have a look at them and address as mentioned. [A concatenation as done in testPackageInfo is fine though]

- A general comment : If there are non-relevant changes that comes under code cleanup, please bundle them together and upload as a separate patch if found required.

- testAddImports_bug_24804_5 is removed. why?
Comment 12 Manoj N Palat CLA 2015-01-16 01:24:02 EST
Pasting John's gerrit commit message here for the sake of completeness

Rewrite of ImportRewriteAnalyzer, fixing several issues.

Fixes the following Eclipse bugs:
Bug 71761 - [import rewrite] ImportRewrite should let me add explicit import to existing on demand import
Bug 318437 - Organize Imports ignores Number of Imports needed for .*
Bug 360789 - Organize imports changes static imports to .* even when that introduces compile errors
Bug 412929 - [organize import] Adding a type results in adding a package and later does not honor order
Bug 430303 - import group sorting is broken
Bug 457051 - comment is discarded when reducing imports to an on-demand import
Bug 457089 - imports are improperly reordered in the presence of a floating comment
Some changes in behavior, which are reflected in the tests:
ImportRewriteAnalyzer no longer attempts to place unmatched imports into their "best match" import groups, instead always placing all unmatched imports together in a single match-all group.
The relocation and/or removal of comments associated with removed imports has been made more consistent. The behavior of removing comments has been made consistent between the two rewrite modes (incremental mode and "Organize Imports" mode): if there is a corresponding on-demand import for a removed single import (or vice-versa), comments are moved there; otherwise they are removed entirely. (This behavior is also more consistent with the removal of comments when deleting e.g. a field or a type.)
Comments relocated from single import(s) to an on-demand import (or vice-versa) are now always placed on line(s) preceding the destination import, even if they had been trailing comments. This behavior makes more sense especially in cases where multiple single imports are reduced into an on-demand import or vice-versa.
The scope of the fix for bug 121428 has been reduced, in that comments after a package declaration are no longer treated as file header comments. This is clearly beneficial in testBug376930_4, where the leading comments of java.util.* are no longer apparently reassigned to java.io.*.
Blank lines around and between comments are now preserved, e.g. in testBug378024c_1. This preserves existing formatting and prevents comments from becoming associated with different imports.
Change-Id: I1ae4edb45f7dc19f4263410038a9d023ac2fe054
Signed-off-by: John Glassmyer <jogl@google.com>
Comment 13 John Glassmyer CLA 2015-01-20 15:41:46 EST
Thanks, Manoj. To answer your questions:

- The five tests you mention (testOrganizeNoImportsWithOneLineDelim, testOrganizeNoImportsWithTwoLinesDelim, testOrganizeNoImportsWithJavadoc, testNoEdits, testAddImportWithCommentBetweenImportsAndType) do indeed test
existing/preserved functionality.

- In testAddImports_bug121428, the test is changed so that the compilation unit no longer contains a package declaration. This is intentional. The idea is that the fix for bug 121428 was overly broad, and as part of this change I have reduced the scope of that fix. That bug (121428) was about the handling of file header comments. You can see that the code snippet in the original bug report does not include a package declaration, and that this led to ambiguity as to whether comments preceding the first import were file header comments or import comments. The existing fix for the bug is to treat all comments before the first import as file header comments, even if they occur after a package declaration. I have reduced the scope of this fix so that comments occurring after a package declaration are no longer considered to be header comments. This helps to keep the first import's comments together with that import when imports are moved or imports are inserted before it. I briefly mentioned this change in the commit description.

- You also mentioned "similar changes elsewhere in the file". Please point these out so that I can address them specifically.

- It was not my intent to do non-relevant code cleanup with this change. Please point out any cases where I seem to have made unnecessary "cleanup"-type changes so that I can address them.

- testAddImports_bug24804_5 is removed. This test expected comments of totally removed (e.g. not folded into an on-demand import) imports to be preserved in the compilation unit, and moved to the end of the imports region, in Organize Imports mode (but not in incremental rewrite mode). As noted in comments #2 through #7 on this bug, and as mentioned in the commit description, I have changed the handling of such removed-import comments in Organize Imports mode to be consistent with that in incremental rewrite mode, so that the comments of removed imports are not preserved. Thus, testAddImports_bug24804_5 is no longer relevant.
Comment 14 Manoj N Palat CLA 2015-01-27 04:09:32 EST
(In reply to John Glassmyer from comment #13)

Thanks for the detailed reply. There was only one pair of changes - as described for testAddImports_bug121428. One minor point is that though pack1 is removed, it is still created with IPackageFragment pack1. The non-relevant code changes meant code cleanup changes such as moving the "\n" to the preceding line as in some of the tests. These can be ignored. I have reviewed the code and have the following comments.
- Well written Code - Thanks!
- Compilation error due to the use of  StringBuilder in ImportRewriteTest:testAddImportWithCommentBetweenImportsAndType since the project corresponding is still in 1.4. Please use StringBuffer instead [Bug 458098 would move this project to 1.6 soon though]
- ImportRewriteTest:testCuInDefaultPackageWithNoExistingImports failed - please check.
- in ImportRewriteAnalyzer:subtractImports(), the remainingImports.removeAll(importsToSubtract); statement does not have an effect?[since the remainingImports is populated only if the entry is not present in importsToSubtract].

 We need to submit this patch for CQ process to get approval and hence need to be submitted soon [read in a couple of days or so]. Lets target to get this in for the review asap.
Comment 15 John Glassmyer CLA 2015-01-28 20:12:12 EST
Thanks, Manoj. I have updated the two changes to fix several more failing jdt.ui tests, which unfortunately I only discovered recently. Some of these were cases where the new test results were clearly superior, but a few of them revealed unhandled cases in my code. I have added unit tests covering the latter cases to ImportRewriteTest in jdt.core.

As for the issues you raised:
- I have replaced StringBuffer with StringBuilder in ImportRewriteTest:testAddImportWithCommentBetweenImportsAndType.
- I have been unable to reproduce the failure you saw of ImportRewriteTest:testCuInDefaultPackageWithNoExistingImports. If it continues to fail for you, please post more details (JUnit stack trace, etc.) so that I can investigate.
- I have removed the unnecessary call to removeAll(). Thanks for catching that!
Comment 16 Manoj N Palat CLA 2015-01-28 20:42:43 EST
(In reply to John Glassmyer from comment #15)
> 
> - I have been unable to reproduce the failure you saw of
> ImportRewriteTest:testCuInDefaultPackageWithNoExistingImports. If it
> continues to fail for you, please post more details (JUnit stack trace,
> etc.) so that I can investigate.

Thanks John for the modifications; I will take look. Meanwhile the issue I see in the testCuInDefaultPackageWithNoExistingImports() is that an addition "\r" character is being added just before the "\n" character in all the lines which causes the test failure [and hence visibly the output looks exactly same as the expected one but during comparison it fails].
Comment 17 John Glassmyer CLA 2015-01-29 13:03:54 EST
(In reply to Manoj Palat from comment #16)
> Thanks John for the modifications; I will take look. Meanwhile the issue I
> in the testCuInDefaultPackageWithNoExistingImports() is that an addition
> "\r" character is being added just before the "\n" character in all the
> lines which causes the test failure [and hence visibly the output looks
> exactly same as the expected one but during comparison it fails].

I'm guessing that you were running the test on Windows (whereas I was running it on Linux). The compilation unit source in that test doesn't contain any newlines, so findRecommendedLineSeparator was falling back to the system default line separator.

I've resolved this by setting the Platform "line.separator" preference for the project in test setup. I'm not too familiar with how preferences work; please let me know if there's a better way to do this.
Comment 18 Manoj N Palat CLA 2015-01-29 19:51:20 EST
Noopur: can you please review the changes in jdt.ui as mentioned in comment 9? They are only test changes.
Comment 19 Manoj N Palat CLA 2015-01-30 03:09:41 EST
Created attachment 250373 [details]
Modified Patch set 4

(In reply to John Glassmyer from comment #17)
> I'm guessing that you were running the test on Windows (whereas I was
> running it on Linux). The compilation unit source in that test doesn't
> contain any newlines, so findRecommendedLineSeparator was falling back to
> the system default line separator.
> 
> I've resolved this by setting the Platform "line.separator" preference for
> the project in test setup. I'm not too familiar with how preferences work;
> please let me know if there's a better way to do this.

Yes, I am running on Windows. Ideally, you should not be changing the test setup, rather you can use the line.separator (using System.getProperty("line.separator")) and use that string as delimiters. 

The patch set 4 does not have this failure though. However, there are a couple of points to be noted:

a) StringBuilder still crops up in 10 places (these are the new tests that are added). I have changed them in the attachment.

b) ImportEditor: lines [420 - 431] are deemed dead code. I have removed them in the attachment. However, you may want to have a look at the same and confirm that these do not hide any missing functionality.

The attachment essentially is patch 4 with the above two changes. pl let know on point b. I have requested jdt.ui to have a look on the jdt.ui changes patch and once they give a green, we can submit this for CQ review.
Comment 20 Noopur Gupta CLA 2015-01-30 05:25:54 EST
Taking jdt.core patch from comment #19 and jdt.ui patch set 2 from comment #9, running all JDT UI tests on Windows shows 6 test failures. Run  org.eclipse.jdt.ui.tests.refactoring.ccp.AllTests to see the failures.
Five are due to newline differences after import and one is due to additional import not removed after move refactoring.
JDT UI patch can be reviewed after all the tests are green.
Comment 21 Manoj N Palat CLA 2015-01-30 07:52:59 EST
CQ submitted: https://dev.eclipse.org/ipzilla/show_bug.cgi?id=9210
Comment 22 Manoj N Palat CLA 2015-01-30 07:57:00 EST
(In reply to Manoj Palat from comment #21)
> CQ submitted: https://dev.eclipse.org/ipzilla/show_bug.cgi?id=9210

John: Please note that the fix can be committed only after all the tests are green. If the failures are less and minor then it can be committed but with a follow-up bug to address the failures (this is subject to the seriousness of failures). CQ process has been initiated in parallel due to the upcoming deadline and also assuming that the fixes to the failures will be minor.
Comment 23 Manoj N Palat CLA 2015-02-02 22:38:22 EST
(In reply to Manoj Palat from comment #22)
> (In reply to Manoj Palat from comment #21)
> > CQ submitted: https://dev.eclipse.org/ipzilla/show_bug.cgi?id=9210
> 
> John: Please note that the fix can be committed only after all the tests are
> green. If the failures are less and minor then it can be committed but with
> a follow-up bug to address the failures (this is subject to the seriousness
> of failures). CQ process has been initiated in parallel due to the upcoming
> deadline and also assuming that the fixes to the failures will be minor.

CQ approval is done.
Comment 24 John Glassmyer CLA 2015-02-03 12:59:14 EST
Thanks, Noopur. I have fixed the broken refactoring.ccp tests. Previously I had been running the wrong "all tests" suite. Unfortunately it took me a while to address the failures you pointed out, as attempting to run the refactoring.ccp tests was hanging Eclipse and I was only able to run the tests by temporarily replacing RefactoringTestSetup with Java16Setup in each test class. Though now they seem to run fine even with RefactoringTestSetup.

I have made the changes Manoj suggested to the jdt.core patch, as well.
Comment 25 Noopur Gupta CLA 2015-02-06 06:56:22 EST
(In reply to John Glassmyer from comment #24)
> Thanks, Noopur. I have fixed the broken refactoring.ccp tests.
> 
> I have made the changes Manoj suggested to the jdt.core patch, as well.

Thanks John, all JDT UI tests are now green. However, I see unnecessary import is added in the following tests that results in "import never used" warning:

org.eclipse.jdt.ui.tests.refactoring/resources/InferTypeArguments/testCuGetTakeClassStayRaw/out/A.java

org.eclipse.jdt.ui.tests.refactoring/resources/MultiMove/test2/out/p2/C.java
Comment 26 John Glassmyer CLA 2015-02-06 12:45:23 EST
(In reply to Noopur Gupta from comment #25)
> Thanks John, all JDT UI tests are now green. However, I see unnecessary
> import is added in the following tests that results in "import never used"
> warning:
> 
> org.eclipse.jdt.ui.tests.refactoring/resources/InferTypeArguments/
> testCuGetTakeClassStayRaw/out/A.java
> 
> org.eclipse.jdt.ui.tests.refactoring/resources/MultiMove/test2/out/p2/C.java

Thanks, Noopur.

In InferTypeArgumentsTests.testCuGetTakeClassStayRaw, jdt.ui refactoring code is explicitly adding an import of a member type (p.A.Z) of the compilation unit's main type. ImportRewriteAnalyzer does not know whether it is safe to omit this import (e.g. it could be needed by another top-level class in the same compilation unit). In this case, the higher-level jdt.ui refactoring code should be changed to either not call addImport for this member type, or to use an ImportRewriteContext when doing so that is capable of recognizing that the import is not needed.

Regarding MultiMoveTests.test2, I have changed ImportRewriteAnalyzer to reduce to on-demands (.* imports) during incremental rewrites not only when adding imports to that import container but also when removing imports from that import container. Doing so causes the unnecessary import to be removed in this case.
Comment 27 John Glassmyer CLA 2015-02-18 13:20:51 EST
I have addressed all of the concerns raised here. Is this good to submit?
Comment 28 Manoj N Palat CLA 2015-02-18 22:35:11 EST
(In reply to John Glassmyer from comment #27)
> I have addressed all of the concerns raised here. Is this good to submit?

Markus is taking a look at the ui patch - would wait to see if he has any comments.
Comment 30 shankha banerjee CLA 2015-03-18 10:06:02 EDT
Verified for 4.5 M6 with build I20150316-2000.