Bug 25097 - [refactoring] Provide "Delete/Remove" refactor
Summary: [refactoring] Provide "Delete/Remove" refactor
Status: ASSIGNED
Alias: None
Product: JDT
Classification: Eclipse Project
Component: UI (show other bugs)
Version: 2.0   Edit
Hardware: All All
: P5 enhancement with 1 vote (vote)
Target Milestone: ---   Edit
Assignee: JDT-UI-Inbox CLA
QA Contact:
URL:
Whiteboard:
Keywords: helpwanted
: 45205 72924 134881 368917 (view as bug list)
Depends on:
Blocks:
 
Reported: 2002-10-19 11:26 EDT by Max Rydahl Andersen CLA
Modified: 2021-11-27 00:05 EST (History)
11 users (show)

See Also:


Attachments
see filename (39.29 KB, patch)
2003-08-14 13:26 EDT, Mariano Kamp CLA
no flags Details | Diff
see filename (27.84 KB, patch)
2003-08-14 13:26 EDT, Mariano Kamp CLA
no flags Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Max Rydahl Andersen CLA 2002-10-19 11:26:13 EDT
We got Rename and move refactoring for classes - but there does not seem to be a "Delete" 
refactoring!

Usecase: 

I have an old exception class that is not used anymore but is 
declared to be thrown multiple places - I then remove the class and now I have to manually wander 
around in the source code and remove the code that references it.
Comment 1 Martin Aeschlimann CLA 2002-10-22 05:25:30 EDT
+ 1 from me. A delete refactoring would be useful. The exception case is a good 
example.
Comment 2 Erich Gamma CLA 2002-11-16 19:46:04 EST
This would be a Safe Delete refactoring with the semantics to also delete 
references to the class (when it is safe).
The UI should present the references to the user allow to selectively decide 
which ones should be deleted.
Comment 3 Adam Kiezun CLA 2003-02-20 09:56:08 EST
to reconsider after 2.1
Comment 4 Adam Kiezun CLA 2003-04-25 11:25:56 EDT
reop
Comment 5 Adam Kiezun CLA 2003-04-25 11:26:31 EDT
external contribution opportunity
Comment 6 Mariano Kamp CLA 2003-07-15 13:57:31 EDT
I haven't seen (actually searched for) this bugreport before. 
I came up with the same idea and here are the three posts I wrote about it. 
Feedback and answer to the questions on searching are most welcome. 
 
1) The search for candidates posting 
  the idea is to remove members (classes, methods and class/instance  
variables) which are not referenced (anymore). The collected candidates for  
removal are to be displayed to let the user pick the methods s/he wants to  
remove. 
   
  Hopefully I was able to express myself clearly. If not, please ask. I am  
more than happy to try a different approach of explaining or thinking about  
it. 
 
  I believe at least the following Issues have to be taken care of... 
 
[ Simple Example ] 
 
Let's start with one of the simplest cases. 
 
      public class A { 
        private void a() {}        // referenced from inside this class 
        public  void b() { a();} // referenced from the outside? 
        private void c() {};       // never referenced 
      } 
       
This would result in the following collection of items to analyze:  
 
Name           Containing Type             Access 
of Item          and Supertypes              Modifier 
                      declaring the item 
------------------------------------------------------------ 
a()                    A                                     private 
b()                    A                                     public 
c()                    A                                     private 
 
The results of this case are pretty obvious: 
 
A.b() is not referenced from inside A. According to the public access modifier  
of A.b() the whole workspace has to be searched for items referring to this  
method. If none is found it is a candidate for removal.  
A.a() is referenced from A.b(). Assuming A.b() is referenced from the outside  
of this class, it will _not_ be a removed.  Hence A.a() will _not_ be a  
candidate for removal.  
A.c() is not referenced from inside A, there is no named inner class and as  
A.c() is declared private the search ends here. Hence it _is_ a candidate for  
removal.  
 
 
[ Taking Supertypes into Account ] 
 
An example with three super types: 
 
      interface I { 
        void i(); 
      } 
      public class SuperSuperA { 
        public void ssaa() {} 
        public void ssab() {} 
      } 
 
      abstract class SuperA extends SuperSuperA 
                          implements I { 
        void i() {} 
      } 
 
      public class A extends SuperA { 
        public void i() {} 
        public void ssab() {} 
      } 
       
Name     Containing Type  Access    Remark 
of Item    and Supertypes   Modifier 
               declaring  
               the item              
------------------------------------------------------------------------------ 
i()            A, SuperA, I          public      The  implementation of  
                                                            A.i() could be  
                                                            used  when  
                                                            referring to  
                                                            SuperA or I. 
ssab()    A, SuperA,             public      The implementation of  
                                                            A.ssab() could  
                                                            be SuperSuperA                                      
                                                            used when  
                                                            referring to  
                                                            SuperA or                                                                                  
                                                            SuperSuperA. 
 
Note that SuperSuper.ssaa() didn't make it into the collection of items as it  
is not overridden in A.  
 
Resulting in:  
As A.i() is public the whole workspace has to be searched for references to  
A.i(), SuperA.i() and I.i().  
This is also true for A.ssab(). A.ssab(), SuperA.ssab() and SuperSuperA.ssab()  
have to be searched in whole workspace.  
 
 
[ Recursive Dependencies  ] 
 
An example with recursive dependencies. 
 
      public class A  { 
        private void a() { b(); } 
        private void b() {} 
      } 
 
A.b() can not be a candidate for removal as it is referenced by A.a(). A.a()  
can be removed though, but after doing so, A.a() is also a candidate for  
removal. As the user may not select to delete A.a(), A.b() cannot be removed  
in every case. (any case? my English su... I mean sometimes it can, sometimes  
it can't). 
 
Therefore it is necessary to record the candidates differently, including the  
dependencies. 
 
Name           Containing Type          Access            Depends on 
of Ite m         and Supertypes          Modifier           the removal of 
                     declaring the item 
-------------------------------------------------------------------------------------------- 
a()                     A                               private         - 
b()                     A                               private         A.a() 
 
The collection of removal candidates is to be presented to the user in a way  
reflecting the dependencies. 
One possible implementation would be to show the candidates in a hierarchical  
view. The independent candidates are shown on the first level and the  
respective depending items are below the candidate they depend on. If a  
containing candidate is deselected all depending candidates and candidates  
depending on the latter are also deselected and cannot be selected without  
selecting the candidate they depend on.  
 
A typical example for recursive dependencies might be accessors and the  
respective instance variable or (inner) classes which are only referenced by  
a Widget using them as XXListeners and can be removed when the button reaches  
its end of life. 
 
 
[ When to stop searching for References ] 
 
Please consider the following code.  
       
      public class A  { 
        public static void aa() { } 
        public static void ab() { A.aa(); } 
                // main() and all methods 
                // of Object are blacklisted 
                // and will not be used as 
                // candidates. 
        public static void main(String[] args) {A.ab(); }  
      } 
 
      public class B { 
        public static void ba() { A.aa();} 
      } 
 
      public class C { 
        public static void ca() { A.aa();} 
      } 
 
Assumption: As A.aa() is referenced by A.ab(), B.ba() and C.ca() it is not a  
candidate for removal. 
To get this result the whole workspace must be searched as A.aa() is declared  
public, but this would mean a serious performance penalty. 
The first possible optimization would be to stop looking for more references  
after the first is found. In the am scenario that would mean to stop to after  
A.ab() is found. As a consequence A.aa() would be in a unknown state when  
A.ab() (another candidate) will not be confirmed by the user, because when  
A.ab() is not removed it is unknown if A.aa() is referenced by more callers  
than A.ab(), as the search has been stopped prematurely. 
Therefore the search can only be stopped, after the first caller is found  
which is not also a candidate. 
Hence the callers, which are also candidates, have not to be recorded in case  
a caller outside the candidate list is found.  
 
 
[ Performance ] 
 
I believe that displaying the search results and executing the removal are  
relatively cheap compared to the search, so I will just focus on the search  
for now. 
 
- general search scope -  
For non-static private members only the class itself and the named,  
non-static, inner classes have to be searched.  
The search for members declared with the package modifier will start like a  
search for private members, but will also the search the declaring package.  
Regarding protected members the private and package search has to be performed  
and furthermore all subclasses, which are not in the same package as the  
member itself. 
Public members are the worst. They can be referenced by everything. So as  
worst case the whole workspace has to be searched. 
This is just a rough outline to build on for the below mentioned issues. 
 
It would be important to show the user a tip helping him to understand that it  
is important to define an appropriate workingset, which includes only his/her  
own code, not all the libraries etc. 
 
Also the default settings should already narrow down the search, e.g. "public"  
as a search criteria should be deselected. 
 
- batching the search -  
The two factors having the most impact on the performance will likely be disk  
io and parsing, therefore an ideal search would touch each ressource only  
once and also parse it only once.  
 
- stopping early / redefining search scope -  
That would mean that search scope needs to reflect the maximum scope universe  
of all members to search for. If there are 10 members to search for declared  
private and one public this could mean to search the whole workspace. Still  
for this particular public member it is the only choice there is. 
Therefore it is necessary to suspend the search after the first reference to  
the public member is found (where the calling member is not a candidate  
itself) _and_ recalculate the maximum search scope for the remaining  
candidates (in the a.m. case it should be none, as private members should be  
searched first, but there are other cases, like having more than one public  
member to search for). 
To make a long story short, the search scope needs to be redefined during the  
search and it also must be possible for the caller to stop the search for the  
case that relevant references are found for all candidates before having  
searched the remaining scope completely. 
 
 
[ Performance :: Implementing the search using the Java Search Engine ] 
 
I had a quick peek at the JavaSearchEngine and although I am not through yet,  
I have found the OrPattern to build the maximum search scope, but haven't  
found anything yet to suspend/stop a running search and to reduce the scope  
of it. 
 
2) How the user experience should look like and the answers to some questions of 
Adam 
  The user should be able to select an arbitrary JavaElement and invoke the  
refactoring on it. I would start with methods only, but increase the scope  
later on to types, packages etc. I believe the only limitation is  
performance.  
 
  [ Searching the candidates ] 
  After selecting a JavaElement a Wizard is fired up where the scope of  
searching for _candidates_ is to be defined. Here comes an outline of some of  
the _visible_ filter criterias to select: 
 
  Selection of Candidate: 
  [ includes ] 
  access modifiers: 
  - public (off) 
  - protected (on) 
  - package (on) 
  - private (on) 
 
  other contained elements: 
  - include members of subclasses (on)  
  - etc.   
 
  by type:  
  - methods (on) 
  - anonymous and names inner classes (on) 
  - fields (on) 
  - etc. (on) 
  
  [ excludes ] 
  by member names: 
  - getters (off) 
  - setters (off) 
  - regexp method names (off) 
 
  Search for references to candidates: 
  - working set etc. (will be inspired by the java search) 
 
  Furthermore the user should be presented with a tip explaining the  
importance to narrow down the search and possibly define a working set, which  
just contains code with the potential to reference the candidates, not also  
third-party-libraries etc. It should also be mentioned what the consequences  
of a to narrow search would be. 
 
  It should also be possible to set _invisible_ excludes on a preference page.  
The defaults will include all methods from Object and the main() method. 
 
  Note: Please bear in mind, that this is only the search for candidates.  
During the search for references to the candidates the excludes and  
deselected includes will obviously not being honored. 
 
  [ Searching the candidates ] 
  Here is the beef. See my previous posting (not the accidently sent one  
though ;-). 
  When a resource cannot be read (e.g. out of sync) then the search will be  
cancelled and the user will be presented with an error message saying what  
the problem is. The same holds true, if a candidate is read-only. 
  The only unanswered question crossing my mind is how to deal with multiple  
errors? Will it make more sense to fail early or to collect all errors (might  
be very slow). I guess it makes sense to make this a preference. I believe  
the multiple-error approach would pay off in the real world. E.g. in my  
current day job project we are forced to use a scm tool utilizing the  
pessimistic approach and so this information is needed for checking out the  
affected ressources. 
 
  [ Displaying the candidates ] 
  The candidates, i.e. java elements only referenced by other candidates or  
not referenced at all, should be displayed in a tree view.  Unreferenced java  
elements not referenced by anything in the whole workspace are to be shown on  
the first level. Candidates that will only be unreferenced when another  
candidate will be removed are to be shown below the candidate they depend on.  
The next level will show the candidates depending on candidate depending on a  
top-level candidate etc. etc. 
 
  A depending candidate can be deselected independently of its parent, but can  
only be selected if the parent is also selected. If a parent is deselected  
all children will be deselected too. 
 
  Beneath the candidate will be a check box, triggering the removal. 
 
  extension (from Olivier): 
  The checkbox for removal will be replaced by a radio button or list with two  
options: "Remove Element" and "Deprecate Element". Remove Element says it  
all, deprecate element means to change the Java Doc of the element. A  
@deprecated keyword will be added an a TODO marker to be replaced by a good  
explanation why this method has been deprecated and probably what is to be  
used instead. 
  
  For this extension it would also be necessary to rethink the tree view. The  
rule would the read as follows: A child is/can only be selected when the  
parent is marked for removal, i.e. a parent marked as deprecated will still  
reference the child.  
 
  [ Executing the removal / deprecation ] 
  That looks pretty straight-forward to me. Of course it has to be checked for  
read-only files (see above), but nothing special I can think of.  
 
  OK, after the full picture back to the check list: 
 
- how do people invoke the refactoring? 
They select a JavaElement and invoke the first wizard page from the context  
menu. The refactoring will be available for all Java Elements. Well, in the  
final implementation. It would be already a worthy refactoring if would just  
work on individual methods. 
 
> - what does the ui look like? 
It's golden with red stripes from the upper left to the lower right. There  
will be nice mp3 song playing, downloaded from an illegal server, selected by  
consulting a chart list website and accounting for the current time of the  
day and if is a weekend or not ;-) There will also be a mpeg playing. The  
selection of content will be based on the users age. 
Apart from that the first page will ask the user to define the scope of  
candidate selection. 
 
> -- what is the input that the refactoring needs? 
a) The raw candidates from the selection and its children and the filter. 
b) the Java Elements in the search scope. 
 
> -- is all the input available at once, or do we need to ask additional 
> questions after some part of the analysis has been performed? 
Well, the filter wizard page does have all input. The display of the removal  
candidates will need the selection of the user how to proceed. 
 
> - what are the possible errors and how to present them to the user? 
Unreadable resources. They should be displayed altogether after the search is  
finished. Probably a preference to fail early makes sense here. 
 
> - an application has many 'root' entry points normally (elements that are 
> always 'reachable') e.g. the main method is the most obvious one, but every 
> test... method is an entry point in a class that subclasses 
You should be able to exclude some elements defined by name (regexp) on a  
preference page. 
The default settings might also include suite() etc.  
 
> everything in not-internal packages in eclipse's 
> source code is an entry point, etc. We need a way to collect that 
> information from the user in an intuitive way. 
I am not sure if I got this right ... Is it? We need to care for references  
outside of the workspace, like framework methods not used inside the  
framework or the user code in the workspace? 
I don't see a way to compute that.  
 
Or do you mean to select for example setUp() on some testcase and fear that it  
will be a candidate for removal as it is not referenced in the user code? 
That is something the user needs to know about. See the "tip" I want to  
display when defining the search scope. The user needs to know that s/he  
needs to select the junit.jar in the working set. It will be found then in  
the collection process and not removed (see other posting). 
 
On the sunny side the removal is not done automatically. The user needs to opt  
out what elements s/he doesn't want to be removed. 
 
3) And another thing for the "tip" explaining to the user the consequences of narrowing 
down the scope of the search for candidates 
I just thought about something else to tell in the "tip". 
 
It probably makes sense to exclude JUnit-Tests from the search, as if they  
would be the only ones referencing the candidate it's propably still worth to  
remove the method and the tests. I don't see that automatic support for the  
removal would be a) much needed and b) hard to implement as the tests likely  
uses the candidate to build something better.  
 
Hence when removing the candidates the test code would break (compile error).  
Which can be fine or not. The refactoring is still preserving the behaviour  
of the "productive" code. 
 
Another approach would be to change the second wizard page, presenting the  
user with the candidates for removal.  
The extension would be to also show elements which are referenced. When  
selecting a referenced member then the references will be shown in the lower  
part of the screen and the user can make an informed to decision to deprecate  
(even remove?) the element anyway. 
But I believe that would be to complicate to understand for some users. Maybe  
a setting on the search page may help. 
From my point of view this is a bit ugly and eventually something to do after  
the first release in any way. 
 
 
Comment 7 Adam Kiezun CLA 2003-07-15 15:00:01 EDT
>We need to care for references outside of the workspace, 
>like framework methods not used inside the framework 
>or the user code in the workspace? 
>I don't see a way to compute that.  

in the long run we need to care about these. 
one test case that should work is: run the refactoring on jdt.ui code and find 
all unused classes. Things contributed by plugin.xml are not referenced from 
java code but still cannot be deleted.
Comment 8 Mariano Kamp CLA 2003-07-16 16:04:01 EDT
I am not sure if I understand the topic properly. I'd like to rephrase what I got:
class SomeException extends Exception {}

class A {
  public static void doesNotRaiseSomeExceptionDirectly() throws SomeException {
        B.doesNotRaiseSomeExceptionToo();
  }
}

class B {
  public static void doesNotRaiseSomeExceptionToo() throws SomeException {
    // no reference to SomeException in here
  }
}

class C {
  public void doesRaiseSomeException() throws SomeException {
    throw new SomeException();
  }
}

1) So when selecting SomeException the refactoring should check
doesNotRaiseSomeExceptionDirectly(), descend to doesNotRaiseSomeExceptionToo(),
learn that both aren't using SomeException (anymore) and mark both as removal
candidates to let the user decide later on if s/he wants to remove them. Then
the refactoring goes into C.doesRaiseSomeException() and will show an error
message (or a summary of the references) that SomeException is still used and
that there is no way of deleting SomeException safely without the user manually
removing the offending code in C.doesRaiseSomeException?  

2) What if C wouldn't exist? The user is presented with the list of changes to
do and can deselect each proposed change individually? That would yield in the
removal of SomeException being unsafe again. So only the preliminary steps are
to be performed, but not the removal of the initially selected exception?!   
That would also mean that in case 1) the prelimary steps can be executed also.

Is that the story?

3) What should happen with comments, javadoc etc.? Sure, the javadoc around the
declaration of the exception should be eliminated, but what about references in
javadoc of a calling method, e.g. @throws? It has to go too, right? What about
just the word SomeException in a comment of a method where SomeException will be
removed from the throws clause or just about any comment refering to it by
either qualified or simple name?
Comment 9 Adam Kiezun CLA 2003-08-03 18:16:24 EDT
i will respond more fully after i get back from vaction but i think it's best 
if you first implement the first, early version and we will be able to discuss 
and improve things more concretely, on the working tests and code. If you 
attach the code here i'll be able to look at it and comment on it.
Comment 10 Adam Kiezun CLA 2003-08-14 06:41:23 EDT
I think the unused exception case is not so interesting. unused 
type/method/field/package, however, is. I'd start by implementing the first cut 
of this functionality, along with test cases.  My candidate for the first cut 
would be finiding unused classes (with classes with 'main' methods used as the 
only entry points). We could then improve incrementally (add support for real 
entry point selection, unused method detection etc. ).

I'm afraid this item will only be included if we receive it as a contribution 
from the community - otherwise it is unlikely that it will make it into the 3.0 
plan.
We can provide guidance, however.
Comment 11 Mariano Kamp CLA 2003-08-14 07:52:03 EDT
Hi Adam,

>>I think the unused exception case is not so interesting. 
? It is an exceptional case and more complicated than the others in terms of
reference usage analysis and cleaning (remove unused Exceptions from throws
clause etc.). Shortly after I tried to rephrase what I believe was the scope
from this report (16th of July), I implemented a first cut of the non-ui part.
It is handling every cases I could think of. I was cutting a lot of corners
though and the code is not cleaned up at all. There are lots of TODO markers,
which are actually questions. Maybe you can look at the code and answer one or
two of the marked questions. As the the scope just changed and the
implementation of the new scope would be very much different (no usage analyzer,
no exception usage cleaner etc.), I am not gonna clean up the old code and start
over again. 
The current code demonstrates its cases by using half a dozen tests. I'll upload
it tonight. But again ... it's not cleaned up ;-)

Last time you said to base it on SelfEncapsulateField. Is this still valid, or
would it make more sense to go for the processor/participants approach?

>I'm afraid this item will only be included if we receive it as a contribution 
>from the community - otherwise it is unlikely that it will make it into the 3.0 
>plan.
Due to constraints from my day job I am not able to commit myself to it.

>We can provide guidance, however.
Appreciated a lot.
Comment 12 Mariano Kamp CLA 2003-08-14 13:26:05 EDT
Created attachment 5770 [details]
see filename
Comment 13 Mariano Kamp CLA 2003-08-14 13:26:19 EDT
Created attachment 5771 [details]
see filename
Comment 14 Mariano Kamp CLA 2003-08-20 14:42:23 EDT
Adam, sorry, but as mentioned in my last email this communication thing doesn't seem 
to work out for me. I am out of this. 
 
Cheers. 
Comment 15 Dirk Baeumer CLA 2003-10-21 04:49:33 EDT
*** Bug 45205 has been marked as a duplicate of this bug. ***
Comment 16 Dirk Baeumer CLA 2004-08-31 06:29:54 EDT
*** Bug 72924 has been marked as a duplicate of this bug. ***
Comment 17 Dirk Baeumer CLA 2004-08-31 06:30:51 EDT
Bug 72924 asked for save delete/remove of method references if a method gets 
deleted.
Comment 18 Martin Aeschlimann CLA 2006-04-05 11:56:07 EDT
*** Bug 134881 has been marked as a duplicate of this bug. ***
Comment 19 Eclipse Webmaster CLA 2009-08-30 02:36:38 EDT
As of now 'LATER' and 'REMIND' resolutions are no longer supported.
Please reopen this bug if it is still valid for you.
Comment 20 Dani Megert CLA 2012-01-18 04:34:08 EST
.
Comment 21 Dani Megert CLA 2012-01-18 04:34:57 EST
*** Bug 368917 has been marked as a duplicate of this bug. ***
Comment 22 Paul Benedict CLA 2016-07-21 17:09:17 EDT
Ping. How about starting small? The simplest use case would be deleting a field along with its getter/setter methods. The user should be warned if: (1) the field is used outside the getter/setters or (2) the getter/setters are used outside the class.
Comment 23 Christoph Laeubrich CLA 2021-11-27 00:05:09 EST
(In reply to Dirk Baeumer from comment #17)
> Bug 72924 asked for save delete/remove of method references if a method gets 
> deleted.

It would be very useful if one wants to remove a no longer used method from an interface. Currently one has to delete the method and then remove it everywhere in implementations. If there are alot this can become a nightmare...