How to automatically remove methods in java code - java

I need to remove some methods in a large java project and I was wondering if there are tools that would help me do so. I would basically specify a signature and a source folder in which all the matching method would be removed.
It no such thing exists then I will write a script.

Today, there are a couple of way to achieve this. For example, you can tell the Eclipse compiler to give you the AST (see my blog for an example). You can navigate the AST to find methods and remove nodes to change the source.
This makes it easy to keep the code valid. Using the Eclipse code formatter, you can clean up the formatting afterwards.
Projects like MoDisCo go one step further by analyzing a whole project and giving you ways to search the whole thing. I just found that the documentation for MoDisCo is not very helpful when you're a beginner.

If you use eclipse or other powerful IDE you may have built in support for doing this.
See I can't delete a method using eclipse refactoring? for a way to achieve what you want.

You're talking about a "source transformation", and Google turns up the existence of BeautyJ for Java:
http://beautyj.berlios.de/
"BeautyJ can convert Java source code to XML and back. BeautyJ introduces the XJava format which stores classes or interfaces and their members together with associated source code and Javadoc comments in a single XML file. BeautyJ opens up a wide range of possibilities for automatic structuring of Java source code either by its Sourclet API or by converting Java source code to XML and allowing any external transformations (for example by applying XSLT)."
No clue whether your case is so simple that a script would be better than going through the trouble of learning how to use such a program, though. Although it may be worthwhile to get it in your toolchain for other purposes as well.

Our DMS Software Reengineering Toolkit with its Java Front End could be used for this.
DMS parses languages (using its front ends, in this case Java), builds ASTs and symbol tables, and provide facilities to manipulate the ASTs based on custom analysis. In this case OP wants to provide a method signature (presumably with a context in which it should be interpreted because otherwise the types used in the signature might not be defined), look that signature up in the symbol table, find the point of declaration (this is in the symbol table) as a AST node, and then apply a rewriting rule that replaces the declaration with an empty declaration.
An analysis he may wish to perform on the found method is whether is it used or not. We have that information in the symbol table, too.

You can use something in Java to batch-modify your Java project. The trick is to use a custom annotation processor. With annotation processor you can modify almost everything.
For an example, this library ( for ecilipse and IDEA ) modifies some String field members to it's /**multiline*/ docs.
Studying the library gives me the power to modify the method body as well.
Today, I want to strip out some unwanted methods in the android support library( used as editable local module ). I can manually remove them, but it would be hard to keep udpate after that.
So, instead of using script or mere-hand to modify the code directly, I decide to write another annotation processor which allow you to remove some class members.
For IDEA, the concept-proving code is very simple:
// the custom annotation
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.SOURCE)
public #interface StripMethods {
boolean strip() default true;
String key() default "";
}
// processing the custom annotation
#Override
public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnv) {
Set<? extends Element> fields = roundEnv.getElementsAnnotatedWith(StripMethods.class);
for (Element field : fields) {
StripMethods annotation = field.getAnnotation(StripMethods.class);
ElementKind KIND = field.getKind();
if(KIND == ElementKind.CLASS) {
// the class declaration
JCTree.JCClassDecl laDcl = (JCTree.JCClassDecl) elementUtils.getTree(field);
// the definition tree list
ArrayList<JCTree> defs = new ArrayList<>(laDcl.defs);
// remove the second member which in this case is a string field
defs.remove(2);
// finally modify the class definition
laDcl.defs = List.from(defs);
}
}
}
#StripMethods
public class Test {
// the first member is the default constructor
static {
}
static final int FieldToRemove = 0;
#Test
public void test() {
int variableToRemove = FieldToRemove;
}
}
the result error caused by the member removal:
Test.java:10: error: cannot find symbol
int variableToRemove = FieldToRemove;
^
symbol: variable FieldToRemove
location: class Test
Still a long way to go. I will publish the code when it's finished.
Done. see https://github.com/KnIfER/Metaline
Exmaple usage, removing NightMode from androidx/appcompat:
#StripMethods(key="Night")
public class AppCompatActivity
...
#StripMethods(key="Night")
public abstract class AppCompatDelegate
...
#StripMethods(key="Night")
class AppCompatDelegateImpl
...

I looked for quick solutions and did not find any, I used different tools to get there:
grep to find all the files containing the main methods I wanted
to remove
find/replace in these files to make those methods private
Eclipse clean-up with just one rule: remove unused
private methods
It did the job with the minor side effect of removing other unused private methods. It is not a big deal in the context of that project since pretty much all the files were previously saved with a save action removing unused private methods.
Thanks all for the input, it may be applicable later on.

Related

Compile-time annotation processing

Is there way to do compile-time annotation processing in Java?
Consider this example:
#Name("appName")
private Field<String> appName;
public void setAppName(String name) {
appName.setValue(name);
}
public String getAppName(String name) {
return appName.getValue();
}
public void someFunction() {
String whatFieldName = appName.getName();
}
Where the annotation Name will be processed at compile-time to set the value for Field That is without the common runtime annotation processing. As such, when appName.getName(); (the Field) is accessed it will return the typed value.
Yes, there is, but, no, it cannot change existing files. You can 'plug in' to the compiler and be informed of any annotations; as part of this, you can see signatures (so, field declarations, method signatures, types, etc) but no contents (so not the expression used to initialize a field, and not the contents in the {} of a method declaration), and you can make NEW files, even java files, but you can't edit existing ones.
Project Lombok does edit them, but that is quite the framework to make that possible.
There are some crazy tricks you can use. Project lombok uses one trick (reflect its way into compiler internals, fix everything from there, install agents and plugins in IDEs). Another trick is to use a java source file as a template, of sorts. You name your class some funky (so if you want, say, public class AppDescriptor, you'd actually make the java file AppDescriptorTemplate.java and put public class AppDescriptorTemplate inside. This file has the annotation precisely as you pasted. Your annotation processor can then, during compilation, generate AppDescriptor.java, writing the impls of all methods as simple pass-throughs (a field of type AppDescriptorTemplate is generated, and all methods in ADT are copied over, and the implementations are all one-liners that just invoke that method on the template class). The template class can be package private. In this specific scenario it sounds like you can generate virtually the whole thing based off of pretty much only "appName", though.
Lombok plugs straight into the build and is therefore virtually entirely transparent, in the sense that you simply type in your IDE and the methods it generates just appear as you type, whereas 'normal' annotation processors that e.g. use the XTemplate trick do not work that way and require the build system to kick in, every time. It can be a bit of a productivity drain.

Find object's class

Currently I have task to migrate some rules in SonarQube. Older ones are based on PMD and Checkstyle libraries. New ones need to be written with integrated Sonar rules (like most squid rules). Rule I'm currently working on needs to check if any method from class which contains "Dao" in it's name is used in loop.
I'm not quite experienced with rules migration and use of sonar libraries, so I am trying to use some parts from squid rules but I'm not able to find exactly what i need.
#Override
public void visitNode(Tree tree) {
MethodInvocationTree methodInvocationTree = (MethodInvocationTree) tree;
IdentifierTree methodName = methodName(methodInvocationTree);
if ( methodClassContainsDaoInName(tree?) && insideLoop(tree?)) {
reportIssue(methodName, "DAO methods should not be called inside loop");
}
}
I'm still stuck on first method (methodClassContainsDaoInName()). Currently I'm trying to get method and object and check if that object's class has "Dao" in it's name. I use this command:
tree.parent().firstToken().text();
this command finds my object but only gives me it's name. I tried to do this:
tree.parent().firstToken().toClass();
but it gives me wrong class (If I recall correctly it returns symbol or token class).
Maybe someone know how could I convert that result to correct object?
Maybe someone know better way to make this rule alive and could share some examples?
As a disclaimer, I'm not going to write the rule for you. Everything should be available from the API (syntax or semantic)... Once correctly formulated your requirement in term of rule writing and concepts.
To reformulate the requirement: You are trying to verify that the class (the object...) owning the called method is a DAO (a.k.a. the owner), and if it's the case, raise an issue (if it's within a loop, but that's another story).
Now, from the methodInvocationTree itself that you already have, access its associated symbol (there is a symbol() method in the API), then it's owner symbol, which should be the owning class. From the owner symbol, you can then access directly its name, and validate if it contains "Dao".
Put into code:
boolean methodFromDao = methodInvocationTree
.symbol()
.owner()
.name()
.contains("Dao");
Important note: From the syntax API, you used another level of abstraction from the SonarJava API, which gives you access to symbols. In order to complete the semantic, SonarJava requires access to bytecode during analysis. If bytecode is not provided, it is most likely that methods won't be resolved by the semantic engine. Concretely, it means that all the symbols will be unknown, and you won't be able to know their owners.

Get Android R class when passing value with annotation?

I have this annotation BindPrefRes.class
#Target(ElementType.FIELD)
#Retention(RetentionPolicy.SOURCE)
public #interface BindPrefRes {
#StringRes int value();
}
Now when I use that annotation in any Android project like this:
#BindPrefRes(R.string.app_name) String appName;
And I'm trying to get the value with javax.annotation.processing.AbstractProcessor
int value = element.getAnnotation(BindPrefRes.class).value()
I get the actual int (2131230753), which I understand is the expected behavior. But how do I parse this int back to field (R.string.app_name), or at least string representation of that field ("R.string.app_name")?
The source code is on GitHub if I'm being unclear.
Short answer: you should avoid doing so whenever possible, because the required information might not be available at all. For example, if Gradle (or other build systems) ever start supporting incremental builds with annotation processors, the R class might not be present on compilation path by the time your processor runs.
Long answer: there are two ways:
Ugly
Scan the source code for R classes
Iterate over every field in found R classes and add all constants to Map
Associate the value, received from annotation with constant from the Map
One implementation of this approach (which for some reason relies on non-public Javac internals) can be found in ButterKnife sources).
Another one is maintained by Android Annotations projects (you can copy it or access it directly by making your processor a plugin for Android Annotations processor).
Also ugly
Use Oracle proprietary semi-stable Trees API to directly introspect source code of #BindPrefRes(R.string.app_name). I recommend going that way whenever possible, because it has fewer side-effects.
Obtain a TreePath for annotation value, cast it to ExpressionTree and manually parse contents.

Java static metaprogramming

I'd like to implement annotation processor that will generate new class based on existing "prototype" class.
import java.util.List
#MyAnnotation
class MySuperClassPrototype {
static MySuperClassPrototype createInstance() {
return new MySuperClassPrototype();
}
}
As a result of code below. The following new source file (compilation unit) will be generated:
import java.util.List
class MySuperClass {
static MySuperClass createInstance() {
return new MySuperClass();
}
public void specialAddedMethod() {
/*...*/
}
}
I'd like to copy all top-level import statements and static members and not static members of prototype-class. I've moved pretty far with Compiler Tree API (com.sun.source.tree). I can print out Tree data-type while substituting new class name for old. But there are problems that seems pretty hard.
If I get Tree.Kind.IDENTIFIER in the tree, how can I find what actual class it references. I need to replace all occurrences of MySuperClassPrototype identifier with MySuperClass identifier, and than print out whole tree.
Is it feasible?
Similarly I need to filter out #MyAnnotation annotation, and again it is represented with Tree.Kind.IDENTIFIER or Tree.Kind.MEMBER_SELECT.
How can I find out actual annotation class that is referenced by this identifier?
And another problem is printing out tree. If I use toString method I got decent result, but constructors are printed as methods with "<init>" name instead of methods with the same name as it's class, so I need to manually print every kind of Tree node.
You can see code I've come with here
Yes, it is possible and I know at least 2 ways.
First, "traditional" way is to write ant task/maven plugin/just command line java utility that scans given file path and calls for each class something like Class.forName(className).getAnnotations(MyAnnotation.class). If this is not null discover class using reflection and do what you need.
Other way is a little bit more difficult but more powerful.
You can implement your own Processor (that implements javax.annotation.processing.Processor or even better extends javax.annotation.processing.AbstractProcessor.
Your processor will just have to be placed to the compiler classpath and it will run automatically when compiler runs. You can even configure your IDE (e.g. Eclipse) to run your processor. It is a kind of extension to java compiler. So, every time eclipse builds your project it runs the processor and creates all new classes according to new annotations you have added.
Please take a look on this project as a reference.
8 Years and not yet answered. Because of that, i will try to answer it, to your satisfaction.
I fill furthermore concentrate on the static part of the question.
TL;DR:
You will not find copy and paste code in this answer.
Is it feasible?
Yes, absolutely.
How can I find out actual annotation class that is referenced by this identifier?
You will have to use the RoundEnvironment within an Annotation Processor to get the TypeElement.
Static Metaprogramming
Static metaprogramming (which you asked for) is metaprogramming done at compile time. By Kontrast: Dynamic metaprogramming is metaprogramming done at run time. And metaprogramming it self is the design of programs, that handle other programs as data.
Pfeh, a lot to take in. If you are interested in this topic, a more or less good source for that is wikipedia.
Your target would be, to generate a class at compile time. For run time, this would be done with something like cglib. But, since you choose static (and for all the right reasons), i will not explain this.
The concept you are looking for is the annotation processor. The link is a link to Baeldung, where they do exactly, what you are looking for, only with the builder pattern in mind. You will love to hear, that this scenario is highly encouraged and easy to do with the annotation processor API. It even allows you, to generate code, which again is passed to the same or another annotation processor, without you doing anything.
Before jumping right in, try to google yourself about "Java Annotation Processing". There are a lot of good sources out there, which will help you. To much, to list here. Just note, that coding in an annotation processor is different than coding normally. Not a huge difference, but the classes you are working on are not yet created. So keep this in mind and don't get discouraged!
Using the Annotation Processor
Your basic annotation processor would look something like this:
#SupportedAnnotationTypes("package.of.MyAnnotation")
#SupportedSourceVersion(SourceVersion.RELEASE_8)
#AutoService(Processor.class)
public class BuilderProcessor extends AbstractProcessor {
#Override
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {
// First let's find all annotated elements
Set<? extends Element> annotatedElements = roundEnv.getElementsAnnotatedWith(MyAnnotation.class);
// Handle all the annotated classes
return false;
}
}
The AutoService Annotation is used, to dynamically register your annotation processor. It comes from an external source, just so you don't wonder, why this code won't compile.
In the handle all annotated classes part, you have the annotated Elements (which are the annotated classes). You now would have to verify, that they are classes and not interfaces or other annotations. This is because #Target(ElementType.Type) aims at any type, which includes interfaces and annotations. Furthermore, you would want to verify, that anything you require is present, or print an error to the compiler using the Messager.
If you print an error here (for example), you will stop compiling and the error will be seen in most modern IDEs. It can be reached by calling roundEnv.getMessager()
Afterwards you can generate a new class and write it to the input of the compiler, as a .java file. This can be done by using the Filer.
An answer in StackOverflow really does no justice to this topic. I highly recommend looking at the Baeldung example and trying to uncover things from there. This API is as old as Java 6, but still not that greatly used. I encourage you, the reader, to try it out for yourself :)
take a look at https://github.com/rzwitserloot/lombok/, It add methods as you described.
such as
#Getter add getter methods based on fields
#Setter
#ToString add toString() methods base on the fields

Is there a way to apply a refactoring script just for its side-effects?

I've got a library that was changed to introduce a factory method to replace a constructor (the reasons aren't important right now, in the current case it was for improved type inference mostly).
Assuming that there is an Eclipse refactoring script for it (so I pretty much have a nice computer-readable description of the change), is there some way to apply that script to a project that only uses that library (i.e. it only has a reference to a compiled version of the library)?
For example, I start with this simple class:
public class MyContainer<T> {
private final T content;
public MyContainer(final T content) {
this.content = content;
}
public T getContent() {
return content;
}
}
Now I introduce a factory (without making the constructor private, but that isn't really relevant right now), the factory method looks like this:
public static <T> MyContainer<T> holding(T content) {
return new MyContainer<T>(content);
}
When I now export a migration script it looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<session version="1.0">
<refactoring comment="Introduce factory &apos;holding&apos; in &apos;scratch.MyContainer&apos; for constructor &apos;scratch.MyContainer.MyContainer()&apos;
- Original project: &apos;scratch&apos;
- Original element: &apos;scratch.MyContainer.MyContainer()&apos;
- Factory name: &apos;holding&apos;
- Owner class: &apos;scratch.MyContainer&apos;"
description="Introduce factory for &apos;MyContainer&apos;"
element1="/src<scratch{MyContainer.java[MyContainer"
flags="589830" id="org.eclipse.jdt.ui.introduce.factory"
input="/src<scratch{MyContainer.java"
name="holding" project="scratch" protect="false"
selection="89 0" version="1.0"/>
</session>
This should hold enough information to apply the refactoring to another project that references (a current version) of the class via a jar file.
But trying to apply the refactoring gives me this error:
The refatoring 'Introduce Factory' (org.eclipse.jdt.ui.introduce.factory) cannot be performed, since its input 'scratch.MyContainer.java' does not exist.
Note that it explicitly mentions .java, so it's looking for a source file and isn't content with applying the refactoring to the binary type.
Note: I don't want to patch the binary library. I just want the callers of the original constructor to be converted to calling the factory method instead.
Note 2: Search-and-replace won't help in my specific case. The reason is that I have two constructors, one taking an Object and the other takes a String. They should be mapped on two different factory methods, so I'd need the search-and-replace function to look at the type information of the argument as well.
Note 3: even a way to manually specify "replace instantiations of object X with this constructor with calls to this method instead" would be appreciated.
Update: There is a bugzilla entry on the Eclipse bugzilla that is about adding a replace invocation refactoring. An implementation of that feature might be able to solve my problem, if "invocation" includes object instantiation via a given constructor. Unfortunately it saw no activity in the last few years (although it was planned for 3.2).
This is not a way to apply refactoring to callers only but it may get you past your problem.
Would it be possible to add to your project a fake MyContainer.java file that has the same signatures as that in the library, apply your refactoring and then discard the java file? You could even use decompiled soure, though you might have to generify it yourself.
I think you will have to write your own refactoring plugin. The tests for org.eclipse.jdt.ui.tests.refactoring are an interesting place to start.
Some interesting reads on the topic:
http://www.eclipse.org/articles/article.php?file=Article-Unleashing-the-Power-of-Refactoring/index.html
http://www.eclipse.org/articles/Article-LTK/ltk.html

Categories

Resources