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
CodePudding user response:
Use Spoon. This can be accomplished as follows:
- FIX BUGS IN SPOON.
- Copy the source files to a new location.
- Create a new Java project.
- Create a launcher.
- Revise the pretty printer for Spoon's formatter.
- Add the source code directories.
- Build the abstract syntax tree (AST) model.
- Rename the classes.
- 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 iftype
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 thelibs
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.