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