Netbeans Java Language Infrastructure

Whilst building the "Coherence Netbean Plug-in" I decided to work with the Netbeans "Java Language Infrastructure" which although reasonably well documented did not have the answers to all my questions. So given that and as a memory jogger for myself in future I have decide to put together this short blog that will show how to write the java, that uses the Java Language Infrastructure, required for the generating the Coherence Code as described in "Coherence Netbean Plug-in".

Overview

The Java Language Infrastructure is essentially a method of querying, writing and modifying Java code within the Netbeans editor. Once the Java Source has bee parsed, see Java Infrastructure Wiki, we can process the code as required. Within the "Coherence Netbean Plug-in" I needed to be able to examine the Java source code and automatically generate one of two methods adding all the required imports, implements, methods, parameters etc. This required that I understand and use a variety of the features, trees and elements available within the Netbeans Java Language Infrastructure.

My Common Variables

Because I was mostly concerned with the Fields within Java Source I was processing the majority of my code contains a loop similar to the following and I have attempted to keep the variable names consistent. Therefore I will define at this point what each of the variable does / contains and then simply refer to them later.

The following is a quick explanation of the key variables:

  1. WorkingCopy workingCopy:This is the current WorkingCopy copy of the Java source that we have accessed and retrieved on initiation of the insert code functions.
  2. ClassTree clazz:Top level Class Structure retrieved from the workingCopy.
  3. TreeMaker make:Utility class that will be used to call / build the appropriate Tree Structures that are required to build the actual code.
  4. Element el:Element that represents the top level class.
  5. Element enclosedElement:Currently processing sub-element of the top level class as accessed through the enclosedElements List.

As can be seen in lines 622 - 626 we retrieve all the Elements (Fields, Methods, etc) within the top level Class, clazz, and filter out the FIELD elements and then, because we are generating Coherence POF Code, further restrict our choice to those we can Serialize within a POF method.

Accessing Java Source

To access the Java Source we need to execute the following:

 85 /\*\*
86 \*
87 \* @param context containing JTextComponent and possibly other items registered by {@link CodeGeneratorContextProvider}
88 \*/
89 private POFSerializerGenerator(Lookup context) { // Good practice is not to save Lookup outside ctor
90 textComp = context.lookup(JTextComponent.class);
91 }
92

112 @Override
113 public void invoke() {
114 try {
115 Document doc = textComp.getDocument();
116 JavaSource javaSource = JavaSource.forDocument(doc);
117
118 // First we need to check if the code already exists within the java
119 // and if so is it ok to replace
120 if (isOkToModify(javaSource)) {
121 modifyJava(javaSource);
122 }
123
124 } catch (Exception e) {
125 Exceptions.printStackTrace(e);
126 }
127 }

Execute Modification and Access Working Copy

Once we have the Java Source we can retrieve the working copy and execute a series of modifications against it before saving the changes as a single undo-able task as follows:

 161 protected void modifyJava(JavaSource javaSource) throws IOException {

218 /\*
219 \* Create a working Task to edit the code
220 \* ======================================
221 \*
222 \* This Task is executed to generate and insert the generated code into
223 \* the java source. At this point we will be using the Java Source Compiler
224 \* to do all the heavy lifting required to correctly format and declare
225 \* required imports etc.
226 \*/
227 CancellableTask insertTask = new CancellableTask<WorkingCopy>() {

228

229 @Override
230 public void run(WorkingCopy workingCopy) throws IOException {
231 workingCopy.toPhase(Phase.RESOLVED);
232 CompilationUnitTree cut = workingCopy.getCompilationUnit();
233 TreeMaker make = workingCopy.getTreeMaker();
234 /\*
235 \* Loop through the top level Types although we know that their
236 \* will only be one this code is added for safety and once we have
237 \* processed the top level class we will exist the loop.
238 \*/
239 for (Tree typeDecl : cut.getTypeDecls()) {
240 if (Tree.Kind.CLASS == typeDecl.getKind()) {
241 ClassTree clazz = (ClassTree) typeDecl;
242 ClassTree modifiedClazz = clazz;
243 VariableTree parameter = null;
244 MethodTree newConstructor = null;
245 ClassTree innerClass = null;
246
247 // Create _remainder variable
248 parameter = createRemainderField(workingCopy);
249 // Modify the working copy
250 modifiedClazz = make.addClassMember(modifiedClazz, parameter);
251
252 // Create Constructor
253 newConstructor = createConstructor(workingCopy, clazz);
254 // Modify the working copy
255 modifiedClazz = make.addClassMember(modifiedClazz, newConstructor);
256
257 // Create Inner Serializer Class
258 innerClass = createInnerClass(workingCopy, clazz);
259 // Write Inner Class to Class
260 modifiedClazz = make.addClassMember(modifiedClazz, innerClass);
261
262 // Write Working Copy
263 workingCopy.rewrite(clazz, modifiedClazz);
264 }
265 }
266 }
267
268 @Override
269 public void cancel() {
270 }
271 };
272 // Commit the Code Changes
273 ModificationResult insertResult = javaSource.runModificationTask(insertTask);
274 insertResult.commit();
275
276 }

Here we can see that we create a CancellableTask and then execute it before committing the changes. Within the standard, overridden, run method we convert the workingCopy to the RESOLVED state, i.e. process the code to a state where we can modify it, and then retrieve the top level Class information before calling all our methods that generate the actual code that will be inserted into the workingCopy as can be seen between lines 247 and 263. Once we have generated code, say the Remainder Field at line 248, this can be inserted into the working copy by executing the TreeMaker addClassMember method passing the existing Class structure and the Code / Tree to be inserted, line 250, and then when all code changes are complete rewriting the working copy, at line 263, and then finally committing the change at line 274.

It can be seen in the above code that the result of the modification is always assigned to the working copy of the class, i.e. modifiedClazz = make.addClassMember(modifiedClazz, parameter);, and this is because we want to add the next modification to our previously modified code. If we do not do this only the last modification will be applied.

How to generated the code components

In this section I will explain what code is required to generate the Coherence Inner Serializer in the code below:

Before:

After:

I will take each of the components seen within the generated code as a sub-heading and show the generated code, from that seen above, and the actual Java code within my class that is required to generate the code.

Import Statement

Their is no specific code I use for inserting import statements into the code instead when you used make.QualIdent(element) to make a Qualified Identifier the generation of the import statement is handled for you and if it does not exist then one will be created.

Add Property to the Class

Adding the the _remainder field to the class "SimpleClass" on line 27 can be achieved as follows:

Generated Code

 26 // Required to store the Remainder portion for the Serialize / Deserialize functionality in case of Upgrades
27 private Binary _remainder;

Generation Source Code

 448 protected VariableTree createRemainderField(WorkingCopy workingCopy) {
449 TreeMaker make = workingCopy.getTreeMaker();
450 TypeElement element = null;
451 VariableTree parameter = null;
452
453 // Create _remainder variable
454 element = workingCopy.getElements().getTypeElement("com.tangosol.util.Binary");
455 parameter = make.Variable(
456 make.Modifiers(
457 Collections.<Modifier>singleton(Modifier.PRIVATE),
458 Collections.<AnnotationTree>emptyList()),
459 REMAINDER_VAR_NAME,
460 make.QualIdent(element),
461 null);
462 make.addComment(parameter, Comment.create(Comment.Style.LINE, VARIABLE_COMMENT), true);
463
464 return parameter;
465 }

The code above also shows the use of make.QualIdent(element) which will result in import com.tangosol.util.Binary;.

Add Method to the Class

To add the serialize method to the inner class requires the similar functionality to inserting a method into the main class the only difference is the class element we refer to when calling the make.addClassMember(innerClass, newMethod).

Generated Code

 64 /\*\*
65 \* Handles POF Write Serialization
66 \* @param writer POF Writer
67 \* @param obj Object to be Serialized
68 \* @throws IOException if an I/O error occurs
69 \*/
70 public void serialize(PofWriter writer, Object obj) throws IOException {
71 instance = (SimpleClass) obj;
72 writer.writeBooleanArray(FLAGS_IDX, flags);
73 writer.writeString(SIMPLESTR_IDX, simpleStr);
74 writer.writeLong(SIMPLELONG_IDX, simpleLong);
75 writer.writeRemainder(_remainder);
76 }

Generation Source Code

 538 // Add Write Serialize Method
539 methodModifiers =
540 make.Modifiers(Collections.<Modifier>singleton(Modifier.PUBLIC),
541 Collections.<AnnotationTree>emptyList());
542 methodParams = new ArrayList<VariableTree>();
543 // Writer Param
544 element = workingCopy.getElements().getTypeElement("com.tangosol.io.pof.PofWriter");
545 parameter =
546 make.Variable(make.Modifiers(Collections.<Modifier>emptySet()),
547 "writer",
548 make.QualIdent(element),
549 null);
550 methodParams.add(parameter);
551 // Object Param
552 element = workingCopy.getElements().getTypeElement("java.lang.Object");
553 parameter =
554 make.Variable(make.Modifiers(Collections.<Modifier>emptySet()),
555 "obj",
556 make.QualIdent(element),
557 null);
558 methodParams.add(parameter);
559 element = workingCopy.getElements().getTypeElement("java.io.IOException");
560 throwsClause = make.QualIdent(element);
561 newMethod =
562 make.Method(
563 methodModifiers, // Modifiers and Annotations
564 "serialize", // Name of the Method
565 make.PrimitiveType(TypeKind.VOID), // Return Type
566 Collections.<TypeParameterTree>emptyList(), // Type of Parameters for the Parameters
567 methodParams, // Parameters <VariableTree>
568 Collections.<ExpressionTree>singletonList(throwsClause), // Throws Clause
569 generateSerializerBodyStatements(workingCopy, clazz), // Body of the Methods as a Block of Expressions
570 null);
571 make.addComment(newMethod, Comment.create(Comment.Style.JAVADOC, SERIALIZE_JAVADOC), true);
572 // Modify the working copy
573 innerClass = make.addClassMember(innerClass, newMethod);
574

From the above we can see that lines 539 - 541 define that the method should have PUBLIC access and no annotations. Whilst lines 542 - 558 define the two methods parameters and line 559 - 560 specify the Throws Exception. The method is constructed between line 561 and 570 with line 571 adding a simple Javadoc comment before it is inserted into the class structure at line 573.

Specify Method Throws Clause

Generated Code

 70 public void serialize(PofWriter writer, Object obj) throws IOException {

Generation Source Code

 559 element = workingCopy.getElements().getTypeElement("java.io.IOException");
560 throwsClause = make.QualIdent(element);
561 newMethod =
562 make.Method(
563 methodModifiers, // Modifiers and Annotations
564 "serialize", // Name of the Method
565 make.PrimitiveType(TypeKind.VOID), // Return Type
566 Collections.<TypeParameterTree>emptyList(), // Type of Parameters for the Parameters
567 methodParams, // Parameters <VariableTree>
568 Collections.<ExpressionTree>singletonList(throwsClause), // Throws Clause
569 generateSerializerBodyStatements(workingCopy, clazz), // Body of the Methods as a Block of Expressions
570 null);

Specify Method Parameters

Generated Code

 70 public void serialize(PofWriter writer, Object obj) throws IOException {

Generation Source Code

 542 methodParams = new ArrayList<VariableTree>();
543 // Writer Param
544 element = workingCopy.getElements().getTypeElement("com.tangosol.io.pof.PofWriter");
545 parameter =
546 make.Variable(make.Modifiers(Collections.<Modifier>emptySet()),
547 "writer",
548 make.QualIdent(element),
549 null);
550 methodParams.add(parameter);
551 // Object Param
552 element = workingCopy.getElements().getTypeElement("java.lang.Object");
553 parameter =
554 make.Variable(make.Modifiers(Collections.<Modifier>emptySet()),
555 "obj",
556 make.QualIdent(element),
557 null);
558 methodParams.add(parameter);

561 newMethod =
562 make.Method(
563 methodModifiers, // Modifiers and Annotations
564 "serialize", // Name of the Method
565 make.PrimitiveType(TypeKind.VOID), // Return Type
566 Collections.<TypeParameterTree>emptyList(), // Type of Parameters for the Parameters
567 methodParams, // Parameters <VariableTree>
568 Collections.<ExpressionTree>singletonList(throwsClause), // Throws Clause
569 generateSerializerBodyStatements(workingCopy, clazz), // Body of the Methods as a Block of Expressions
570 null);

Insert Constants

Generated Code

 77 // Index Constants used to access the variables in the Serialize / Deserialize methods
78 public static final int FLAGS_IDX = 0;
79 public static final int SIMPLESTR_IDX = 1;
80 public static final int SIMPLELONG_IDX = 2;

Generation Source Code

It can be seen from the code above that the constant is specified by defining the PUBLIC STATIC FINAL, 916 - 918, and assigning them during the call to make.Variable. We can see that to assign a Literal values we, in this case because they are all int constants, use make.Literal(idx) to generate a simple number literal.

Add Inner Class

Generated Code

 48 /\*\*
49 \* POF Serializer which can be used instead of implementing PortableObject.
50 \* The following example XML can be used to reference the Serializer within
51 \* your <pof-config> although you will need to generate a TYPEID > 1000.
52 \*
53 \* <user-type>
54 \* <type-id>{TYPEID}</type-id>
55 \* <class-name>SimpleClass</class-name>
56 \* <serializer>
57 \* <class-name>SimpleClass$Serializer</class-name>
58 \* </serializer>
59 \* </user-type>
60 \*/
61 public static class Serializer implements PofSerializer {
62
63 boolean[] flags;
64 String simpleStr;
65 long simpleLong;
66 List simpleList;
67 Binary _remainder;
68 SimpleClass instance;
69
70 /\*\*
71 \* Handles POF Write Serialization
72 \* @param writer POF Writer
73 \* @param obj Object to be Serialized
74 \* @throws IOException if an I/O error occurs
75 \*/
76 public void serialize(PofWriter writer, Object obj) throws IOException {
77 instance = (SimpleClass) obj;
78 writer.writeBooleanArray(FLAGS_IDX, flags);
79 writer.writeString(SIMPLESTR_IDX, simpleStr);
80 writer.writeLong(SIMPLELONG_IDX, simpleLong);
81 writer.writeCollection(SIMPLELIST_IDX, simpleList);
82 writer.writeRemainder(_remainder);
83 }
84 // Index Constants used to access the variables in the Serialize / Deserialize methods
85 public static final int FLAGS_IDX = 0;
86 public static final int SIMPLESTR_IDX = 1;
87 public static final int SIMPLELONG_IDX = 2;
88 public static final int SIMPLELIST_IDX = 3;
89
90 /\*\*
91 \* Handles POF Read De-serialization
92 \* @param reader POF Reader
93 \* @throws IOException if an I/O error occurs
94 \*/
95 public Object deserialize(PofReader reader) throws IOException {
96 flags = reader.readBooleanArray(FLAGS_IDX);
97 simpleStr = reader.readString(SIMPLESTR_IDX);
98 simpleLong = reader.readLong(SIMPLELONG_IDX);
99 simpleList = (List) reader.readCollection(SIMPLELIST_IDX, simpleList);
100 _remainder = reader.readRemainder();
101 return new SimpleClass(flags, simpleStr, simpleLong, simpleList, _remainder);
102 }
103 }

Generation Source Code

 257 // Create Inner Serializer Class
258 innerClass = createInnerClass(workingCopy, clazz);
259 // Write Inner Class to Class
260 modifiedClazz = make.addClassMember(modifiedClazz, innerClass);

498 protected ClassTree createInnerClass(WorkingCopy workingCopy, ClassTree clazz) {
499 TreeMaker make = workingCopy.getTreeMaker();
500 TypeElement element = null;
501 VariableTree parameter = null;
502 ModifiersTree methodModifiers = null;
503 ModifiersTree paramModifiers = null;
504 ClassTree innerClass = null;
505 List<VariableTree> methodParams = null;
506 ModifiersTree classModifiers = null;
507 ExpressionTree throwsClause = null;
508 MethodTree newMethod = null;
509
510 paramModifiers = make.Modifiers(Collections.<Modifier>emptySet());
511 /\*
512 \* Get the parameters for the Class
513 \*/
...........................
606 // Modify the working copy
607 innerClass = make.addClassMember(innerClass, newMethod);
608
609 make.addComment(innerClass, Comment.create(Comment.Style.JAVADOC, INNER_CLASS_COMMENT.replace("{CLASS}", clazz.getSimpleName())), true);
610
611 return innerClass;
612 }

Create a Constructor

Generated Code

 32 /\*\*
33 \* Auto generated constructor that will be used by the Serializer to create new instances of SimpleClass.
34 \* @param flags
35 \* @param simpleStr
36 \* @param simpleLong
37 \* @param simpleList
38 \* @param _remainder
39 \*/
40 protected SimpleClass(boolean[] flags, String simpleStr, long simpleLong, List simpleList, Binary _remainder) {
41 this.flags = flags;
42 this.simpleStr = simpleStr;
43 this.simpleLong = simpleLong;
44 this.simpleList = simpleList;
45 this._remainder = _remainder;
46 }

Generation Source Code

 252 // Create Constructor
253 newConstructor = createConstructor(workingCopy, clazz);
254 // Modify the working copy
255 modifiedClazz = make.addClassMember(modifiedClazz, newConstructor);

467 protected MethodTree createConstructor(WorkingCopy workingCopy, ClassTree clazz) {
468 TreeMaker make = workingCopy.getTreeMaker();
469 ModifiersTree methodModifiers = null;
470 MethodTree newConstructor = null;
471 StringBuilder sbComment = new StringBuilder(CONSTRUCTOR_COMMENT);
472
473 /\*
474 \* Get the parameters for the Class
475 \*/
476 List<VariableElement> classParams = getClassParams(workingCopy, clazz);
477 List<VariableTree> constructorParams = getConstructorParams(workingCopy, clazz, classParams);
478
479 for (VariableTree param : constructorParams) {
480 sbComment.append("\\n@param ");
481 sbComment.append(param.getName().toString());
482 }
483
484 methodModifiers =
485 make.Modifiers(Collections.<Modifier>singleton(Modifier.PROTECTED),
486 Collections.<AnnotationTree>emptyList());
487 newConstructor = make.Constructor(
488 methodModifiers,
489 Collections.<TypeParameterTree>emptyList(),
490 constructorParams,
491 Collections.<ExpressionTree>emptyList(),
492 generateConstructorBodyStatements(workingCopy, clazz));
493 make.addComment(newConstructor, Comment.create(Comment.Style.JAVADOC, sbComment.toString().replace("{CLASS}", clazz.getSimpleName())), true);
494
495 return newConstructor;
496 }

Generate Assign Statements

Generated Code

 40 protected SimpleClass(boolean[] flags, String simpleStr, long simpleLong, List simpleList, Binary _remainder) {
41 this.flags = flags;
42 this.simpleStr = simpleStr;
43 this.simpleLong = simpleLong;
44 this.simpleList = simpleList;
45 this._remainder = _remainder;
46 }

Generation Source Code

 614 protected BlockTree generateConstructorBodyStatements(WorkingCopy workingCopy, ClassTree clazz) {
615 List<StatementTree> statements = new ArrayList<StatementTree>();
616 TreeMaker make = workingCopy.getTreeMaker();
617 Element el = workingCopy.getTrees().getElement(workingCopy.getTrees().getPath(workingCopy.getCompilationUnit(), clazz));
618 AssignmentTree assignment = null;
619
620 if (el == null) {
621 StatusDisplayer.getDefault().setStatusText("Cannot resolve class!");
622 } else {
623 TypeElement te = (TypeElement) el;
624 List enclosedElements = te.getEnclosedElements();
625 for (int i = 0; i < enclosedElements.size(); i++) {
626 Element enclosedElement = (Element) enclosedElements.get(i);
627 if (enclosedElement.getKind() == ElementKind.FIELD) {
628 if (isSerializable((VariableElement) enclosedElement)) {
629 assignment = make.Assignment(
630 make.Identifier("this.".concat(enclosedElement.getSimpleName().toString())),
631 make.Identifier(enclosedElement.getSimpleName()));
632
633 statements.add(make.ExpressionStatement(assignment));
634 }
635 }
636 }
637 assignment = make.Assignment(
638 make.Identifier("this.".concat(REMAINDER_VAR_NAME)),
639 make.Identifier(REMAINDER_VAR_NAME));
640
641 statements.add(make.ExpressionStatement(assignment));
642 }
643
644 return make.Block(statements, false);
645 }

Cast an Object

Generated Code

 99 simpleList = (List) reader.readCollection(SIMPLELIST_IDX, simpleList);

Generation Source Code

 647 protected BlockTree generateDeserializerBodyStatements(WorkingCopy workingCopy, ClassTree clazz) {
648 TreeMaker make = workingCopy.getTreeMaker();
649 Element classElement = workingCopy.getTrees().getElement(workingCopy.getTrees().getPath(workingCopy.getCompilationUnit(), clazz));
650 List<StatementTree> statements = new ArrayList<StatementTree>();
651 AssignmentTree assignment = null;
652 String methodName = null;
653 Tree typeCast = null;
654 List<ExpressionTree> arguments = null;
655 List<ExpressionTree> newClassArgs = new ArrayList<ExpressionTree>();
656 ExpressionTree expression = null;
657 MethodInvocationTree invocation = null;

721 } else if (isCollection(ve)) {
722 methodName = "readCollection";
723 arguments.add(make.Identifier(enclosedElement));
724 typeCast = ((VariableTree) workingCopy.getTrees().getTree(enclosedElement)).getType();

740 // Build Assignment and add to Statement Tree
741 invocation = make.MethodInvocation(
742 Collections.<ExpressionTree>emptyList(),
743 make.MemberSelect(make.Identifier("reader"), methodName),
744 arguments);

750 } else if (typeCast != null) {
751 expression = make.TypeCast(typeCast, invocation);
752 } else {
753 expression = invocation;
754 }
755 assignment = make.Assignment(
756 make.Identifier(enclosedElement.getSimpleName()),
757 expression);
758
759 statements.add(make.ExpressionStatement(assignment));

Comments:

Post a Comment:
Comments are closed for this entry.
About

As a member of the Oracle A-Team we specialise in enabling and supporting the Oracle Fusion Middleware communities.

Search

Archives
« April 2014
MonTueWedThuFriSatSun
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    
       
Today