简体   繁体   中英

Bulk rename of Java classes

Background

Hundreds of class files need to be renamed with a prefix. For example, rename these:

  • com.domain.project.Mary
  • com.domain.project.Jones
  • com.domain.project.package.Locket
  • com.domain.project.package.Washington

to these:

  • com.domain.project.PrefixMary
  • com.domain.project.PrefixJones
  • com.domain.project.package.PrefixLocket
  • com.domain.project.package.PrefixWashington

Such a rename could use a regular expression on the corresponding .java filename, such as:

(.+)\.(.+) -> Prefix$1\.$2

Problem

Most solutions describe renaming files. Renaming files won't work because it leaves the class references unchanged, resulting in a broken build.

Question

How would you rename Java source files en mass (in bulk) so that all references are also updated, without performing hundreds of actions manually (ie, one per file)?

Ideas

  • refactobot is inscrutable, but looks promising.
  • Spoon 's Refactoring API is too buggy and introduced broken code.
  • JavaParser doesn't appear to have a concept of related references. Renaming the class resulted in only the class name being changed, but not its constructor, much less other references. This would require a visitor pattern, but even so the output from JavaParser loses formatting and may introduce other issues.
  • CodART could help refactor the class names.
  • Rename the files using a regular expression and the find command, then use an IDE to fix all the problems. I couldn't see a way for IntelliJ IDEA to correct errors in bulk, which means fixing hundreds of issues one at a time.
  • Use IntelliJ IDEA's "replace structurally" functionality. This doesn't perform refactoring, resulting in a broken build. Also, there's no easy way to distinguish between files that have already been renamed and files that haven't: you have to rename by selecting classes in batches.
  • Use IntelliJ's RenameProcessor API to perform renaming. There doesn't appear to be a fine-grained separation of packages that can be pulled from a central repository.
  • Use an IDEA plug-in, such as RenameFilesRefactorBatch . The plug-in has been updated to support regular expressions, making it the most promising candidate.

IDEA

The main stumbling block with using IDEA is that although it can detect the problems as "Project Errors" when renaming the files, it offers no way to resolve the all the errors at once:

IDEA-01

The screenshot shows Glue and Num having been renamed to KtGlue and KtNum , respectively. There's no way to select multiple items, and the context menu does not have an option to automatically fix the problems.

As for CodART , the following code is used to rename a specified class:

    from codart.refactorings.rename_class2 import main
    project_path_ = r"/JSON/"  # Project source files root
    package_name_ = r"org.json"  # Class package name
    class_identifier_ = r"CDL"  # Old class name
    new_class_name_ = r"CDL_Renamed"  # New class name
    output_dir_ = r"JSON_Refactored"  # Refactored project source files root
    main(project_path_, package_name_, class_identifier_, new_class_name_, output_dir_)

A few solutions courtesy of HackerNews.


A shell script :

#!/usr/bin/env bash

javas=$(find . -regex '.*\.java$')
sed -i -E "$(printf 's/\\<(%s)\\>/Kt\\1/g;' $(grep -hrPo '\b(class|interface|record|enum) (?!Kt)(?!List\b)(?!Entry\b)\K[A-Z]\w+'))" $(echo $javas); 
perl-rename 's;\b(?!Kt)(\w+[.]java)$;Kt$1;' $(echo $javas)

This is a little overzealous, but rolling back some of the changes was quick and painless. Also, Arch Linux doesn't have perl-rename installed by default, so that's needed.


Another solution is to create a Kotlin IDEA plug-in :

  1. Install, run, then import the project into IDEA .
  2. Install the Kotlin plug-in for IDEA.
  3. Press Ctrl+Shift+A to open the Script Engine menu.
  4. Select Kotlin .
  5. Paste the script (given below).
  6. Press Ctrl+A to select the script.
  7. Press Ctrl+Enter to integrate the script into the IDE.
  8. Open the Project window.
  9. Select a single package directory (ie, a root-level package).
  10. Click Navigate >> Search Everywhere .
  11. Click the Actions tab.
  12. Search for: Bulk
  13. Select Bulk refactor .

The classes are renamed. Note: There may be prompts for shadowing class names and other trivial issues to resolve.

Script

@file:Suppress("NAME_SHADOWING")

  import com.intellij.notification.Notification
  import com.intellij.notification.NotificationType
  import com.intellij.notification.Notifications
  import com.intellij.openapi.actionSystem.*
  import com.intellij.openapi.keymap.KeymapManager
  import com.intellij.openapi.command.WriteCommandAction
  import com.intellij.psi.*
  import com.intellij.psi.search.*
  import com.intellij.refactoring.rename.RenameProcessor
  import com.intellij.util.ThrowableConsumer
  import java.io.PrintWriter
  import java.io.StringWriter
  import javax.swing.KeyStroke

  // Usage: In IDEA: Tools -> IDE Scripting Console -> Kotlin
  // Ctrl+A, Ctrl+Enter to run the script
  // Select folder containing target classes, Ctrl+Shift+A to open action menu, search for Bulk refactor

  //<editor-fold desc="Boilerplate">
  val b = bindings as Map<*, *>
  val IDE = b["IDE"] as com.intellij.ide.script.IDE

  fun registerAction(
    name: String,
    keyBind: String? = null,
    consumer: ThrowableConsumer<AnActionEvent, Throwable>
  ) {
    registerAction(name, keyBind, object : AnAction() {
      override fun actionPerformed(event: AnActionEvent) {
        try {
          consumer.consume(event);
        } catch (t: Throwable) {
          val sw = StringWriter()
          t.printStackTrace(PrintWriter(sw))
          log("Exception in action $name: $t\n\n\n$sw", NotificationType.ERROR)
          throw t
        }
      }
    });
  }

  fun registerAction(name: String, keyBind: String? = null, action: AnAction) {
    action.templatePresentation.text = name;
    action.templatePresentation.description = name;

    KeymapManager.getInstance().activeKeymap.removeAllActionShortcuts(name);
    ActionManager.getInstance().unregisterAction(name);
    ActionManager.getInstance().registerAction(name, action);

    if (keyBind != null) {
      KeymapManager.getInstance().activeKeymap.addShortcut(
        name,
        KeyboardShortcut(KeyStroke.getKeyStroke(keyBind), null)
      );
    }
  }

  fun log(msg: String, notificationType: NotificationType = NotificationType.INFORMATION) {
    log("Scripted Action", msg, notificationType)
  }

  fun log(
    title: String,
    msg: String,
    notificationType: NotificationType = NotificationType.INFORMATION
  ) {
    Notifications.Bus.notify(
      Notification(
        "scriptedAction",
        title,
        msg,
        notificationType
      )
    )
  }
  //</editor-fold>

  registerAction("Bulk refactor") lambda@{ event ->
    val project = event.project ?: return@lambda;
    val psiElement = event.getData(LangDataKeys.PSI_ELEMENT) ?: return@lambda

    log("Bulk refactor for: $psiElement")

    WriteCommandAction.writeCommandAction(event.project).withGlobalUndo().run<Throwable> {
      psiElement.accept(object : PsiRecursiveElementWalkingVisitor() {
        override fun visitElement(element: PsiElement) {
          super.visitElement(element);
          if (element !is PsiClass) {
            return
          }

          if(element.name?.startsWith("Renamed") == false) {
            log("Renaming $element")

            // arg4 = isSearchInComments
            // arg5 = isSearchTextOccurrences
            val processor = object : RenameProcessor(project, element, "Renamed" + element.name, false, false) {
              override fun isPreviewUsages(usages: Array<out UsageInfo>): Boolean {
                return false
              }
            }
  
            processor.run()
          }
        }
      })
    }
  }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM