jdt ui R3.2.x
java development tooling ui |
![]() |
New Rename Type Feature: Update Similarly
Named Elements |
|
This document is intended for providers of participants for the rename type, method, and field refactorings. The document describes the new rename type feature "Update Similarly Named Elements" and the changes required in rename type, method, and field refactoring participants induced by this new feature. |
|
![]() |
Table of Contents:I. IntroductionII. Signalling the new changes III. Required actions IV. How to write similarly named declarations-aware participants (migration guide) |
![]() |
Introduction Starting in Eclipse 3.2 M4, the rename type refactoring is able to rename “similar declarations": methods, fields, and local variables with names similar to the name of the renamed type. For example, consider the following code: public class Foo { private Foo foo; private Foo() { } static Foo createFoo() { return new Foo(); } public Foo getFoo() { return foo; } public void setFoo(Foo foo) { this.foo= foo; } }Renaming the type Foo to Bar used to update only the type’s name and all references to it, for example the return type of createFoo() or the parameter type of setFoo(Foo foo). The new update similar declarations feature allows the user to change all method, field, and variable names corresponding to or containing the type name. For example, when renaming Foo to Bar, the code may be changed in the following way (depending on the users choices): public class Bar { private Bar bar; private Bar() { } static Bar createBar() { return new Bar(); } public Bar getBar() { return bar; }In contrast to the old type rename refactoring whose changes were limited to the type itself and its references, the new rename type refactoring creates much more changes which also affect arbitrary fields, methods, and local variables inside and outside of the renamed type. This raises the question of how to signal those changes to interested parties by loading corresponding participants. |
![]() |
Signalling the new changes The best solution would have been to simply reuse existing participants, i.e. loading
Type rename: Considering the example above, the normal rename type refactoring would only have changed the type name and the references to the type; for example, the type of the field foo and the return type of method getBar(). Rename type participants could therefore assume that all fields and methods inside the type would keep their defining attributes:
public void setFoo(Foo foo) { }Renaming the type Foo to Bar effectively changes this method to public void setBar(Bar bar) { }The changes to this method are:
As a side note, signature changes to methods have already been a problem when renaming methods like the method setFoo(Foo foo): Even in the former implementation, a signature change was performed on this method as the parameter type Foo was changed to Bar which was not signaled to the rename type participant; we also did not load a special participant for it. Method, field and local variable renames: Considering a rename of the method setFoo(Foo foo) from the example above, the normal rename method refactoring would only have changed the name of the method (and possibly of overridden methods and references). Rename method participants could therefore assume that only the name of the method changed and everything else stayed the same. In the new refactoring, this is now no longer the case. Consider the method signature change of setFoo(Foo foo) from the example above. The changes to this method are:
|
![]() |
Required Actions As outlined in section II, some existing participants will not work with the new refactoring. Because of the breaking nature of this change, we provide
<property name="org.eclipse.jdt.ui.refactoring.handlesSimilarDeclarations" value="false" />Setting the participant descriptor flag: If set, the participant will be disabled in each concrete rename type refactoring if the new option is enabled. To disable a participant in that case, please add the following code to the participant extension definition: <param name="handlesSimilarDeclarations" value="false"/>Updating the participants (Migrating): To be able to respond to the additional changes of the new type rename refactoring, participants will need to know about these changes. Our solution for this problem is new API for the rename type participants, enabling them to handle similarly named declarations. Participant writers will need to adapt or replace their existing rename type participants to
|
![]() |
Migration Guide Not all existing rename type participants will need code changes, and the amount of changes will depend significantly on the existing implementation. Particularly, rename type participants which only deal with the type itself and not its members or other members (for example, for updating a launch configuration) need not be changed at all. For all participants which deal with the members of the type or which need to add support for method, field, or local variable renames, the code handling those elements must be changed. The biggest problem when writing the new participant is mapping the renamed old methods, fields, and local variables to the new, changed elements since a lot of things can change (the complete signature and the direct or indirect container of the elements). To solve this problem, we provide API for relocating any java element or resource in the workspace to reflect all performed changes. Participants can use the relocated handle during change execution to access the new elements. Let’s look at a simple example. Consider the following old rename type and rename method/field/local variable participants which keep a list of all keys of types, methods, fields, and local variables of all Java projects in an external database. The rename participants are implemented something like this (this is the rename type participant, which we will adapt – the code of the method/field/local variable participants looks similar, but need not be changed): public class DBTypeRenameParticipant extends RenameParticipant { private IType fOldType; private String fNewName; private IType fNewType; protected boolean initialize(Object element) { fOldType= (IType)element; fNewName= getArguments().getNewName(); // (assuming top level types) fNewType= fOldType.getPackageFragment().getCompilationUnit (fNewName + ".java").getType(fNewName); return true; } public Change createChange(IProgressMonitor pm) { return new DBChange(fOldType, fNewType) { public Change perform(…) { getDB().update(fOldType.getKey(), fNewType.getKey()); for (all fields of old Type) getDB().update(oldField.getKey(), fNewType.getField( oldField.getElementName().getKey()); } // methods, local variables… }; } }The code makes the assumption that all fields in the new type have the same name as in the old type. We need to:
fNewName= getArguments().getNewName();The RenameArguments class has been subclassed for the Rename Type Refactoring to contain additional information about similarly named declarations. We can cast the class to RenameTypeArguments and check if similarly named declarations are renamed: RenameTypeArguments args= (RenameTypeArguments) getArguments(); if (args.getUpdateSimilarDeclarations()) { // do something }Now we’d like to know which elements are renamed. Again, we ask the RenameTypeArguments for this information: IJavaElement[] similarDeclarations= args.getSimilarDeclarations();All of the elements in this array are either of type IMethod, IField, or ILocalVariable. These are the handles of the “old” elements before all changes. Now we know which elements have changed, but we still don’t know how they will look after the refactoring. What we’d like to do is change our existing code which resolves the new elements to handling complete signature changes, i.e. we’d like to change the lines fNewType= fType.getPackageFragment().getCompilationUnit (fNewName + ".java").getType(fNewName);and fNewType.getField(oldField.getElementName().getKey());To do this, we ask our refactoring processor for the new elements. The processor is guaranteed to implement the interface IJavaElementMapper – a mapper to map old pre-refactoring handles to post-refactoring handles. We adapt the processor to the interface: IJavaElementMapper processor = (IJavaElementMapper) getProcessor().adapt(IJavaElementMapper.class);Now, we can call the following method on the mapper: IJavaElement getRefactoredJavaElement(IJavaElement element);We can use this method to ask the refactoring to relocate any java element in the workspace to reflect all performed changes. For example, when asking the refactoring for an updated handle of the method: “setSomeClass(SomeClass someClass) in InnerType in SomeClass”,It will return the handle: “setAnotherClass(AnotherClass anotherClass) in InnerType in AnotherClass”(Provided the class SomeClass was renamed to AnotherClass). During change creation, the new handle will still point to a non-existing element. However, during participant change execution, the element will exist and can be used to retrieve resources etc. Side Note: The Rename Type Refactoring may also affect resources: If a top-level type was renamed, its enclosing file will be renamed as well. To be able to re-map such resource elements as well, we can adapt the RenameTypeProcessor to the IResourceMapper interface: IResourceMapper processor = (IResourceMapper) getProcessor().adapt(IResourceMapper.class);Now, we can call the following method on the mapper: IResource getRefactoredResource(IResource element);Again, this method can be used to map any IResource in the workspace to a new element reflecting all changes. With these helper methods, we change our code to the following to respond to signature changes in members of the type itself and also to changes to fields, methods, and local variables outside of the type: public class DBTypeRenameParticipant extends RenameParticipant { private IType fType; private IJavaElementMapper fMapper; private IJavaElement[] fSimilarDeclarations; protected boolean initialize(Object element) { fType= (IType)element; RenameTypeArguments args= (RenameTypeArguments) getArguments(); if (args.getUpdateSimilarDeclarations()) { fSimilarDeclarations= args. getSimilarDeclarations(); } fMapper= (IJavaElementMapper) getProcessor().adapt(IJavaElementMapper.class); return true; } public Change createChange(IProgressMonitor pm) { return new DBChange(fType, fNewType) { public Change perform(…) { getDatabase().update(fType.getKey(), fMapper.getRefactoredElement(fType).getKey()); for (all members of old Type) getDB().update(oldMember.getKey(),fMapper.getRefactoredElement(oldMember).getKey(); } for (all similar declarations not in old type) {Observe the following changes:
To sum up, you need to be aware of the following issues when adapting your old rename type participant code:
|