View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.bcel.generic;
18  
19  import static org.junit.jupiter.api.Assertions.assertEquals;
20  import static org.junit.jupiter.api.Assertions.assertNotNull;
21  import static org.junit.jupiter.api.Assertions.assertNull;
22  import static org.junit.jupiter.api.Assertions.assertTrue;
23  import static org.junit.jupiter.api.Assertions.fail;
24  
25  import java.io.File;
26  import java.util.ArrayList;
27  import java.util.List;
28  
29  import org.apache.bcel.AbstractTestCase;
30  import org.apache.bcel.Const;
31  import org.apache.bcel.classfile.AnnotationEntry;
32  import org.apache.bcel.classfile.ArrayElementValue;
33  import org.apache.bcel.classfile.ElementValue;
34  import org.apache.bcel.classfile.ElementValuePair;
35  import org.apache.bcel.classfile.JavaClass;
36  import org.apache.bcel.classfile.Method;
37  import org.apache.bcel.classfile.ParameterAnnotationEntry;
38  import org.apache.bcel.classfile.SimpleElementValue;
39  import org.apache.bcel.util.SyntheticRepository;
40  import org.junit.jupiter.api.Test;
41  
42  /**
43   * The program that some of the tests generate looks like this:
44   *
45   * <pre>
46   * public class HelloWorld {
47   *     public static void main(String[] argv) {
48   *         BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
49   *         String name = null;
50   *
51   *         try {
52   *             name = &quot;Andy&quot;;
53   *         } catch (IOException e) {
54   *             return;
55   *         }
56   *         System.out.println(&quot;Hello, &quot; + name);
57   *     }
58   * }
59   * </pre>
60   */
61  public class GeneratingAnnotatedClassesTestCase extends AbstractTestCase {
62      private void assertArrayElementValue(final int nExpectedArrayValues, final AnnotationEntry anno) {
63          final ElementValuePair elementValuePair = anno.getElementValuePairs()[0];
64          assertEquals("value", elementValuePair.getNameString());
65          final ArrayElementValue ev = (ArrayElementValue) elementValuePair.getValue();
66          final ElementValue[] eva = ev.getElementValuesArray();
67          assertEquals(nExpectedArrayValues, eva.length);
68      }
69  
70      private void assertMethodAnnotations(final Method method, final int expectedNumberAnnotations, final int nExpectedArrayValues) {
71          final String methodName = method.getName();
72          final AnnotationEntry[] annos = method.getAnnotationEntries();
73          assertEquals(expectedNumberAnnotations, annos.length, () -> "For " + methodName);
74          if (expectedNumberAnnotations != 0) {
75              assertArrayElementValue(nExpectedArrayValues, annos[0]);
76          }
77      }
78  
79      private void assertParameterAnnotations(final Method method, final int... expectedNumberOfParmeterAnnotations) {
80          final String methodName = "For " + method.getName();
81          final ParameterAnnotationEntry[] parameterAnnotations = method.getParameterAnnotationEntries();
82          assertEquals(expectedNumberOfParmeterAnnotations.length, parameterAnnotations.length, methodName);
83  
84          int i = 0;
85          for (final ParameterAnnotationEntry parameterAnnotation : parameterAnnotations) {
86              final AnnotationEntry[] annos = parameterAnnotation.getAnnotationEntries();
87              final int expectedLength = expectedNumberOfParmeterAnnotations[i++];
88              final int j = i;
89              assertEquals(expectedLength, annos.length, () -> methodName + " parameter " + j);
90              if (expectedLength != 0) {
91                  assertSimpleElementValue(annos[0]);
92              }
93          }
94          assertNotNull(method.getAttribute(Const.ATTR_RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS));
95          assertNull(method.getAttribute(Const.ATTR_RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS));
96      }
97  
98      private void assertSimpleElementValue(final AnnotationEntry anno) {
99          final ElementValuePair elementValuePair = anno.getElementValuePairs()[0];
100         assertEquals("id", elementValuePair.getNameString());
101         final SimpleElementValue ev = (SimpleElementValue) elementValuePair.getValue();
102         assertEquals(42, ev.getValueInt());
103     }
104 
105     private void buildClassContents(final ClassGen cg, final ConstantPoolGen cp, final InstructionList il) {
106         // Create method 'public static void main(String[]argv)'
107         final MethodGen mg = createMethodGen("main", il, cp);
108         final InstructionFactory factory = new InstructionFactory(cg);
109         // We now define some often used types:
110         final ObjectType iStream = new ObjectType("java.io.InputStream");
111         final ObjectType pStream = new ObjectType("java.io.PrintStream");
112         // Create variables in and name : We call the constructors, i.e.,
113         // execute BufferedReader(InputStreamReader(System.in)) . The reference
114         // to the BufferedReader object stays on top of the stack and is stored
115         // in the newly allocated in variable.
116         il.append(factory.createNew("java.io.BufferedReader"));
117         il.append(InstructionConst.DUP); // Use predefined constant
118         il.append(factory.createNew("java.io.InputStreamReader"));
119         il.append(InstructionConst.DUP);
120         il.append(factory.createFieldAccess("java.lang.System", "in", iStream, Const.GETSTATIC));
121         il.append(factory.createInvoke("java.io.InputStreamReader", "<init>", Type.VOID, new Type[] {iStream}, Const.INVOKESPECIAL));
122         il.append(factory.createInvoke("java.io.BufferedReader", "<init>", Type.VOID, new Type[] {new ObjectType("java.io.Reader")}, Const.INVOKESPECIAL));
123         LocalVariableGen lg = mg.addLocalVariable("in", new ObjectType("java.io.BufferedReader"), null, null);
124         final int in = lg.getIndex();
125         lg.setStart(il.append(new ASTORE(in))); // "in" valid from here
126         // Create local variable name and initialize it to null
127         lg = mg.addLocalVariable("name", Type.STRING, null, null);
128         final int name = lg.getIndex();
129         il.append(InstructionConst.ACONST_NULL);
130         lg.setStart(il.append(new ASTORE(name))); // "name" valid from here
131         // Create try-catch block: We remember the start of the block, read a
132         // line from the standard input and store it into the variable name .
133         // InstructionHandle try_start = il.append(factory.createFieldAccess(
134         // "java.lang.System", "out", p_stream, Constants.GETSTATIC));
135         // il.append(new PUSH(cp, "Please enter your name> "));
136         // il.append(factory.createInvoke("java.io.PrintStream", "print",
137         // Type.VOID, new Type[] { Type.STRING },
138         // Constants.INVOKEVIRTUAL));
139         // il.append(new ALOAD(in));
140         // il.append(factory.createInvoke("java.io.BufferedReader", "readLine",
141         // Type.STRING, Type.NO_ARGS, Constants.INVOKEVIRTUAL));
142         final InstructionHandle tryStart = il.append(new PUSH(cp, "Andy"));
143         il.append(new ASTORE(name));
144         // Upon normal execution we jump behind exception handler, the target
145         // address is not known yet.
146         final GOTO g = new GOTO(null);
147         final InstructionHandle tryEnd = il.append(g);
148         // We add the exception handler which simply returns from the method.
149         final LocalVariableGen varEx = mg.addLocalVariable("ex", Type.getType("Ljava.io.IOException;"), null, null);
150         final int varExSlot = varEx.getIndex();
151         final InstructionHandle handler = il.append(new ASTORE(varExSlot));
152         varEx.setStart(handler);
153         varEx.setEnd(il.append(InstructionConst.RETURN));
154         mg.addExceptionHandler(tryStart, tryEnd, handler, new ObjectType("java.io.IOException"));
155         // "Normal" code continues, now we can set the branch target of the GOTO
156         // .
157         final InstructionHandle ih = il.append(factory.createFieldAccess("java.lang.System", "out", pStream, Const.GETSTATIC));
158         g.setTarget(ih);
159         // Printing "Hello": String concatenation compiles to StringBuffer
160         // operations.
161         il.append(factory.createNew(Type.STRINGBUFFER));
162         il.append(InstructionConst.DUP);
163         il.append(new PUSH(cp, "Hello, "));
164         il.append(factory.createInvoke("java.lang.StringBuffer", "<init>", Type.VOID, new Type[] {Type.STRING}, Const.INVOKESPECIAL));
165         il.append(new ALOAD(name));
166         il.append(factory.createInvoke("java.lang.StringBuffer", "append", Type.STRINGBUFFER, new Type[] {Type.STRING}, Const.INVOKEVIRTUAL));
167         il.append(factory.createInvoke("java.lang.StringBuffer", "toString", Type.STRING, Type.NO_ARGS, Const.INVOKEVIRTUAL));
168         il.append(factory.createInvoke("java.io.PrintStream", "println", Type.VOID, new Type[] {Type.STRING}, Const.INVOKEVIRTUAL));
169         il.append(InstructionConst.RETURN);
170         // Finalization: Finally, we have to set the stack size, which normally
171         // would have to be computed on the fly and add a default constructor
172         // method to the class, which is empty in this case.
173         mg.setMaxStack();
174         mg.setMaxLocals();
175         cg.addMethod(mg.getMethod());
176         il.dispose(); // Allow instruction handles to be reused
177         cg.addEmptyConstructor(Const.ACC_PUBLIC);
178     }
179 
180     private void buildClassContentsWithAnnotatedMethods(final ClassGen cg, final ConstantPoolGen cp, final InstructionList il) {
181         // Create method 'public static void main(String[]argv)'
182         final MethodGen mg = createMethodGen("main", il, cp);
183         final InstructionFactory factory = new InstructionFactory(cg);
184         mg.addAnnotationEntry(createSimpleVisibleAnnotation(mg.getConstantPool()));
185         // We now define some often used types:
186         final ObjectType iStream = new ObjectType("java.io.InputStream");
187         final ObjectType pStream = new ObjectType("java.io.PrintStream");
188         // Create variables in and name : We call the constructors, i.e.,
189         // execute BufferedReader(InputStreamReader(System.in)) . The reference
190         // to the BufferedReader object stays on top of the stack and is stored
191         // in the newly allocated in variable.
192         il.append(factory.createNew("java.io.BufferedReader"));
193         il.append(InstructionConst.DUP); // Use predefined constant
194         il.append(factory.createNew("java.io.InputStreamReader"));
195         il.append(InstructionConst.DUP);
196         il.append(factory.createFieldAccess("java.lang.System", "in", iStream, Const.GETSTATIC));
197         il.append(factory.createInvoke("java.io.InputStreamReader", "<init>", Type.VOID, new Type[] {iStream}, Const.INVOKESPECIAL));
198         il.append(factory.createInvoke("java.io.BufferedReader", "<init>", Type.VOID, new Type[] {new ObjectType("java.io.Reader")}, Const.INVOKESPECIAL));
199         LocalVariableGen lg = mg.addLocalVariable("in", new ObjectType("java.io.BufferedReader"), null, null);
200         final int in = lg.getIndex();
201         lg.setStart(il.append(new ASTORE(in))); // "in" valid from here
202         // Create local variable name and initialize it to null
203         lg = mg.addLocalVariable("name", Type.STRING, null, null);
204         final int name = lg.getIndex();
205         il.append(InstructionConst.ACONST_NULL);
206         lg.setStart(il.append(new ASTORE(name))); // "name" valid from here
207         // Create try-catch block: We remember the start of the block, read a
208         // line from the standard input and store it into the variable name .
209         // InstructionHandle try_start = il.append(factory.createFieldAccess(
210         // "java.lang.System", "out", p_stream, Constants.GETSTATIC));
211         // il.append(new PUSH(cp, "Please enter your name> "));
212         // il.append(factory.createInvoke("java.io.PrintStream", "print",
213         // Type.VOID, new Type[] { Type.STRING },
214         // Constants.INVOKEVIRTUAL));
215         // il.append(new ALOAD(in));
216         // il.append(factory.createInvoke("java.io.BufferedReader", "readLine",
217         // Type.STRING, Type.NO_ARGS, Constants.INVOKEVIRTUAL));
218         final InstructionHandle tryStart = il.append(new PUSH(cp, "Andy"));
219         il.append(new ASTORE(name));
220         // Upon normal execution we jump behind exception handler, the target
221         // address is not known yet.
222         final GOTO g = new GOTO(null);
223         final InstructionHandle tryEnd = il.append(g);
224         // We add the exception handler which simply returns from the method.
225         final LocalVariableGen varEx = mg.addLocalVariable("ex", Type.getType("Ljava.io.IOException;"), null, null);
226         final int varExSlot = varEx.getIndex();
227         final InstructionHandle handler = il.append(new ASTORE(varExSlot));
228         varEx.setStart(handler);
229         varEx.setEnd(il.append(InstructionConst.RETURN));
230         mg.addExceptionHandler(tryStart, tryEnd, handler, new ObjectType("java.io.IOException"));
231         // "Normal" code continues, now we can set the branch target of the GOTO
232         // .
233         final InstructionHandle ih = il.append(factory.createFieldAccess("java.lang.System", "out", pStream, Const.GETSTATIC));
234         g.setTarget(ih);
235         // Printing "Hello": String concatenation compiles to StringBuffer
236         // operations.
237         il.append(factory.createNew(Type.STRINGBUFFER));
238         il.append(InstructionConst.DUP);
239         il.append(new PUSH(cp, "Hello, "));
240         il.append(factory.createInvoke("java.lang.StringBuffer", "<init>", Type.VOID, new Type[] {Type.STRING}, Const.INVOKESPECIAL));
241         il.append(new ALOAD(name));
242         il.append(factory.createInvoke("java.lang.StringBuffer", "append", Type.STRINGBUFFER, new Type[] {Type.STRING}, Const.INVOKEVIRTUAL));
243         il.append(factory.createInvoke("java.lang.StringBuffer", "toString", Type.STRING, Type.NO_ARGS, Const.INVOKEVIRTUAL));
244         il.append(factory.createInvoke("java.io.PrintStream", "println", Type.VOID, new Type[] {Type.STRING}, Const.INVOKEVIRTUAL));
245         il.append(InstructionConst.RETURN);
246         // Finalization: Finally, we have to set the stack size, which normally
247         // would have to be computed on the fly and add a default constructor
248         // method to the class, which is empty in this case.
249         mg.setMaxStack();
250         mg.setMaxLocals();
251         cg.addMethod(mg.getMethod());
252         il.dispose(); // Allow instruction handles to be reused
253         cg.addEmptyConstructor(Const.ACC_PUBLIC);
254     }
255 
256     // helper methods
257     private ClassGen createClassGen(final String className) {
258         return new ClassGen(className, "java.lang.Object", "<generated>", Const.ACC_PUBLIC | Const.ACC_SUPER, null);
259     }
260 
261     public AnnotationEntryGen createCombinedAnnotation(final ConstantPoolGen cp) {
262         // Create an annotation instance
263         final AnnotationEntryGen a = createSimpleVisibleAnnotation(cp);
264         final ArrayElementValueGen array = new ArrayElementValueGen(cp);
265         array.addElement(new AnnotationElementValueGen(a, cp));
266         final ElementValuePairGen nvp = new ElementValuePairGen("value", array, cp);
267         final List<ElementValuePairGen> elements = new ArrayList<>();
268         elements.add(nvp);
269         return new AnnotationEntryGen(new ObjectType("CombinedAnnotation"), elements, true, cp);
270     }
271 
272     public AnnotationEntryGen createFruitAnnotation(final ConstantPoolGen cp, final String aFruit) {
273         final SimpleElementValueGen evg = new SimpleElementValueGen(ElementValueGen.STRING, cp, aFruit);
274         final ElementValuePairGen nvGen = new ElementValuePairGen("fruit", evg, cp);
275         final ObjectType t = new ObjectType("SimpleStringAnnotation");
276         final List<ElementValuePairGen> elements = new ArrayList<>();
277         elements.add(nvGen);
278         return new AnnotationEntryGen(t, elements, true, cp);
279     }
280 
281     private MethodGen createMethodGen(final String methodname, final InstructionList il, final ConstantPoolGen cp) {
282         return new MethodGen(Const.ACC_STATIC | Const.ACC_PUBLIC, // access
283             // flags
284             Type.VOID, // return type
285             new Type[] {new ArrayType(Type.STRING, 1)}, // argument
286             // types
287             new String[] {"argv"}, // arg names
288             methodname, "HelloWorld", // method, class
289             il, cp);
290     }
291 
292     public AnnotationEntryGen createSimpleInvisibleAnnotation(final ConstantPoolGen cp) {
293         final SimpleElementValueGen evg = new SimpleElementValueGen(ElementValueGen.PRIMITIVE_INT, cp, 4);
294         final ElementValuePairGen nvGen = new ElementValuePairGen("id", evg, cp);
295         final ObjectType t = new ObjectType("SimpleAnnotation");
296         final List<ElementValuePairGen> elements = new ArrayList<>();
297         elements.add(nvGen);
298         return new AnnotationEntryGen(t, elements, false, cp);
299     }
300 
301     public AnnotationEntryGen createSimpleVisibleAnnotation(final ConstantPoolGen cp) {
302         final SimpleElementValueGen evg = new SimpleElementValueGen(ElementValueGen.PRIMITIVE_INT, cp, 4);
303         final ElementValuePairGen nvGen = new ElementValuePairGen("id", evg, cp);
304         final ObjectType t = new ObjectType("SimpleAnnotation");
305         final List<ElementValuePairGen> elements = new ArrayList<>();
306         elements.add(nvGen);
307         return new AnnotationEntryGen(t, elements, true, cp);
308     }
309 
310     private void dumpClass(final ClassGen cg, final String fname) {
311         try {
312             final File f = createTestdataFile(fname);
313             cg.getJavaClass().dump(f);
314         } catch (final java.io.IOException e) {
315             System.err.println(e);
316         }
317     }
318 
319     private void dumpClass(final ClassGen cg, final String dir, final String fname) {
320         dumpClass(cg, dir + File.separator + fname);
321     }
322 
323     private JavaClass getClassFrom(final String where, final String clazzname) throws ClassNotFoundException {
324         // System.out.println(where);
325         final SyntheticRepository repos = createRepos(where);
326         return repos.loadClass(clazzname);
327     }
328 
329     /**
330      * Steps in the test:
331      * <ol>
332      * <li>Programmatically construct the HelloWorld program</li>
333      * <li>Add two simple annotations at the class level</li>
334      * <li>Save the class to disk</li>
335      * <li>Reload the class using the 'static' variant of the BCEL classes</li>
336      * <li>Check the attributes are OK</li>
337      * </ol>
338      */
339     @Test
340     public void testGenerateClassLevelAnnotations() throws ClassNotFoundException {
341         // Create HelloWorld
342         final ClassGen cg = createClassGen("HelloWorld");
343         cg.setMajor(49);
344         cg.setMinor(0);
345         final ConstantPoolGen cp = cg.getConstantPool();
346         final InstructionList il = new InstructionList();
347         cg.addAnnotationEntry(createSimpleVisibleAnnotation(cp));
348         cg.addAnnotationEntry(createSimpleInvisibleAnnotation(cp));
349         buildClassContents(cg, cp, il);
350         // System.out.println(cg.getJavaClass().toString());
351         dumpClass(cg, "HelloWorld.class");
352         final JavaClass jc = getClassFrom(".", "HelloWorld");
353         final AnnotationEntry[] as = jc.getAnnotationEntries();
354         assertEquals(2, as.length, "Wrong number of AnnotationEntries");
355         // TODO L??;
356         assertEquals("LSimpleAnnotation;", as[0].getAnnotationType(), "Wrong name of annotation 1");
357         assertEquals("LSimpleAnnotation;", as[1].getAnnotationType(), "Wrong name of annotation 2");
358         final ElementValuePair[] vals = as[0].getElementValuePairs();
359         final ElementValuePair nvp = vals[0];
360         assertEquals("id", nvp.getNameString(), "Wrong name of element in SimpleAnnotation");
361         final ElementValue ev = nvp.getValue();
362         assertEquals(ElementValue.PRIMITIVE_INT, ev.getElementValueType(), "Wrong type of element value");
363         assertEquals("4", ev.stringifyValue(), "Wrong value of element");
364         assertTrue(createTestdataFile("HelloWorld.class").delete());
365     }
366 
367     /**
368      * Just check that we can dump a class that has a method annotation on it and it is still there when we read it back in
369      */
370     @Test
371     public void testGenerateMethodLevelAnnotations1() throws ClassNotFoundException {
372         // Create HelloWorld
373         final ClassGen cg = createClassGen("HelloWorld");
374         final ConstantPoolGen cp = cg.getConstantPool();
375         final InstructionList il = new InstructionList();
376         buildClassContentsWithAnnotatedMethods(cg, cp, il);
377         // Check annotation is OK
378         int i = cg.getMethods()[0].getAnnotationEntries().length;
379         assertEquals(1, i, "Wrong number of annotations of main method prior to dumping");
380         dumpClass(cg, "temp1" + File.separator + "HelloWorld.class");
381         final JavaClass jc2 = getClassFrom("temp1", "HelloWorld");
382         // Check annotation is OK
383         i = jc2.getMethods()[0].getAnnotationEntries().length;
384         assertEquals(1, i, "Wrong number of annotation on JavaClass");
385         final ClassGen cg2 = new ClassGen(jc2);
386         // Check it now it is a ClassGen
387         final Method[] m = cg2.getMethods();
388         i = m[0].getAnnotationEntries().length;
389         assertEquals(1, i, "Wrong number of annotations on the main 'Method'");
390         final FieldGenOrMethodGen mg = new MethodGen(m[0], cg2.getClassName(), cg2.getConstantPool());
391         // Check it finally when the Method is changed to a MethodGen
392         i = mg.getAnnotationEntries().length;
393         assertEquals(1, i, "Wrong number of annotations on the main 'MethodGen'");
394 
395         assertTrue(delete("temp1", "HelloWorld.class"));
396     }
397 
398     /**
399      * Going further than the last test - when we reload the method back in, let's change it (adding a new annotation) and
400      * then store that, read it back in and verify both annotations are there ! Also check that we can remove method
401      * annotations.
402      */
403     @Test
404     public void testGenerateMethodLevelAnnotations2() throws ClassNotFoundException {
405         // Create HelloWorld
406         final ClassGen cg = createClassGen("HelloWorld");
407         final ConstantPoolGen cp = cg.getConstantPool();
408         final InstructionList il = new InstructionList();
409         buildClassContentsWithAnnotatedMethods(cg, cp, il);
410         dumpClass(cg, "temp2", "HelloWorld.class");
411         final JavaClass jc2 = getClassFrom("temp2", "HelloWorld");
412         final ClassGen cg2 = new ClassGen(jc2);
413         // Main method after reading the class back in
414         final Method mainMethod1 = jc2.getMethods()[0];
415         assertEquals(1, mainMethod1.getAnnotationEntries().length, "Wrong number of annotations of the 'Method'");
416         final MethodGen mainMethod2 = new MethodGen(mainMethod1, cg2.getClassName(), cg2.getConstantPool());
417         assertEquals(1, mainMethod2.getAnnotationEntries().length, "Wrong number of annotations of the 'MethodGen'");
418         final AnnotationEntryGen fruit = createFruitAnnotation(cg2.getConstantPool(), "Pear");
419         mainMethod2.addAnnotationEntry(fruit);
420         cg2.removeMethod(mainMethod1);
421         cg2.addMethod(mainMethod2.getMethod());
422         dumpClass(cg2, "temp3", "HelloWorld.class");
423         final JavaClass jc3 = getClassFrom("temp3", "HelloWorld");
424         final ClassGen cg3 = new ClassGen(jc3);
425         final Method mainMethod3 = cg3.getMethods()[1];
426         final int i = mainMethod3.getAnnotationEntries().length;
427         assertEquals(2, i, "Wrong number of annotations on the 'Method'");
428         mainMethod2.removeAnnotationEntry(fruit);
429         assertEquals(1, mainMethod2.getAnnotationEntries().length, "Wrong number of annotations on the 'MethodGen'");
430         mainMethod2.removeAnnotationEntries();
431         assertEquals(0, mainMethod2.getAnnotationEntries().length, 0, "Wrong number of annotations on the 'MethodGen'");
432         assertTrue(delete("temp2", "HelloWorld.class"));
433         assertTrue(delete("temp3", "HelloWorld.class"));
434     }
435 
436     /**
437      * Load a class in and modify it with a new attribute - A SimpleAnnotation annotation
438      */
439     @Test
440     public void testModifyingClasses1() throws ClassNotFoundException {
441         final JavaClass jc = getTestJavaClass(PACKAGE_BASE_NAME + ".data.SimpleAnnotatedClass");
442         final ClassGen cgen = new ClassGen(jc);
443         final ConstantPoolGen cp = cgen.getConstantPool();
444         cgen.addAnnotationEntry(createFruitAnnotation(cp, "Pineapple"));
445         assertEquals(2, cgen.getAnnotationEntries().length, "Wrong number of annotations");
446         dumpClass(cgen, "SimpleAnnotatedClass.class");
447         assertTrue(delete("SimpleAnnotatedClass.class"));
448     }
449 
450     /**
451      * Load a class in and modify it with a new attribute - A ComplexAnnotation annotation
452      */
453     @Test
454     public void testModifyingClasses2() throws ClassNotFoundException {
455         final JavaClass jc = getTestJavaClass(PACKAGE_BASE_NAME + ".data.SimpleAnnotatedClass");
456         final ClassGen cgen = new ClassGen(jc);
457         final ConstantPoolGen cp = cgen.getConstantPool();
458         cgen.addAnnotationEntry(createCombinedAnnotation(cp));
459         assertEquals(2, cgen.getAnnotationEntries().length, "Wrong number of annotations");
460         dumpClass(cgen, "SimpleAnnotatedClass.class");
461         final JavaClass jc2 = getClassFrom(".", "SimpleAnnotatedClass");
462         jc2.getAnnotationEntries();
463         assertTrue(delete("SimpleAnnotatedClass.class"));
464         // System.err.println(jc2.toString());
465     }
466 
467     /**
468      * Transform simple class from an immutable to a mutable object. The class is annotated with an annotation that uses an
469      * array of SimpleAnnotations.
470      */
471     @Test
472     public void testTransformClassToClassGen_ArrayAndAnnotationTypes() throws ClassNotFoundException {
473         final JavaClass jc = getTestJavaClass(PACKAGE_BASE_NAME + ".data.AnnotatedWithCombinedAnnotation");
474         final ClassGen cgen = new ClassGen(jc);
475         // Check annotations are correctly preserved
476         final AnnotationEntryGen[] annotations = cgen.getAnnotationEntries();
477         assertEquals(1, annotations.length, "Wrong number of annotations");
478         final AnnotationEntryGen a = annotations[0];
479         assertEquals(1, a.getValues().size(), "Wrong number of values for the annotation");
480         final ElementValuePairGen nvp = a.getValues().get(0);
481         final ElementValueGen value = nvp.getValue();
482         assertTrue(value instanceof ArrayElementValueGen, "Value should be ArrayElementValueGen but is " + value);
483         final ArrayElementValueGen arrayValue = (ArrayElementValueGen) value;
484         assertEquals(1, arrayValue.getElementValuesSize(), "Wrong size of the array");
485         final ElementValueGen innerValue = arrayValue.getElementValues().get(0);
486         assertTrue(innerValue instanceof AnnotationElementValueGen, "Value in the array should be AnnotationElementValueGen but is " + innerValue);
487         final AnnotationElementValueGen innerAnnotationValue = (AnnotationElementValueGen) innerValue;
488         assertEquals("L" + PACKAGE_BASE_SIG + "/data/SimpleAnnotation;", innerAnnotationValue.getAnnotation().getTypeSignature(), "Wrong type signature");
489 
490         // check the three methods
491         final Method[] methods = cgen.getMethods();
492         assertEquals(3, methods.length);
493         for (final Method method : methods) {
494             final String methodName = method.getName();
495             if (methodName.equals("<init>")) {
496                 assertMethodAnnotations(method, 0, 1);
497                 assertParameterAnnotations(method, 0, 1);
498             } else if (methodName.equals("methodWithArrayOfZeroAnnotations")) {
499                 assertMethodAnnotations(method, 1, 0);
500             } else if (methodName.equals("methodWithArrayOfTwoAnnotations")) {
501                 assertMethodAnnotations(method, 1, 2);
502             } else {
503                 fail(() -> "unexpected method " + method.getName());
504             }
505         }
506     }
507 
508     /**
509      * Transform simple class from an immutable to a mutable object. The class is annotated with an annotation that uses an
510      * enum.
511      */
512     @Test
513     public void testTransformClassToClassGen_EnumType() throws ClassNotFoundException {
514         final JavaClass jc = getTestJavaClass(PACKAGE_BASE_NAME + ".data.AnnotatedWithEnumClass");
515         final ClassGen cgen = new ClassGen(jc);
516         // Check annotations are correctly preserved
517         final AnnotationEntryGen[] annotations = cgen.getAnnotationEntries();
518         assertEquals(1, annotations.length, "Wrong number of annotations");
519     }
520 
521     // J5TODO: Need to add deleteFile calls to many of these tests
522     /**
523      * Transform simple class from an immutable to a mutable object.
524      */
525     @Test
526     public void testTransformClassToClassGen_SimpleTypes() throws ClassNotFoundException {
527         final JavaClass jc = getTestJavaClass(PACKAGE_BASE_NAME + ".data.SimpleAnnotatedClass");
528         final ClassGen cgen = new ClassGen(jc);
529         // Check annotations are correctly preserved
530         final AnnotationEntryGen[] annotations = cgen.getAnnotationEntries();
531         assertEquals(1, annotations.length, "Wrong number of annotations");
532     }
533 
534     /**
535      * Transform complex class from an immutable to a mutable object.
536      */
537     @Test
538     public void testTransformComplexClassToClassGen() throws ClassNotFoundException {
539         final JavaClass jc = getTestJavaClass(PACKAGE_BASE_NAME + ".data.ComplexAnnotatedClass");
540         final ClassGen cgen = new ClassGen(jc);
541         // Check annotations are correctly preserved
542         final AnnotationEntryGen[] annotations = cgen.getAnnotationEntries();
543         assertEquals(1, annotations.length, "Wrong number of annotations");
544         final List<?> l = annotations[0].getValues();
545         boolean found = false;
546         for (final Object name : l) {
547             final ElementValuePairGen element = (ElementValuePairGen) name;
548             if (element.getNameString().equals("dval") && element.getValue().stringifyValue().equals("33.4")) {
549                 found = true;
550             }
551         }
552         assertTrue(found, "Did not find double annotation value with value 33.4");
553     }
554 }