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