Trees, Elements, Types and Tokens
Conversions between trees and elements and types...
Getting to the javac information = writting a java infrastructure task
Class Files vs. Signature Files
How to setup a module which uses the Java related APIs
How do I Get into the Javac Context for a File?
How do I Get All Methods/Fields/Constructors of a Class?
How do I do modification to a source file?
TreeMaker difficulties, Questions & Answers
How do I Find All Usages of a Class?
How do I get an ElementHandle if all I have is the Project and the class name?
When working with the Java related APIs in the NetBeans IDE we recommend making yourself familiar with some of the basic concepts which are used. The APIs are based directly on the APIs of the javac compiler. Some of the APIs are taken directly from javac. Some are added in order to integrate javac into the IDE properly and in order to supply functionality not provided by javac (e.g. modifying code). Being able to understand the basic of how javac works also helps.
Please note that there are some pitfalls when using these APIs. Make sure you know what these are and how to detect basic mistakes (both described later in the text) in order to not harm the performance of the IDE.
The essential packages a module writer will be interested in are:
When compiling source files, javac works in phases. Knowing these phases can help you make your code more performant. When writing a task which looks into the source you should think first about what phase you need javac to be in. The less work required, the sooner your task will be run by the infrastructure. The important phases are:
In order to efficiently model the Java 5.0 Language, three different hierarchies are defined:
All three hierarchies form the javac APIs. All the hierarchies are read only; if you want to make changes to the code you will have to go and consult the NetBeans API described later.
PITFALL - Don't try to implement the interfaces!
Even if all the APIs are done using interfaces. These interfaces are not there for users to implement them. Trying to provide your own implementation of a Tree, an Element or a Type and putting it back as a parameter to a method call to the APIs will very likely result into ClassCastException.
PITFALL - Don't relay on instanceof!
As there are several subinterfaces for various elements or trees (e.g. MethodTree, VariableTree, ExecutableElement, VariableElement, ... ) you may be tempted to test for it using instance of e.g. writing code like:
if( myTree instanceof VariableTree ) {
... do something with the variable ...
}
This is generally incorrect. Notice that it is not guaranteed that the interfaces that all or some of the interfaces are not implemented by the same class. Therefore results of instanceof operator may not be what you would expect.
Instead of using the instanceof operator rather call a getKind() method. This method returns an enum constant which describes the kind of the Tree, Element or Type correctly. See. ElementKind, TypeKind, Tree.Kind.
correct for of the code above would be
if( myTree.getKind() == Tree.Kind.VARIABLE ) {
... do something with the variable ...
}
Sometimes you need to go from element to a tree, or from a type to an element and vice versa.
PITFALL Methods for going from a Tree to an Element or a Type require TreePath
It is not enough to send the Tree as a parameter. There are basically several ways how to get a TreePath:
Now you know that there is the javac, how it works and what data it produces. The question is how the integration into the IDE is done. In the IDE there can be lots of modules interested in getting information about the sources. These modules have to "compete" for the information in a well defined way. This is where the javac phases come into play. A module writer can register tasks which will run after javac completes a given phase. (There is an enum for the phases JavaSource.Phase). We will talk about how to register a task later in this document. If there are more tasks for a given phase, which usually is the case, then tasks are sorted by priority. (Again there is an enum for the priorities JavaSource.Pritority).
You may wonder why you should create and register tasks. Why can't you just do some blocking calls into the infrastructure? To understand that think of javac as a scarce resource which has to be accessed by one thread only at a given time. In order to manage such resources you have to introduce some mechanism to access it e.g. locks, transactions or whatever. The NetBeans java infrastructure chose to do the access using a queue of tasks which are then run in defined order (as described above). This is very similar what Java platform does in the AWT. In AWT you also call InvokeLater(Runnable) where the runnable specifies what will be done when the task comes to run. The difference between AWT InvokeLater and a task in the Java infrastructure is that unlike in AWT the registered tasks run again when the source is modified. This approach permits the infrastructure to do some important things. E.g. cancelling currently running task and run some more important task. This may happen when the user starts typing in the editor or invokes code completion explicitly. At such events the user is more interested in seeing the result of the actions immediately rather than waiting for some low priority task to be finished.
PITFALL (big one) Don't hold objects from javac when your task finishes
This may seem strange at first look. But there is a good reason for not doing it. If you will remember a Tree, Type or Element outside of the task then the remembered object will hold all the javac created objects in memory. This may be a relatively large amount of data. Especially those classes which refer to many other classes have big closure of what has to be loaded and analysed in order to resolve the class correctly. Therefore remembering javac objects is very dangerous and may produce large memory leaks. Not only that after your task finishes the data may become useless very soon (when the file changes again). So you might be working on incorrect data which may later lead to further errors. There are ways around this problem (described later) e.g. using the ElementHandle.
List of packages/classes which should be used only inside of a task
com.sun.javadoc.* com.sun.source.tree.* (except: com.sun.source.tree.Tree.Kind and com.sun.source.tree.TreeVisitor) com.sun.source.util.* (except: com.sun.source.util.SimpleTreeVisitor, com.sun.source.util.TreeScanner, com.sun.source.util.TreePathScanner?) javax.lang.model.element.* (except: ElementKind, ElementVisitor, AnnotationValueVisitor, Modifier, NestingKind) javax.lang.model.type.* (except TypeKind and TypeVisitor) javax.lang.model.util.Elements javax.lang.model.util.Types javax.tools.Diagnostics org.netbeans.api.java.source.CompilationController org.netbeans.api.java.source.CompilationInfo org.netbeans.api.java.source.WorkingCopy org.netbeans.api.java.source.ElementUtilities org.netbeans.api.java.source.CommentUtilities org.netbeans.api.java.source.TreeUtilities org.netbeans.api.java.source.Comment org.netbeans.api.java.source.TreeMaker
PITFALL Choose priority carefully
We've already explained why choosing the correct phase is important. The proper priority is important as well. Do not set the priority of your task too high. It is usually unnecessary and the higher priorities should remain reserved for tasks which have high demand on responsibility (e.g. code completion or coloring). Tasks which do things like putting annotations into the error stripe can usually wait a bit as it is fine to show these with some delay.
PITFALL Why tasks should be fast and really cancellable
The fact that the faster your tasks work the better for the IDE's performance is obvious. However, you should also try to make the task really cancellable. I.e. when the method cancel() is called on your task you should immediately stop the task. If you keep the task running you can considerably hurt the performance of other important tasks like editing, code completion, showing errors etc.
In many cases your task will need to traverse the AST. This is usually done using a visitor or scanner. In such cases you may want to look at the org.netbeans.java.source.support.CancellableTreeScanner/CancellableTreePathScanner, which will save you the work with checking for cancellation in your visitor.
Another smart thing to do when coding a module containing the tasks is to run your testing copy of the IDE with -J-Dorg.netbeans.api.java.source.JavaSource.reportSlowTasks=true which will report long running tasks and those tasks which do not react to cancel by printing them into the console.
After brief description of how the infrastructure works we need to look at writing a task in more detail. The interface you need to implement looks like:
public interface CancellableTask<P> {
public void cancel();
public void run( P parameter ) throws Exception;
}
We already mentioned what is the cancel() method good for. The more interesting and more important method obviously is the run() method. You may see that it takes a parameter of type P. P can be generally anything as the interface can be used in other context than the java infrastructure as well. However, when using it for implementation of the java related tasks the P can become: CompilationInfo, CompilationController or WorkingCopy. It depends in how you registered or started your task. Considering the ordering each of the parameter types adds some functionality you may use.
The last thing to do with a correctly implemented task is to let it really run. There are two basic ways how to do it. First, register it into the infrastructure so that it will be run automatically when the source gets modified. Second, running it directly.
Use the former option if:
Use the later option:
There are support classes for the most used cases of repeatingly running tasks. The classes are in the org.netbeans.java.source.support package. Generally you need to subclass the factory of your choice (see later) and implement the abstract methods or override some methods. You will need to implement the getPhase() and getPriority() methods and return an enum constant of your choice in order to indicate at which javac phase and with which priority the tasks created by this factory should run. (See above for discussion about phases and priorities). The most important method to implement is createTask( FileObject ), which has to return the task to be run.
Once you have your factory implemented you will need to register it into the global Lookup. To do so create a folder called META-INF.services in your project and put a file called org.netbeans.java.source.JavaSourceTaskFactory (note: has this changed to org.netbeans.api.java.source.JavaSourceTaskFactory?) in the folder. In this file list the fully qualified names (one per line) of all factories you want to register. So if you want to register two factories the file content would look like:
foo.bar.my.java.tasks.MyEdtiorAwareTaskFactory foo.bar.my.java.tasks.MyCaretAwareTaskFactory
public class JavaSourceTaskFactoryImpl extends EditorAwareJavaSourceTaskFactory {
public CancellableTask<CompilationInfo> createTask(FileObject file) {
return new MyTask();
}
public Priority getPriority() {
return Priority.LOW;
}
public Phase getPhase() {
return Phase.RESOLVED;
}
}
The run( CompilationInfo ) of your task will then be run every time a file changes in the editor and the compiler gets into the state where all identifiers are resolved.
As was mentioned above, it is not possible to hold Elements, TypeMirrors and Trees and to compare instances got from different javac instances.
To solve this limitation, ElementHandle was introduced. The ElementHandle allows to pass an Element from one javac instance to another. In the first instance of javac, create the handle using ElementHandle.create(Element) method. Keep the resulting handle, but do not hold the element itself. In the other instance of javac, use ElementHandle.resolve(CompilationInfo) method to resolve the ElementHandle back to the Element.
As was mentioned above, it is not possible to hold Elements, TypeMirrors and Trees and to compare instances got from different javac instances.
To solve this limitation, ElementHandle and TreePathHandlewas introduced. The TreePathHandle allows to pass an TreePath from one javac instance to another. In the first instance of javac, create the handle using TreePathHandle.create(Element, CompilationInfo) method. Keep the resulting handle, but do not hold the tree or tree path itself. In the other instance of javac, use TreePathHandle.resolve(CompilationInfo) method to resolve the TreePathHandle back to the TreePath.
There is no page called 'JavaHT_ComparingTreesElements'. Would you like to create it?
Parsing a file from the source code is time and memory consuming task, compared to simple loading of a class file. So, it is important for all the authors of a code in the IDE to know if their code requires parsing a source file or if it is sufficient to use data from a class file.
Another significant difference between these two approaches is that the Trees are available only if the file has been compiled from the source file. The Elements load from a class file are supposed to be "identical" to elements got by parsing the corresponding source file.
The rule of thumb here is that if you need only classes, their methods, constructors, fields and inner classes and their annotations, loading the data from the class file is sufficient for you. If you need access of the method content (statements), etc., you need to get data by parsing the source code.
Although the previous text spoke about loading class files, the IDE does not use true class files much. When a file is compiled by the IDE, the result is not a class file, but rather a "signature" file. These files contain specification of classes and signatures of their members. The main differences between class files and signature files are:
The format of the signature files is subject of change at any time, as it is used only to create the IDE's caches.
Note: for most purposes, it is not important if an Element has been load from the class file or from the signature file.
TBD: Describe necessary module dependencies
First of all, think about what are you trying to do. There are different ways to enter the javac context for different usecases. You may go and consult Registering/Running tasks first. Then if you decide that this is not the way you may continue reading here.
Find answers to these questions:
Please note that not all combinations of answers to the above questions make sense.
In all cases, you will need to create a CancellableTask, which will do the work.
| Read-only access, one file | Read-only access, more files | Write access, one file | Write access, more files | |
|---|---|---|---|---|
| Trees, on user action | I | II | III | IV |
| no trees, on user action | V | V | x (no write access without trees) | x (no write access without trees) |
| Trees, automatically | VI | x | x | x |
| no Trees, automatically | x | x | x | x |
I.: use JavaSource.forFileObject(/given file object/).runUserActionTask(CancellableTask<CompilationController>). Learn more on CompilationController usage ?below?.
II.: use JavaSource.create(ClasspathInfo, FileObject...).runUserActionTask(CancellableTask<CompilationController>). Learn more on CompilationController usage ?below?. Please see javadoc for the JavaSource.create method for more detailed information on how this works.
III.: use JavaSource.forFileObject(/given file object/).runModificationTask(CancellableTask<WorkingCopy>). Learn more on WorkingCopy usage ?below?. The runModificationTask method returns ModificationResult, which represents "textual" diff for changes the CancellableTask did. To commit these changes, perform ModificationResult.commit().
IV.: use JavaSource.create(ClasspathInfo, FileObject...).runModificationTask(CancellableTask<WorkingCopy>). Learn more on WorkingCopy usage ?below?. The runModificationTask method returns ModificationResult, which represents "textual" diff for changes the CancellableTask did. To commit these changes, perform ModificationResult.commit(). Please see javadoc for the JavaSource.create method for more detailed information on how this works.
V.: use JavaSource.create(ClasspathInfo)
Example:
protected void performAction(Node[] activatedNodes) {
DataObject dataObject = (DataObject) activatedNodes[0].getLookup().lookup(DataObject.class);
JavaSource js = JavaSource.forFileObject(dataObject.getPrimaryFile());
try {
js.runUserActionTask(new CancellableTask<CompilationController>() {
public void cancel() {}
public void run(CompilationController parameter) throws IOException {
parameter.toPhase(Phase.ELEMENTS_RESOLVED);
new MemberVisitor(parameter).scan(parameter.getCompilationUnit(), null);
}
}, true);
}
catch (IOException e) {
Logger.getLogger("global").log(Level.SEVERE, e.getMessage(), e);
}
}
private static class MemberVisitor extends TreePathScanner<Void, Void> {
private CompilationInfo info;
public MemberVisitor(CompilationInfo info) {
this.info = info;
}
@Override
public Void visitClass(ClassTree t, Void v) {
Element el = info.getTrees().getElement(getCurrentPath());
if (el == null) {
System.err.println("Cannot resolve class!");
}
else {
TypeElement te = (TypeElement) el;
System.err.println("Resolved class: " + te.getQualifiedName().toString());
//XXX: only as an example, uses toString on element, which should be used only for debugging
System.err.println("enclosed methods: " + ElementFilter.methodsIn(te.getEnclosedElements()));
System.err.println("enclosed types: " + ElementFilter.typesIn(te.getEnclosedElements()));
}
return null;
}
}
Most modifications are done through the API. Direct document changes are not recommended. Editing source through the API has many advantages, for instance it respects formatting settings.
This part will show you typical steps to make a modification to your source. There can be found different usecases, but this is the most common:
TODO: link to example, describe what the example does.
File tutorialFile = getFile(getSourceDir(), "/org/netbeans/test/codegen/Tutorial1.java");
JavaSource tutorialSource = JavaSource.forFileObject(FileUtil.toFileObject(tutorialFile));
The JavaSource represents the file Tutorial1.java in package org.netbeans.test.codegen.
CancellableTask task = new CancellableTask<WorkingCopy>() {
...
}
The interface contains two method, perhaps no surprise here:
CancellableTask task = new CancellableTask<WorkingCopy>() {
public void run(WorkingCopy workingCopy) throws Exception {
... our modification code
}
public void cancel() {
... cancel code
}
};
The run method contains all staff describing modification and we will dive in to the details later.
ModificationResult result = tutorialSource.runModificationTask(task);
result.commit();
Original source looks like:
package org.netbeans.test.codegen;
public class Tutorial1 {
}
At the end, we want to see source like:
package org.netbeans.test.codegen;
import java.io.Externalizable;
public class Tutorial1 implements Externalizable {
}
In short, this can be done with this code:
public void run(WorkingCopy workingCopy) throws IOException {
workingCopy.toPhase(Phase.RESOLVED); // is it neccessary?
CompilationUnitTree cut = workingCopy.getCompilationUnit();
TreeMaker make = workingCopy.getTreeMaker();
for (Tree typeDecl : cut.getTypeDecls()) {
if (Tree.Kind.CLASS == typeDecl.getKind()) {
ClassTree clazz = (ClassTree) typeDecl;
ExpressionTree implementsClause = make.Identifier("Externalizable");
implementsClause = make.Identifier("java.io.Externalizable");
TypeElement element = workingCopy.getElements().getTypeElement("java.io.Externalizable");
implementsClause = make.QualIdent(element);
ClassTree modifiedClazz = make.addClassImplementsClause(clazz, implementsClause);
workingCopy.rewrite(clazz, modifiedClazz);
} // end if
} // end for
} // end run
Here is steps description:
public class Tutorial1 implements Externalizable {
}
public class Tutorial1 implements java.io.Externalizable {
}
import java.io.Externalizable;
public class Tutorial1 implements Externalizable {
}
public void writeExternal(final ObjectOutput arg0) throws IOException {
throw new UnsupportedOperationException("Not supported yet.");
}
We have to prepare all elements belonging to method. First of all, prepare modifiers for the method. We use TreeMaker instance make again.
public void run(WorkingCopy workingCopy) throws IOException {
...
// create method modifier: public and no annotation
ModifiersTree methodModifiers = make.Modifiers(
Collections.<Modifier>singleton(Modifier.PUBLIC),
Collections.<AnnotationTree>emptyList()
);
...
}
Next step is preparing method parameter arg0 of type ObjectOutput and modifier final:
public void run(WorkingCopy workingCopy) throws IOException {
...
// create parameter:
// final ObjectOutput arg0
VariableTree parameter = make.Variable(
make.Modifiers(
Collections.<Modifier>singleton(Modifier.FINAL),
Collections.<AnnotationTree>emptyList()
),
"arg0", // name
make.Identifier("Object"), // parameter type
null // initializer - does not make sense in parameters.
);
...
}
Method throws exception, prepare exception identifier IOException. It is the same when we prepared interface for implements clause.
public void run(WorkingCopy workingCopy) throws IOException {
...
// prepare simple name to throws clause:
// 'throws IOException' and its import will be added (if it is not available yet)
TypeElement element = workingCopy.getElements().getTypeElement("java.io.IOException");
ExpressionTree throwsClause = make.QualIdent(element);
...
}
We have everything, what we need for method creation. Make method:
public void run(WorkingCopy workingCopy) throws IOException {
...
// create method.
MethodTree newMethod = make.Method(
methodModifiers, // public
"writeExternal", // writeExternal
make.PrimitiveType(TypeKind.VOID), // return type "void"
Collections.<TypeParameterTree>emptyList(), // type parameters - none
Collections.<VariableTree>singletonList(parameter), // final ObjectOutput arg0
Collections.<ExpressionTree>singletonList(throwsClause), // throws
"{ throw new UnsupportedOperationException(\"Not supported yet.\") }", // body text
null // default value - not applicable here, used by annotations
);
...
}
In the example above, we used the most often used factory method for source code method creation. It contains string for its body. You can add it as plain syntax correct text and engine will do imports and formatting stuff for you. There is also second method, which allows to add the body as a block:
public void run(WorkingCopy workingCopy) throws IOException {
...
// create method.
MethodTree newMethod = make.Method(
methodModifiers, // public
"writeExternal", // writeExternal
make.PrimitiveType(TypeKind.VOID), // return type "void"
Collections.<TypeParameterTree>emptyList(), // type parameters - none
Collections.<VariableTree>singletonList(parameter), // final ObjectOutput arg0
Collections.<ExpressionTree>singletonList(throwsClause), // throws
make.Block(Collections.<StatementTree>emptyList(), false), // empty statement block
null // default value - not applicable here, used by annotations
);
...
}
Example creates method with empty body.
At the end, do not forget to add it to type declaration and register change on working copy:
// and in the same way as interface was added to implements clause,
// add feature to the class as its member:
ClassTree modifiedClazz = make.addClassMember(clazz, newMethod);
workingCopy.rewrite(clazz, modifiedClazz);
...
}
Do you want to see it in a practice? Open the java/source project, go to unit test packages, then org.netbeans.api.java.source.gen package, open file TutorialTest.java and run it in IDE. You can experiment with it little bit.
Answer: Most of the actions can be replaced by the new method. There are several conceptual differences: The most important and visible is immutability of the new trees, so you cannot modify original model elements (in Java/MDR speech), you have to always create new tree (in Retouche speech). Here are couple of examples:
Question: I need to make textual replace of the tags from the template with some values in method body
Answer: Currently there is no direct way how to do that. We could consider to add method to obtain body as a text to TreeMaker class, but bear in mind that such a method does not modify source, so it is not correct place. For the time being, you can workaround it by following code:
(Code will not work, there are still many bugs.)
...
TreeMaker make = workingCopy.getTreeMaker();
...
MethodTree method = ...;
BlockTree body = method.getBody();
// get SourcePositions instance for your working copy and
// fetch out start and end position.
SourcePositions sp = workingCopy.getTrees().getSourcePositions();
int start = (int) sp.getStartPosition(cut, body);
int end = (int) sp.getEndPosition(cut, body);
// get body text from source text
String bodyText = workingCopy.getText().substring(start, end);
MethodTree modified = make.Method(
method.getModifiers(), // copy original values
method.getName(),
method.getReturnType(),
method.getTypeParameters(),
method.getParameters(),
method.getThrows(),
bodyText.replace("{0}", "-tag-replace-"), // replace body with the new text
null // not applicable here
);
// rewrite the original modifiers with the new one:
workingCopy.rewrite(method, modified);
...
Question: How do I create a constructor? Passing TypeKind.VOID as the return type to TreeMaker.Method() generates a method with a void return type.
Answer: by passing "<init>" as the method name and null as the return type. See also issue 88697.
Question: Create an Import and add it to a class
Retouche:
TreeMaker make;
CompilationUnitTree cut = ...;
CompilationUnitTree copy = make.addCompUnitImport(
cut,
make.Import(make.Identifier("java.io.IOException"), false)
);
workingCopy.rewrite(node, copy);
Note: There is a management tool which allows to handle imports automatically. Use make.QualIdent to create expression tree - import will be handled for it, if it does not exist yet.
Question: Create that some imports are not already there
This is some kind of workaround. Users should use automatic import management (QualIdentTree) in the most cases. For those who request manual handling, use similar scenario in retouche:
CompilationUnitTree cut = ...;
List<ImportTree> imports = cut.getImports();
for (ImportTree dovoz : imports) [
if ("whateverYouWant".equals(dovoz.getQualifiedIndetifier().toString())) {
found = true; break;
}
Note: It is not recommended to make your own import management, instead of, use the automatic one. Use your own solution only if automatic one does not fit to your needs!
Question: Create a method
It is very similar to other creations. All the stuff is done in TreeMaker. Here is a short example: Retouche:
// create modifiers for parameters (no modifier present) and create annotations. (again empty list)
ModifiersTree parMods = make.Modifiers(Collections.<Modifier>emptySet(), Collections.<AnnotationTree>emptyList());
// make a variable trees - representing parameters
VariableTree par1 = make.Variable(parMods, "a", make.PrimitiveType(TypeKind.INT), null);
VariableTree par2 = make.Variable(parMods, "b", make.PrimitiveType(TypeKind.FLOAT), null);
List<VariableTree> parList = new ArrayList<VariableTree>(2);
parList.add(par1);
parList.add(par2);
// now, start the method creation
MethodTree newMethod = make.Method(
make.Modifiers(Collections.singleton(Modifier.PUBLIC)), // modifiers and annotations
"newlyCreatedMethod", // name
make.PrimitiveType(TypeKind.VOID), // return type
Collections.<TypeParameterTree>emptyList(), // type parameters for parameters
parList, // parameters
Collections.singletonList(make.Identifier("java.io.IOException")), // throws
make.Block(Collections.EMPTY_LIST, false), // empty statement block
null // default value - not applicable here, used by annotations
);
Question: Add a method to a class
Consider you have created method and want to add it to a superclass:
Java/MDR:
Class clazz = ...; Method method = ...; clazz.getContents().add(method);
Retouche:
TreeMaker make = ...; ClassTree clazz = ...; MethodTree method = make.Method(...); ClassTree copy = make.addClassMember(clazz, method); workingCopy.rewrite(clazz, copy);
Question: Set a super class
Java/MDR:
Class clazz = ...;
clazz.setSuperClassName("motherClassName");
Retouche:
ClassTree clazz = ...;
ClassTree copy = clazz.setExtends(clazz, make.Identifier("MotherClassName");
workingCopy.rewrite(clazz, copy);
Question: Add a parameter to a constructor
Java/MDR:
Constructor construct = tgtClass.getConstructor(new ArrayList(), false);
Parameter param = pkg.getParameter().createParameter(
"theRef", // NOI18N
Collections.EMPTY_LIST, // annotations
false, // is final
getTypeRef(pkg, mbean.getWrappedClassName()), // typename
0, // dimCount
false);
construct.getParameters().add(param);
Retouche:
TreeMaker make = ...;
ClassTree clazz = ...;
MethodTree constr = ...;
VariableTree var = make.Variable(make.Modifiers(Collections.<Modifier>emptySet()), "theRef", make.Identifier("someType"), null);
MethodTree copy = make.addMethodParameter(constr, var);
workingCopy.rewrite(constr, copy);
Question: Add a field to a class
Java/MDR:
Class clazz = ...;
Field refField = pkg.getField().createField("theRef", Collections.EMPTY_LIST, Modifier.PRIVATE, null, ...);
clazz.getFeatures().add(0, refField);
Retouche:
TreeMaker make = ...;
ClassTree clazz = ...;
VariableTree var = make.Variable(make.Modifiers(Modifier.PUBLIC), "theRef", make.Identifier("someType", null);
ClassTree clazzCopy = make.insertClassMember(0, var);
workingCopy.rewrite(clazz, clazzCopy);
Note: All fields, local variables and parameters is represented by VariableTree in Jsr199.
Question: Remove constructor's 'throws' clause
Java/MDR:
Constructor construct = ...; construct.getExceptionNames().clear();
Retouche:
TreeMaker make = ...;
MethodTree method = ...;
MethodTree modified = make.Method( // copy original values
method.getModifiers(),
method.getName(),
method.getReturnType(),
method.getTypeParameters(),
method.getParameters(),
Collections.<ExpressionTree>emptyList(), // use empty list instead of orig. value
method.getBody(),
null // not applicable here
);
workingCopy.rewrite(method, modified);
For exact 'throws' item removal, you can use methods make.removeMethodThrows(...) in TreeMaker class.
Question: Clear body text
Java/MDR:
Method method = ...;
method.setBodyText("");
Retouche:
TreeMaker make = ...; MethodTree method = ..; BlockTree emptyBlock = make.Block(Collections.<StatementTree>emptyList(), false); workingCopy.rewrite(method.getBody(), emptyBlock);
Question: Type object
Question: Access to the String representation of a method body
There is not any direct support for such a functionality. You have to obtain positions and then cut the string from the source. We consider about adding such a method somewhere to the API. (Currently no suitable places has been found.)
Java/MDR:
String bodyText = method.getBodyText();
Retouche:
...
TreeMaker make = workingCopy.getTreeMaker();
...
MethodTree method = ...;
BlockTree body = method.getBody();
// get SourcePositions instance for your working copy and
// fetch out start and end position.
SourcePositions sp = workingCopy.getTrees().getSourcePositions();
int start = (int) sp.getStartPosition(cut, body);
int end = (int) sp.getEndPosition(cut, body);
// get body text from source text
String bodyText = workingCopy.getText().substring(start, end);
Question: Contextualize the Java classes search
Question: Add a set of Exceptions to a method
Java/MDR:
method.getExceptionNames().addAll(exceptions);
Retouche:
MethodTree node = ...; // original MethodTree node to modify
MethodTree copy = make.addMethodThrows(node, make.Identifier("IOException"));
copy = make.addMethodThrows(copy, make.Identifier("FileNotFoundException"));
workingCopy.rewrite(node, copy);
Short comment about functionality is available below in next question.
Question: How to make the class implements an interface
Java/MDR:
JavaModelPackage pkg = (JavaModelPackage) tgtClass.refImmediatePackage();
tgtClass.getInterfaceNames().add(pkg.getMultipartId().createMultipartId(
"MBeanRegistration", // NOI18N
null,
Collections.EMPTY_LIST));
Retouche:
workingCopy.toPhase(Phase.RESOLVED);
TreeMaker make = workingCopy.getTreeMaker();
ClassTree clazz = ...; // obtain class somewhere
ClassTree copy = class.addImplementsClause(make.Identifier("MBeanRegistration"));
workingCopy.rewrite(class, copy);
Java/MDR:
method.getExceptionNames().addAll(exceptions);
Retouche:
MethodTree method = ...;
List<ExpressionTree> listCopy = new ArrayList<ExpressionTree>(method.getThrows());
listCopy.add(exceptions);
MethodTree copy = make.Method(
method.getModifiers(),
method.getName(),
method.getReturnType(),
method.getTypeParameters(),
method.getParameters(),
listCopy,
method.getBody(),
null
);
workingCopy.rewrite(method, copy);
From the example above, it is obvious that this solution is not straightforward enough. You have also another chance how to do it:
Collection<IdentifierTree> exceptions = ...;
MethodTree method = ...;
MethodTree copy = make.addMethodThrows(method, make.Identifier("IOException"));
copy = make.addMethodThrows(copy, make.Identifier("FileNotFoundException"));
copy = make.addMethodThrows(copy, make.Identifier("IllegalArgumentException));
... etc.
workingCopy.rewrite(method, copy);
Bear in mind that in every next 'add' call, you have to use created copy instead of the original method MethodTree! (See the first parameter of addMethodThrows invocation.) This solution is shorter and simple to write, but it has minor performance impact when adding many items .It is up to user if he uses first or second solution, the result is the same. It is obvious that if you want to add just one item, you will use provided addMethodThrows method.
Note: Users can be confused when add multiple 'extends' clause to interface. Extends clause in interface is represented by 'implements' clause in class.
Question: How do I create a field with an initial value if the initial value is not a primitive, for example something like the following?
MyClass field = Something.getMyClass();
Answer: Obviously you have to create appropriate tree, in the example above method invocation with member select tree inside.
Such a code might look like:
...
CompilationUnitTree cut = workingCopy.getCompilationUnit();
TreeMaker make = workingCopy.getTreeMaker();
ClassTree clazz = (ClassTree) cut.getTypeDecls().get(0);
VariableTree var = make.Variable(make.Modifiers(
Collections.<Modifier>emptySet(), Collections.<AnnotationTree>emptyList()),
"myField",
make.Identifier("MyClass"),
make.MethodInvocation(
Collections.<ExpressionTree>emptyList(),
make.MemberSelect(
make.Identifier("Something"), "getMyClass"),
Collections.<ExpressionTree>emptyList()
)
);
ClassTree copy = make.addClassMember(clazz, var);
workingCopy.rewrite(clazz, copy);
...
Question: How to access and update the Javadoc of an existing class.
Currently totally broken, there are issues reported and will be addressed in M7: (#89873, #90302, #92325) There are methods in CommentHandlerService, they will be perhaps reused.
Tip: If you are not familiar with trees enough, write sample source code, run user action task against the source code and dump the tree to some readable form. That allows you to learn how expressions are represented in tree.
There is no page called 'JavaHT_FindUsagesOfClass'. Would you like to create it?