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.
Related
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()
}
}
})
}
}
I'm trying to redefine a method at runtime using javassist, but I'm running into some issues on the last step, because of the weird requirements I have for this:
I can't require the user to add startup flags
My code will necessarily run after the class has already been defined/loaded
My code looks like this:
val cp = ClassPool.getDefault()
val clazz = cp.get("net.minecraft.world.item.ItemStack")
val method = clazz.getDeclaredMethod(
"a",
arrayOf(cp.get("net.minecraft.world.level.block.state.IBlockData"))
)
method.setBody(
"""
{
double destroySpeed = this.c().a(this, $1);
if (this.s()) {
return destroySpeed * this.t().k("DestroySpeedMultiplier");
} else {
return destroySpeed;
}
}
""".trimIndent()
)
clazz.toClass(Items::class.java)
(I'm dealing with obfuscated method references, hence the weird names)
However, calling .toClass() causes an error as there are then two duplicate classes on the class loader - and to my knowledge there's no way to unload a single class.
My next port of call to update the class was to use the attach API and an agent, but that requires a startup flag to be added (on Java 9+, I'm running J17), which I can't do given my requirements. I have the same problem trying to load an agent on startup.
I have tried patching the server's jar file itself by using .toBytecode(), but I didn't manage to write the new class file to the jar - this method sounds promising, so it's absolutely on the table to restart the server after patching the jar.
Is there any way I can get this to work with my requirements? Or is there any alternative I can use to change a method's behavior?
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).
What I am trying to do
I use the Eclipse JDT API to create the AST of some java project and manipulate it, however, my software is not an Eclipse Plug-in but it's supposed to be a stand-alone desktop application.
Right now in order to use a specific method of the API, I need an instance of org.eclipse.jdt.core.ICompilationUnit.
As far as I understood this code snippet would do that:
IWorkspace workspace = ResourcesPlugin.getWorkspace();
IPath path = Path.fromOSString(stringPath);
IFile file = workspace.getRoot().getFileForLocation(path);
ICompilationUnit compilationUnit =(ICompilationUnit)JavaCore.create(file);
The problem is that ResourcesPlugin.getWorkspace() won't work, I guess because my application is not Eclipse Plug-in. The specific Exception that is raised is the following:
Caused by: java.lang.IllegalStateException: Workspace is closed.
at org.eclipse.core.resources.ResourcesPlugin.getWorkspace(ResourcesPlugin.java:432)
What I need
The solution that I would like to implement involves making the folder in which the source code is contained in an Eclipse Workspace (provisionally).
Therefore, I need a way to get an instance of org.eclipse.core.internal.resources.Workspace given the path of the folder in which the source code is contained, basically:
String path = "./folder/with/source/code";
Workspace workspace = pathToWorkspace(path);
N.B. btw if there is a way get an instance of an ICompilationUnit, without the need of a workspace, that also would solve my problem.
What I have tried
What I have tried to do is create an instance of ResourcesPlugin and make it start with the hope that through BundleContext I would be able to specify the path. Unfortunately, org.osgi.framework.BundleContext is an interface which is specified that is not supposed to be implemented by the consumers and I wasn't able to find a concrete class that implements this interface.
ResourcesPlugin plugin = new ResourcesPlugin();
plugin.start(boundleContext);
What is the specific method you need to use?
You could try:
AST ast = AST.newAST(AST.JLS16, false);
CompilationUnit unit = ast.newCompilationUnit();
then add to the unit:
//with package:
PackageDeclaration pd = ast.newPackageDeclaration();
pd.setName(ast.newSimpleName("example"));
unit.setPackage(pd);
//class:
TypeDeclaration type = ast.newTypeDeclaration();
unit.types().add(type);
//and so on..
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);
}
}