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 (i.e., 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:
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:
Install, run, then import the project into IDEA.
Install the Kotlin plug-in for IDEA.
Press Ctrl+Shift+A to open the Script Engine menu.
Select Kotlin.
Paste the script (given below).
Press Ctrl+A to select the script.
Press Ctrl+Enter to integrate the script into the IDE.
Open the Project window.
Select a single package directory (i.e., a root-level package).
Click Navigate >> Search Everywhere.
Click the Actions tab.
Search for: Bulk
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()
}
}
})
}
}
Related
I'm trying to understand a comment that a colleague made. We're using testcontainers to create a fixture:
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.utility.DockerImageName;
public class SalesforceFixture extends GenericContainer<SalesforceFixture> {
private static final String APPLICATION_NAME = "salesforce-emulator";
public SalesforceFixture() {
// super(ImageResolver.resolve(APPLICATION_NAME));
super(DockerImageName.parse("gcr.io/ad-selfserve/salesforce-emulator:latest"));
...
}
...
The commented code is what it used to be. The next line is my colleague's suggestion. And on that line he commented:
This is the part I don't know. The [ImageResolver] gets the specific version of the emulator, rather than the latest. You need a docker-info file for that though, which jib doesn't automatically generate (but I think it can).
This is what I know or have figured so far:
SalesforceFixture is a class that will be used by other projects to write tests. It spins up a container in Docker, running a service that emulates the real service's API. It's like a local version of the service that behaves enough like the real thing that if one writes code and tests using the fixture, it should work the same in production. (This is where my knowledge ends.)
I looked into ImageResolver—it seems to be a class we wrote that searches a filesystem for something:
public static String resolve(String applicationName, File... roots) {
Stream<File> searchPaths = Arrays.stream(roots).flatMap((value) -> {
return Stream.of(new File(value, "../" + applicationName), new File(value, applicationName));
});
Optional<File> buildFile = searchPaths.flatMap((searchFile) -> {
if (searchFile.exists()) {
File imageFile = new File(searchFile + File.separator + "/target/docker/image-name");
if (imageFile.exists()) {
return Stream.of(imageFile);
}
}
return Stream.empty();
}).findAny();
InputStream build = (InputStream)buildFile.map(ImageResolver::fileStream).orElseGet(() -> {
return searchClasspath(applicationName);
});
if (build != null) {
try {
return IOUtils.toString(build, Charset.defaultCharset()).trim();
} catch (IOException var6) {
throw new RuntimeException("An exception has occurred while reading build file", var6);
}
} else {
throw new RuntimeException("Could not resolve target image for application: " + applicationName);
}
}
But I'm confused. What filesystem? Like, what is the present working directory? My local computer, wherever I ran the Java program from? Or is this from within some container? (I don't think so.) Or maybe the directory structure inside a .jar file? Or somewhere in gcr.io?
What does he mean about a "specific version number" vs. "latest"? I mean, when I build this project, whatever it built is all I have. Isn't that equivalent to "latest"? In what case would an older version of an image be present? (That's what made me think of gcr.io.)
Or, does he mean, that in the project using this project's image, one will not be able to specify a version via Maven/pom.xml—it will always spin up the latest.
Sorry this is long, just trying to "show my work." Any hints welcome. I'll keep looking.
I can't comment on specifics of your own internal implementations, but ImageResolver seems to work on your local filesystem, e.g. it looks into your target/ directory and also touches the classpath. I can imagine this code was just written for resolving an actual image name (not an image), since it also returns a String.
Regarding latest, using a latest tag for a Docker image is generally considered an anti-pattern, so likely your colleague is commenting about this. Here is a random article from the web explaining some of the issues with latest tag:
https://vsupalov.com/docker-latest-tag/
Besides, I don't understand why you ask these questions which are very specific to your project here on SO rather than asking your colleague.
I am using Eclipse with Java. I need to define several very similar classes. It gets tedious typing the same thing automatically each time and wondering whether I could set up a short cut. I read Eclipse key bindings but it looks like something must already be in a plugin. This is what I need to type each time
public class SomeClass extends Token {
WebDriver driver = null;
WindowStack stack = null;
#Override
public void init() throw InitException {
super.init();
driver = TestCont.getWebDriver(); // defined and set elsewhere
stack = TestCont.getWindowStack();
}
#Override
public void exec throws ExecException {
}
}
SomeClass is actually some unique name.
I guess I could just keep the text in a file and copy/paste, but it would be nice to create a short cut. I recently saw an online class where someone was using an IDE (I don't know which one it was). He typed psvm and it automatically changed to
public static void main(String[] argc) {
}
and doing something like new SomeClass(parm1, parm2, parm3).var automatically set to
SomeClass var = new SomeClass(parm1, parm2, parm3);
and similarly anything with ".var" at the end would make such a variable. So I am wondering whether there is a way to do something similar (as above) in Eclipse with Java.
Not sure whether it matters but I have
Eclipse IDE for Enterprise Java Developers.
Version: 2018-12 (4.10.0)
Build id: 20181214-0600
OS: Windows 10, v.10.0, x86_64 / win32
Java version: 1.8.0_144
You can define templates in Preferences -> Java -> Editor -> Templates
The content assist takes these into account for template completion (the name of the template).
For example, two of the predefined templates are called sysout and syserr. If you type sys, then trigger code completion, it suggests these two templates. Selecting sysout results in this code being inserted:
System.out.println();
(the template also defines places where other stuff needs to be inserted, where the cursor goes etc. but for your problem that seems like nice-to-have).
I have a c code like this
static S16 test_1603b( const S16 *, const S16 * );
I want to edit this code pragmatically to be something like this
static S16 test_1603b( const S16 *varName, const S16 *varName );
So what I did I used Eclipse CDT plugin outside the eclipse, and I have successfully extracted the Abstract syntax tree(AST) and visited all the method parameter declaration, but I can not found any way to rewrite the AST again with the new modification, My Code snippet:
public class RuleChk extends AbstractRule {
public RuleChk(IASTTranslationUnit ast) {
super("RuleChk", false, ast);
shouldVisitDeclarations = true;
shouldVisitParameterDeclarations = true;
}
#Override
public int visit(IASTParameterDeclaration parameterDeclaration) {
if (!parameterDeclaration.getRawSignature().startsWith("void")) {
if (parameterDeclaration.getDeclarator().getName().getRawSignature().equals("")) {
IASTDeclarator oldDec = parameterDeclaration.getDeclarator();
//Create New Declarator Node
INodeFactory factory = ast.getASTNodeFactory();
IASTName name = factory.newName("varName".toCharArray());
IASTDeclarator declarator = factory.newDeclarator(name);
declarator.setParent(oldDec.getParent());
declarator.setInitializer(oldDec.getInitializer());
declarator.setName(name);
declarator.setNestedDeclarator(oldDec.getNestedDeclarator());
declarator.setPropertyInParent(oldDec.getPropertyInParent());
//get the rewriter
final TextEditGroup editGroup = new TextEditGroup("FakeGroup");
ASTRewrite rewriter = ASTRewrite.create(ast);
rewriter.replace(declarator,oldDec,editGroup);
rewriter.rewriteAST();
}
}
return super.visit(parameterDeclaration);
}
}
After Debugging I found the org.eclipse.cdt.internal.formatter.ChangeFormatter#formatChangedCode, when it try to get the
ICProject project = tu.getCProject();
It throws a null pointer exception because the TransionUnit (tu) is being null from the beginning of the whole application,
ANY IDEAS GEEKS!
A lot of the CDT infrastructure, including ASTRewrite, is not designed to run outside of an Eclipse project / workspace.
What you generally need to do in cases like this is:
Create an Eclipse workspace. If you don't otherwise need an Eclipse workspace, you can create a temporary one and delete it when you're done.
Create a CDT C project inside your workspace.
Make sure the code you want to process is part of the project. If the files are contained inside the project's directory tree, then this happens automatically. Otherwise, you can set up a "linked folder" in the project to refer to a location outside of the project's directory tree.
Depending on what your refactoring needs, you may need to run CDT's indexer on the project.
Get an ITranslationUnit representing the file you want to process (similar to what you wrote in your comment).
Get the IASTTranslationUnit from the ITranslationUnit.
The first four steps can be done manually, or automatically using Eclipse APIs.
am writing an Eclipse plugin, and I was trying to create a method that returns all the classes in the workspace in an ArrayList<\Class<\?>> (I added the "\" to include the generic brackets since html won't let me do so otherwise).
Here is the code I have:
private ArrayList<Class<?>> getAllClasses() throws JavaModelException {
ArrayList<Class<?>> classList = new ArrayList<Class<?>>();
IWorkspace workspace = ResourcesPlugin.getWorkspace();
IWorkspaceRoot root = workspace.getRoot();
IProject[] projects = root.getProjects();
for (IProject project : projects) {
IJavaProject javaProject = JavaCore.create(project);
IPackageFragment[] packages = javaProject.getPackageFragments();
for (IPackageFragment myPackage : packages) {
IClassFile[] classes = myPackage.getClassFiles();
for (IClassFile myClass : classes) {
classList.add(myClass.getClass());
}
}
}
return classList;
}
This, however, doesn't seem to be working. I had some printlines, and I figured out that it also includes irrelevant classes (ie. classes from Java\jre6\lib\rt.jar). Any suggestions?
I'm not sure what you want to do:
In a running Eclipse plug-in, show all classes that are running in the JVM with the plug-in (i.e. classes for other editors, views, Eclipse machinery)?
In a running Eclipse plug-in, show all classes being built in open Java projects in the workspace? (Since you used the word "workspace", I suspect this is what you're looking for.)
Note in the latter case, you will not be able to get actual Java Class<...> objects, because the projects being edited and compiled are not loaded for execution into the same JVM as your plug-in. Your plug-in's code would be executing alongside the Eclipse IDE and Eclipse JDT tool code; the only time classes in open projects would be loaded for execution (producing Class<...> objects somewhere) would be when you launch or debug one of those projects . . . in which case you're dealing with a brand new JVM, and your plug-in is no longer around. Does that make sense?
If I am reading you right, I think you probably want to find "compilation units", not "class files". "Compilation units" correspond with .java source files, while "class files" correspond with pre-built binary class files (often in JARs). Or maybe you need both. Better yet, it sounds like what you really want are the "types" inside those.
Check out the JDT Core guide for some pretty good information that's remarkably difficult to find. Note that some analysis is possible at the Java Model level, but more detailed analyses (e.g. looking "inside" method definitions) will require parsing chunks of code into ASTs and going from there. The Java Model is pretty convenient to use, but the AST stuff can be a little daunting the first time out.
Also consider the Java search engine (documented near the above) and IType.newTypeHierarchy() for finding and navigating types.
Good luck!
You should try :
for (final ICompilationUnit compilationUnit : packageFragment.getCompilationUnits()) {
// now check if compilationUnit.exits
}
You don't get a CompilationUnit for binary types.
Maybe you can use IJavaProject.getAllPackageFragmentRoots() method to get all source folder,and then get ICompilationUnits in it.
I have much simpler solution for an eclipse project if you're just looking for listing java class names for the current package and add them to a list of classes (please replace PARENT_CLASS by the parent class name of all your classes):
List<PARENT_CLASS> arrayListOfClasses = new ArrayList<>();
String currentDir = new java.io.File("").toURI().toString().split("file:/")[1];
System.out.println("currentDir=" + currentDir);
String[] dirStringTab = currentDir.split("/");
String currentPackageName = dirStringTab[dirStringTab.length-1];
System.out.println("currentPackageName=" + currentPackageName);
File dir = new File("./src/" + currentPackageName + "/");
File[] filesList = dir.listFiles();
String javaClassNameWithoutExtension = "";
for (File file : filesList) {
if (file.isFile()) {
System.out.println(javaClassNameWithoutExtension);
javaClassNameWithoutExtension = file.getName().split(".java")[0];
Class c = Class.forName(javaClassNameWithoutExtension);
Object a = c.newInstance();
arrayListOfClasses.add(a);
}
}
is there a built-in support in Groovy to handle Zip files (the groovy way)?
Or do i have to use Java's java.util.zip.ZipFile to process Zip files in Groovy ?
Maybe Groovy doesn't have 'native' support for zip files, but it is still pretty trivial to work with them.
I'm working with zip files and the following is some of the logic I'm using:
def zipFile = new java.util.zip.ZipFile(new File('some.zip'))
zipFile.entries().each {
println zipFile.getInputStream(it).text
}
You can add additional logic using a findAll method:
def zipFile = new java.util.zip.ZipFile(new File('some.zip'))
zipFile.entries().findAll { !it.directory }.each {
println zipFile.getInputStream(it).text
}
In my experience, the best way to do this is to use the Antbuilder:
def ant = new AntBuilder() // create an antbuilder
ant.unzip( src:"your-src.zip",
dest:"your-dest-directory",
overwrite:"false" )
This way you aren't responsible for doing all the complicated stuff - ant takes care of it for you. Obviously if you need something more granular then this isn't going to work, but for most 'just unzip this file' scenarios this is really effective.
To use antbuilder, just include ant.jar and ant-launcher.jar in your classpath.
AFAIK, there isn't a native way. But check out this article on how you'd add a .zip(...) method to File, which would be very close to what you're looking for. You'd just need to make an .unzip(...) method.
The Groovy common extension project provides this functionality for Groovy 2.0 and above: https://github.com/timyates/groovy-common-extensions
The below groovy methods will unzip into specific folder (C:\folder). Hope this helps.
import org.apache.commons.io.FileUtils
import java.nio.file.Files
import java.nio.file.Paths
import java.util.zip.ZipFile
def unzipFile(File file) {
cleanupFolder()
def zipFile = new ZipFile(file)
zipFile.entries().each { it ->
def path = Paths.get('c:\\folder\\' + it.name)
if(it.directory){
Files.createDirectories(path)
}
else {
def parentDir = path.getParent()
if (!Files.exists(parentDir)) {
Files.createDirectories(parentDir)
}
Files.copy(zipFile.getInputStream(it), path)
}
}
}
private cleanupFolder() {
FileUtils.deleteDirectory(new File('c:\\folder\\'))
}
This article expands on the AntBuilder example.
http://preferisco.blogspot.com/2010/06/using-goovy-antbuilder-to-zip-unzip.html
However, as a matter of principal - is there a way to find out all of the properties, closures, maps etc that can be used when researching a new facet in groovy/java?
There seem to be loads of really useful things, but how to unlock their hidden treasures? The NetBeans/Eclipse code-complete features now seem hopelessly limited in the new language richness that we have here.
Unzip using AntBuilder is good way.
Second option is use an third party library - I recommend Zip4j
Although taking the question a bit into another direction, I started off using Groovy for a DSL that I was building, but ended up using Gradle as a starting point to better handle a lot of the file-based tasks that I wanted to do (eg., unzip and untar files, execute other programs, etc). Gradle builds on what groovy can do, and can be extended further via plugins.
// build.gradle
task doUnTar << {
copy {
// tarTree uses file ext to guess compression, or may be specific
from tarTree(resources.gzip('foo.tar.gz'))
into getBuildDir()
}
}
task doUnZip << {
copy {
from zipTree('bar.zip')
into getBuildDir()
}
}
Then, for example (this extracts the bar.zip and foo.tgz into the directory build):
$ gradle doUnZip
$ gradle doUnTar
def zip(String s){
def targetStream = new ByteArrayOutputStream()
def zipStream = new GZIPOutputStream(targetStream)
zipStream.write(s.getBytes())
zipStream.close()
def zipped = targetStream.toByteArray()
targetStream.close()
return zipped.encodeBase64()
}