Structural Search and Replace: What, Why and How-to

I would like to know more about the source

Finding all descendants of the class

Quite often we need to find all descendants of a particular class. To search for such classes one could use following search template:

class $Clazz$ extends $AnotherClass$ {}

As the text constraint for the AnotherClass variable one needs to specify the name of the base class for which we are looking for descendants. Since we are looking for any descendants of the base class we enable 'Apply the constraint within the type hierarchy' option.

Finding all such methods

In certain situations, one needs to look for many different implementations of the same interface method. This can be achieved with the following search pattern:

        class $a$ {
          public void $show$();
        }

The text constraint for the show variable is 'show', and 'This variable is the target of the search' is enabled.

Finding in literals and comments

Consider that we want to find something in the string literals of our program. For instance, we have some typo in the title of a dialog and we need to find the exact location. The corresponding search template is simple: "$StringContent$", the text constraint for StringContent is .*OurTypo.*. However, if we know in advance that the typo is actually a word, then we can use the 'Whole words only' option (with case sensitive search) to perform an indexed source search, which would find it faster. In this case, we set the text constraint as OurTypo.

The similar approach is used for searching in the comments, just the search pattern used is different, namely: // $LiteralContent$

Finding specific usages

We may be interested in finding all calls of a particular method on instance variables of a particular descendant type. For instance, find 'equals' calls that are called from instances of the String class. The search pattern would be: $instance$.equals($argument$). Text / regular expression constraint for the instance template variable would be String.

I'm tired of manually editing similar things over and over

Upgrading a library

Quite often, a library's evolution is out of our control. Not all of them preserve backward API compatibility, so you have to change existing code extensively after a major update of a third-party library. For instance, when we were upgrading from Xerces 2.0 to 2.6.2, we needed to replace DOMInputSourceImpl with DOMInputImpl. This was achieved with the following settings:
Search template: $InputSourceImpl$, text constraint DOMInputSourceImpl.
Replacement template DOMInputImpl.

I want to change the classes

Consider we want to change the parent class for many classes, e.g. going from bare TestCase to OurHomeGrownTestCase. It is convenient to use Structural Search and Replace to accomplish this with following settings.

Search template:

        class $TestCase$ extends TestCase {
           $MyClassContent$
        }

Replacement template:

        class $TestCase$ extends OurHomeGrownTestCase {
           $MyClassContent$
        }

The minimum and maximum occurrence counts for the MyClassContent variable should be set to 0 and Integer.MAX_VALUE respectively.

Tip: This way, one could also quickly insert or remove a default implementation of an interface method in many classes when a new method is added or removed from the interface.

Note: In case of using the short name of a class in the example above, IDEA will detect that the class is not imported yet, and will suggest automatically adding the necessary import statement where needed.
As for the replaced class, it may, vice versa, need a removal of the redundant imports. This can be easily achieved by calling the 'Optimize Imports' command.

Heavy refactoring: Static utility method calls -> singleton instance method calls

One of our classes, MakeUtil, was completely refactored during its move to the Open API. All its static methods became instance methods and needed to be called from a singleton instance. The problem was hard due to the fact that we had two such classes in different packages. After modifying the class itself, we needed to update all the usages. Here's how we made the static class update with the help of Structural Search and Replace.

Search template:

   com.ij.j2ee.MakeUtil.$MethodCall$($Params$)
		

Replacement template:

  com.ij.j2ee.MakeUtil.getInstance().$MethodCall$($Params$)
	

Constraints:
Minimum and maximum occurrences count for Params variable was set to 0 and 100 respectively.

Note: Fully qualified class name in the search template tells the matcher exactly which class to match for the static method call, but it will be matched with either the FQ name or the short name in the source code. Similarly, the fully qualified class name in the replace template will be left as-is if the option 'Shorten fully qualified names' (Figure 2) is turned off. If it is turned on, then the short name of the class will be used used, possibly with an import statement added.


Have your own opinion? Discuss this article »

Page
Maxim's photo

Maxim Mossienko
JetBrains

Maxim is a Senior Software Developer working in IntelliJ IDEA project where he is responsible for the support of serverside programming. Currently he builds css and javascript integration within the IDE.

Contact Maxim via email: maxim(.)mossienko
(at) jetbrains.com