Bug 37432 - quick assist: invert equals() [quick assist]
Summary: quick assist: invert equals() [quick assist]
Status: RESOLVED FIXED
Alias: None
Product: JDT
Classification: Eclipse Project
Component: UI (show other bugs)
Version: 2.1   Edit
Hardware: PC Windows 2000
: P3 enhancement (vote)
Target Milestone: 3.0 M7   Edit
Assignee: Martin Aeschlimann CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2003-05-09 11:39 EDT by Adam Kiezun CLA
Modified: 2004-01-12 04:38 EST (History)
1 user (show)

See Also:


Attachments
fix (4.65 KB, patch)
2004-01-08 03:13 EST, Sebastian Davids CLA
no flags Details | Diff
fixed String concatenation, object assignment,tenary operator (4.78 KB, patch)
2004-01-08 11:32 EST, Sebastian Davids CLA
no flags Details | Diff
now handles equals(x) and x.equals(this) (3.47 KB, patch)
2004-01-08 21:25 EST, Sebastian Davids CLA
no flags Details | Diff
the test cases (44.56 KB, patch)
2004-01-12 02:27 EST, Sebastian Davids CLA
no flags Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Adam Kiezun CLA 2003-05-09 11:39:25 EDT
foo.equals(bar)
can be changed into
 bar.equals(foo)
(as long as bar is not the null literal)
Comment 1 Dirk Baeumer CLA 2003-05-09 13:06:31 EDT
Depends on 3.0 planning
Comment 2 Sebastian Davids CLA 2004-01-08 03:13:08 EST
Created attachment 7360 [details]
fix

@@ implementation notes @@

All inversions are symmetric.

b.equals((Object) o) will be inverted to ((Object) o).equals(b)
((Object) o).equals(b) will be inverted b.equals((Object) o)

@@ test notes @@

I could not find any JUnit test classes for jdt-ui :/

This is the class I used to test by hand:

public class Test extends Super {
	
	void test() {
		b.equals(c);
		sA.equals(this.b);
		"a".equals("b");
		String s= "a";
		"a".equals(s);
		"a".equals(A.get());
		"a".equals(A.A);
		"a".getClass().equals(String.class);
		boolean x= false && "a".equals(get());
		c.equals((Object) "a"); //should wrap in parentheses
		new Test().equals(this);
		
		"a".equals(null); //should not offer quick assist
		new Test().equals("a", false); //should not offer quick assist
		new Test().equals(false); //should not offer quick assist
		new Test().equals(sBool); //should not offer quick assist
		equals("a"); //should not offer quick assist
	}

	private String b= "b";
	private String c= "c";
	static String get() {
		return "a";
	}
	boolean equals(boolean a) { //should not offer quick assist
		return false;
	}
	boolean equals(Object o, boolean a) { //should not offer quick assist
		return false;
	}

	public boolean equals(Object o) { //should not offer quick assist
		return super.equals(o); //should not offer quick assist
	}
	
	static boolean equals(String a) {//should not offer quick assist
		return false;
	}
}

class Super {
	protected String sA= "a";
	protected boolean sBool= false;
}
class A {
	static String get() {
		return "a";
	}
	static String A= "a";
}
Comment 3 Sebastian Davids CLA 2004-01-08 03:17:11 EST
I'll write some unit tests once you point me to the right place :D
Comment 4 Sebastian Davids CLA 2004-01-08 11:32:01 EST
Created attachment 7368 [details]
fixed String concatenation, object assignment,tenary operator

The following are properly handled now (surrounded by parenthesis if inverted
out of the equals()):

-String concatenation
-object assignment
-tenary operator resulting in an object

Added 6 more tests:

public class Test extends Super {
	
	void test() {
		b.equals(c);
		sA.equals(this.b);
		"a".equals("b");
		String s = "a";
		"a".equals(s);
		"a".equals(A.get());
		"a".equals(A.A);
		"a".getClass().equals(String.class);
		boolean x = false && "a".equals(get());
		new Test().equals(this);

		//parenthesis cases below
		c.equals((Object) "a");
		"a".equals(s = "a");
		"aaa".equals("a" + "a" + "a");
		"a".equals(true ? "a" : "b");
		
		//below should not offer quick assist
		"a".equals(null); 
		new Test().equals("a", false);
		new Test().equals(false);
		new Test().equals(sBool);
		equals("a");
		new Test().equals(true ? true : false);
		new Test().equals(1 + 1);
		int i= 0;
		new Test().equals(i + i);
	}

	private String b= "b";
	private String c= "c";
	static String get() {
		return "a";
	}
	boolean equals(int i) { //should not offer quick assist
		return false;
	}
	boolean equals(boolean a) { //should not offer quick assist
		return false;
	}
	boolean equals(Object o, boolean a) { //should not offer quick assist
		return false;
	}

	public boolean equals(Object o) { //should not offer quick assist
		return super.equals(o); //should not offer quick assist
	}
	
	static boolean equals(String a) {//should not offer quick assist
		return false;
	}
}

class Super {
	protected String sA= "a";
	protected boolean sBool= false;
}
class A {
	static String get() {
		return "a";
	}
	static String A= "a";
}
Comment 5 Sebastian Davids CLA 2004-01-08 11:36:41 EST
The following is not symmetric:

"a".equals(("a")) -> ("a").equals("a");

("a").equals("a") -> "a".equals("a");

The extra parentheses are not necessary in the first place, so the "code
cleanup" should not hurt .)
Comment 6 Martin Aeschlimann CLA 2004-01-08 13:19:23 EST
Great, thanks! Oh, yes, please add tests! :-) In 
plugin 'org.eclipse.jdt.ui.tests' look at class 'AssistQuickFixTest' how to do 
it. 

From a quick look at the code I wasn't sure if you cover the case when
Expression left= method.getExpression(); is null
  if (equals(x)) {}  -> if (x.equals(this)) {}
Seems to me that runs in an NPE later

Also note that parentized expressions can be nested multiple times.
  ((("a"))).equals(s);

BTW: The replacing element must always be a copy
rewrite.markAsReplaced(right, left);
 ->
rewrite.markAsReplaced(right, rewrite.createCopy(left));


patch released > 20030108
Comment 7 Sebastian Davids CLA 2004-01-08 21:25:15 EST
Created attachment 7385 [details]
now handles equals(x) and x.equals(this)

equals(x) -> x.equals(this)
x.equals(this) -> equals(x)
Comment 8 Sebastian Davids CLA 2004-01-08 22:35:18 EST
Well, I really like writing test but the way they're currently written is *rolls
his eyes*

What do you think about an API 

String getFileAsString(File)

in QuickFixTest?

Having those TestClasses als plain text files instead of the ugly StringBuffer
contatenation would make writing those tests a lot easier and maintainable IMO.

I could add that method and adjust existing code if you want me to. Maybe
there's already that kind of functionality burried somewhere in the Eclipse code
base ...

If not I'll bite into the sour apple and do it by churning out those
StringBuffer lines *smiles*
Comment 9 Martin Aeschlimann CLA 2004-01-09 09:22:07 EST
I personally think the StringBuffer story is _much, much_ better. The 
refactoring tests are using the scheme you're suggesting, but I have real 
problems with that.
The thing is that this spread your tests in 3 files. If a test fails, and you 
want to debug it always takes me minutes to get the correspoding sources files 
(original and expected) and I'm swirling bewteen 3 editors.

Bite in the source apple, I'm sure you'll see what I mean. Most of it is 
copy/paste anyways. Just keep the source examples small and have a test per 
tested example.

Thanks a lot!
Comment 10 Martin Aeschlimann CLA 2004-01-09 09:26:58 EST
patch 7385 released > 20040109
Comment 11 Sebastian Davids CLA 2004-01-09 10:34:56 EST
The 3-editor-juggling is a good point didn't think of that :|

Smells like a new feature "Insert File as StringBuffer statements" to me *smiles*

It takes a file as an input and converts it to the StringBuffer scheme (one
append()-statement per line in the input.

Should make our work easier--one does not have to escape all that stuff anymore.

Once I've taken all my tests in college I could work on it.

What do you think?
Comment 12 Martin Aeschlimann CLA 2004-01-09 10:50:09 EST
Would definitly be nice to have! I think this should work with the new reg-exp 
find replace, but I'm not the expert.
I mostly do 
Format the code to using spaces, indent it once. Select the indent, 
find/replace on one of these indent, replace with buf.append(" and add the end.
Comment 13 Sebastian Davids CLA 2004-01-12 02:27:42 EST
Created attachment 7403 [details]
the test cases
Comment 14 Sebastian Davids CLA 2004-01-12 02:37:24 EST
Just an implementation note as a reminder for future questions:

>>>Also note that parentized expressions can be nested multiple times.
>>>((("a"))).equals(s);

Each invocation of this quick assist will strip one pair of unnecessary braces.

Example:

((("a"))).equals(s);
-> s.equals((("a")));
-> (("a")).equals(s); 
-> s.equals(("a")); 
-> ("a").equals(s); 
-> s.equals("a"); 
-> "a".equals(s);

It will create braces if needed:

s.equals(true ? "a" : "b");
-> (true ? "a" : "b").equals(s);
-> s.equals(true ? "a" : "b");
Comment 15 Martin Aeschlimann CLA 2004-01-12 04:38:17 EST
tests released > 20041012

Awesome, thanks a lot Sebastian! I modified the patch for the test by using a 
new 'collectAssists' method so you don't get any of 
the 'AssignToVariableAssistProposal''s.