Home > OS >  Bulk rename of Java classes
Bulk rename of Java classes

Time:01-18

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 renaming each file individually?

Ideas

  • Use Spoon's enter image description here

    CodePudding user response:

    Use Spoon. This can be accomplished as follows:

    1. FIX BUGS IN SPOON.
    2. Copy the source files to a new location.
    3. Create a new Java project.
    4. Create a launcher.
    5. Revise the pretty printer for Spoon's formatter.
    6. Add the source code directories.
    7. Build the abstract syntax tree (AST) model.
    8. Rename the classes.
    9. Write the output.

    I'm not going to go into all the gory details, but there is enough information for anyone else to follow along at home. Even better, if someone wanted to make a command-line tool that "just works", that'd be most helpful.

    Fix Bugs in Spoon

    Spoon has a few bugs that may need to be fixed depending on the complexity of the input source code and whether they trigger the bugs. I happened upon three. Fix them:

    • ./src/main/java/spoon/reflect/visitor/chain/CtConsumableFunction.java
    • ./src/main/java/spoon/reflect/visitor/TypeNameScope.java
    • ./src/main/java/spoon/support/sniper/internal/ModificationStatus.java

    In CtConsumableFunction.java, the method visitAllStaticMembersImport must exit early if type is null:

    public <T> void visitAllStaticMembersImport(CtTypeMemberWildcardImportReference typeReference) { 
      CtType<?> type = typeReference.getDeclaration();
      type.map
    

    In TypeNameScope.java, the if statement needs to check that sourceCode is null before empty:

    String sourceCode = getOriginalSourceCode();
    if (sourceCode.isEmpty()) { 
      return;
    } 
    

    In ModificationStatus, change the toBoolean() method to avoid throwing an exception:

    public boolean toBoolean() {
      if (this == MODIFIED) {
        return true;
      }
      if (this == NOT_MODIFIED) {
        return false;
      }
      return false;
    }
    

    Build a local copy of Spoon:

    mvn package -Dmaven.test.skip=true
    

    Then copy ./target/spoon-core-10.3.0-SNAPSHOT-jar-with-dependencies.jar into the renaming project's libs directory.

    Update build script

    The build.gradle file can look something like (note the imports for the libs directory that'll pick up the custom Spoon build:

    plugins {
      id 'application'
    }
    
    repositories {
      mavenCentral()
    }
    
    dependencies {
      // Spoon, custom build.
      implementation fileTree( include: ['**/*.jar'], dir: 'libs' )
    }
    
    application {
      mainClass = 'com.whitemagicsoftware.rename.App'
    }
    

    Rename using Spoon

    import spoon.Launcher;
    import spoon.refactoring.Refactoring;
    import spoon.support.sniper.SniperJavaPrettyPrinter;
    
    import java.io.File;
    import java.util.regex.Pattern;
    
    public class App {
      public static void main( final String[] args ) {
        final var spoon = new Launcher();
    
        // USE SNIPER TO AVOID SIGNIFICANT CHANGES TO SOURCE FILES.
        final var env = spoon.getEnvironment();
        env.setPrettyPrinterCreator(
          () -> new SniperJavaPrettyPrinter( env )
        );
    
        // ADD SOURCE CODE DIRECTORIES HERE
        spoon.addInputResource( "/tmp/nts/app/src/main/java/nts/app" );
        spoon.addInputResource( "/tmp/nts/tex/src/test/java/nts/io" );
        spoon.addInputResource( etc. );
    
        final var model = spoon.buildModel();
    
        // MATCH FILES USING A REGULAR EXPRESSION.
        final var p = Pattern.compile( "^(?!Kt).*" );
    
        // ITERATE OVER ALL SOURCE FILES
        for( final var clazz : model.getAllTypes() ) {
          final var name = clazz.getSimpleName();
          final var m = p.matcher( name );
    
          // PERFORM RENAME LOGIC HERE
          if( m.matches() ) {
            final var newName = "Kt"   name;
    
            System.out.println( "rename "   name   " to "   newName );
    
            // THIS DOES THE REFACTORING MAGIC.
            Refactoring.changeTypeName( clazz, newName );
          }
        }
    
        // SET OUTPUT LOCATION FOR RENAMED SOURCE FILES
        final var outputDir = new File("/tmp/nts-new");
    
        // SPIT OUT SOURCE FILES TO NEW LOCATION
        if( outputDir.exists() || outputDir.mkdirs() ) {
          spoon.setSourceOutputDirectory( outputDir );
          spoon.prettyprint();
        }
      }
    }
    

    All the file source code files are renamed and all the class references are updated. Further, the code is minimally modified thanks to the sniper pretty printer.

  • Related