View Javadoc

1   /*
2    *  Copyright (c) 2005, 2006 Imola Informatica.
3    *  All rights reserved. This program and the accompanying materials
4    *  are made available under the terms of the LGPL License v2.1
5    *  which accompanies this distribution, and is available at
6    *  http://www.gnu.org/licenses/lgpl.html
7    */
8   
9   
10  package it.imolinfo.jbi4cics.webservices.utils.generators;
11  
12  import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
13  import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
14  import static org.objectweb.asm.Opcodes.ACC_SUPER;
15  import static org.objectweb.asm.Opcodes.ALOAD;
16  import static org.objectweb.asm.Opcodes.ARETURN;
17  import static org.objectweb.asm.Opcodes.GETFIELD;
18  import static org.objectweb.asm.Opcodes.ILOAD;
19  import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
20  import static org.objectweb.asm.Opcodes.INVOKESTATIC;
21  import static org.objectweb.asm.Opcodes.IRETURN;
22  import static org.objectweb.asm.Opcodes.PUTFIELD;
23  import static org.objectweb.asm.Opcodes.RETURN;
24  import static org.objectweb.asm.Opcodes.V1_1;
25  import it.imolinfo.jbi4cics.Logger;
26  import it.imolinfo.jbi4cics.LoggerFactory;
27  import it.imolinfo.jbi4cics.exception.ClassGenerationException;
28  import it.imolinfo.jbi4cics.exception.FormatException;
29  import it.imolinfo.jbi4cics.jbi.BCELClassLoader;
30  import it.imolinfo.jbi4cics.messageformat.FieldDescriptor;
31  import it.imolinfo.jbi4cics.messageformat.MappingDescriptor;
32  import it.imolinfo.jbi4cics.typemapping.cobol.CobolType;
33  import it.imolinfo.jbi4cics.typemapping.cobol.CobolTypeDescriptor;
34  import it.imolinfo.jbi4cics.webservices.descriptor.ServiceDescriptor;
35  import java.util.Map;
36  import org.objectweb.asm.ClassWriter;
37  import org.objectweb.asm.FieldVisitor;
38  import org.objectweb.asm.MethodVisitor;
39  import org.objectweb.asm.Type;
40  
41  public final class ServiceBeanGenerator {
42  
43      /**
44       * The logger for this class and its instances.
45       */
46      private static final Logger LOG
47              = LoggerFactory.getLogger(ServiceBeanGenerator.class);
48  
49      /**
50       * The name of the class that will be generated. This field contains values
51       * like <i>foo.bar.MyClass</i>.
52       */
53      private final String completeClassName;
54  
55      /**
56       * The name of the class that will be generated converted in an internal
57       * format, used by ASM library. It is calculated from
58       * {@link #completeClassName}.
59       */
60      private final String internalClassName;
61  
62      /**
63       * The mapping descriptor.
64       */
65      private final MappingDescriptor mappingDescriptor;
66  
67      /**
68       * The object able to generate a Java class "from scratch", without writing
69       * data on disk.
70       */
71      // 'true' to leave ClassWriter calculate computeMaxs fields in the bytecode
72      private final ClassWriter classWriter = new ClassWriter(true);
73  
74      public ServiceBeanGenerator(final ServiceDescriptor desc,
75                                  final boolean input) {
76          this(desc.getServiceInterfacePackageName() + "."
77               + (input ? desc.getInputBeanClassName()
78                        : desc.getOutputBeanClassName()),
79               input ? desc.getInputMappingDescriptor()
80                     : desc.getOutputMappingDescriptor());
81      }
82  
83      private ServiceBeanGenerator(final String completeClassName,
84                                   final MappingDescriptor mappingDescriptor) {
85          this.completeClassName = completeClassName;
86          this.mappingDescriptor = mappingDescriptor;
87  
88          internalClassName = completeClassName.replace('.', '/');
89          classWriter.visit(V1_1, ACC_PUBLIC + ACC_SUPER, internalClassName, null,
90                            "java/lang/Object", null);
91      }
92  
93      /**
94       * Per gestire il nesting devo potere genereare e usare al volo le classi
95       * visitando la gerarchia di classi in nesting con il metodo depth first. Le
96       * classi generate sono salvate nel bcel classloader che garantisce che
97       * possano essere caricate in seguito.
98       */
99      public Class generateBeanClass(final BCELClassLoader loader)
100             throws ClassGenerationException {
101         boolean debug = LOG.isDebugEnabled();
102         Class clazz;
103 
104         addDefaultConstructor();
105         addToStringMethod();
106         addEqualsMethod();
107         addHashCodeMethod();
108         try {
109             Map<String, FieldDescriptor> fields
110                     = mappingDescriptor.getFieldMap();
111 
112             for (Map.Entry<String, FieldDescriptor> entry : fields.entrySet()) {
113                 String propertyName = entry.getKey();
114                 FieldDescriptor fieldDescriptor = entry.getValue();
115                 Type type;
116 
117                 if (fieldDescriptor instanceof CobolTypeDescriptor) {
118                     CobolTypeDescriptor cobolTypeDescriptor
119                             = (CobolTypeDescriptor) fieldDescriptor;
120                     CobolType cobolType = cobolTypeDescriptor.getType();
121 
122                     if ((cobolType == CobolType.NESTED_COMMAREA)
123                             || (cobolType == CobolType.OCCURS)) {
124 
125                         // We need to generate the nested class
126                         String innerClassName =
127                                 completeClassName.substring(0, completeClassName.lastIndexOf('.') + 1)
128                                 + propertyName;
129                         ServiceBeanGenerator inner = new ServiceBeanGenerator(
130                                 innerClassName,
131                                 cobolTypeDescriptor.getNestedCommarea());
132 
133                         inner.generateBeanClass(loader);
134                     }
135                 }
136 
137                 // Create field, setter and getter
138                 if (debug) {
139                     LOG.debug("handling property name: " + propertyName);
140                 }
141                 type = Type.getType(fieldDescriptor.getPreferredJavaType());
142                 addPrivateField(propertyName, type);
143                 addSetMethod(propertyName, type);
144                 addGetMethod(propertyName, type);
145             }
146         } catch (FormatException e) {
147             Object[] args = new Object[] { completeClassName };
148 
149             LOG.error("CIC002400_Class_generation_error", args, e);
150             throw new ClassGenerationException(
151                     "CIC002400_Class_generation_error", args, e);
152         }
153 
154         loader.addClass(completeClassName, classWriter.toByteArray());
155         try {
156             clazz = loader.loadClass(completeClassName);
157         } catch (ClassNotFoundException e) {
158             Object[] args = new Object[] { completeClassName };
159 
160             LOG.error("CIC002400_Class_generation_error", args, e);
161             throw new ClassGenerationException(
162                     "CIC002400_Class_generation_error", args, e);
163         }
164         mappingDescriptor.setBeanClass(clazz);
165         return clazz;
166     }
167 
168     /**
169      * Adds the default constructor to the java class we're creating. The
170      * generated constructor is defined as <code>public</code>.
171      */
172     private void addDefaultConstructor() {
173         MethodVisitor mv = classWriter.visitMethod(
174                 ACC_PUBLIC, "<init>", "()V", null, null);
175 
176         // Pushes the 'this' variable
177         mv.visitVarInsn(ALOAD, 0);
178 
179         // Invokes the super class constructor
180         mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
181         mv.visitInsn(RETURN);
182 
183         // This code uses a maximum of one stack element and one local variable
184         mv.visitMaxs(1, 1);
185         mv.visitEnd();
186     }
187 
188     /**
189      * Creates the <code>toString()</code> method. The generated method is
190      * equivalent to:
191      * <pre><code>
192      * public String toString() {
193      *     return ReflectionToStringBuilder.toString(this);
194      * }
195      * </code></pre>
196      */
197     private void addToStringMethod() {
198         MethodVisitor mv = classWriter.visitMethod(ACC_PUBLIC,
199                 "toString", "()Ljava/lang/String;", null, null);
200 
201         mv.visitVarInsn(ALOAD, 0);
202         mv.visitMethodInsn(INVOKESTATIC,
203                 "org/apache/commons/lang/builder/ReflectionToStringBuilder",
204                 "toString", "(Ljava/lang/Object;)Ljava/lang/String;");
205         mv.visitInsn(ARETURN);
206         mv.visitMaxs(0, 0);
207         mv.visitEnd();
208     }
209 
210     /**
211      * Create the <code>equals(Object o)</code> method. The generated method is
212      * equivalent to:
213      * <pre><code>
214      * public boolean equals(Object obj) {
215      *     return EqualsBuilder.reflectionEquals(this, obj);
216      * }
217      * </code></pre>
218      */
219     private void addEqualsMethod() {
220         MethodVisitor mv = classWriter.visitMethod(
221                 ACC_PUBLIC, "equals", "(Ljava/lang/Object;)Z", null, null);
222 
223         mv.visitVarInsn(ALOAD, 0);
224         mv.visitVarInsn(ALOAD, 1);
225         mv.visitMethodInsn(INVOKESTATIC,
226                 "org/apache/commons/lang/builder/EqualsBuilder",
227                 "reflectionEquals", "(Ljava/lang/Object;Ljava/lang/Object;)Z");
228         mv.visitInsn(IRETURN);
229         mv.visitMaxs(0, 0);
230         mv.visitEnd();
231     }
232 
233     /**
234      * Create the <code>hashCode()</code> method. The generated method is
235      * equivalent to:
236      * <pre><code>
237      * public boolean equals(Object obj) {
238      *     return HashCodeBuilder.reflectionHashCode(this);
239      * }
240      * </code></pre>
241      */
242     private void addHashCodeMethod() {
243         MethodVisitor mv = classWriter.visitMethod(ACC_PUBLIC, "hashCode",
244                                                    "()I", null, null);
245 
246         mv.visitVarInsn(ALOAD, 0);
247         mv.visitMethodInsn(INVOKESTATIC,
248                            "org/apache/commons/lang/builder/HashCodeBuilder",
249                            "reflectionHashCode", "(Ljava/lang/Object;)I");
250         mv.visitInsn(IRETURN);
251         mv.visitMaxs(0, 0);
252         mv.visitEnd();
253     }
254 
255     /**
256      * Adds a <code>private</code> field to the java class in construction.
257      *
258      * @param  fieldName  the field name. Must be not <code>null</code>.
259      * @param  fieldType  the field type. Must be not <code>null</code>.
260      */
261     private void addPrivateField(final String fieldName, final Type fieldType) {
262         FieldVisitor fv = classWriter.visitField(
263                 ACC_PRIVATE, fieldName, fieldType.getDescriptor(), null, null);
264 
265         fv.visitEnd();
266     }
267 
268     /**
269      * Adds a getter method.
270      *
271      * @param  propertyName  the <i>java bean</i> property name. Must be not
272      *                       <code>null</code>.
273      * @param  propertyType  the <i>java bean</i> property type. Must be not
274      *                       <code>null</code>.
275      */
276     private void addGetMethod(final String propertyName,
277                               final Type propertyType) {
278         StringBuilder methodName = new StringBuilder(propertyName);
279         String returnType = propertyType.getDescriptor();
280         MethodVisitor mv;
281 
282         methodName.setCharAt(0, Character.toUpperCase(methodName.charAt(0)));
283         methodName.insert(0, "get");
284 
285         mv = classWriter.visitMethod(ACC_PUBLIC, methodName.toString(),
286                                      "()".concat(returnType), null, null);
287         mv.visitVarInsn(ALOAD, 0);
288         mv.visitFieldInsn(GETFIELD, internalClassName, propertyName,
289                           returnType);
290         mv.visitInsn(propertyType.getOpcode(IRETURN));
291         mv.visitMaxs(0, 0);
292     }
293 
294     /**
295      * Adds a setter method.
296      *
297      * @param  propertyName  the <i>java bean</i> property name. Must be not
298      *                       <code>null</code>.
299      * @param  propertyType  the <i>java bean</i> property type. Must be not
300      *                       <code>null</code>.
301      */
302     private void addSetMethod(final String propertyName,
303                               final Type propertyType) {
304         StringBuilder methodName = new StringBuilder(propertyName);
305         String desc = propertyType.getDescriptor();
306         MethodVisitor mv;
307 
308         methodName.setCharAt(0, Character.toUpperCase(methodName.charAt(0)));
309         methodName.insert(0, "set");
310 
311         mv = classWriter.visitMethod(ACC_PUBLIC, methodName.toString(),
312                                      "(" + desc + ")V", null, null);
313         mv.visitVarInsn(ALOAD, 0);
314         mv.visitVarInsn(propertyType.getOpcode(ILOAD), 1);
315         mv.visitFieldInsn(PUTFIELD, internalClassName, propertyName, desc);
316         mv.visitInsn(RETURN);
317         mv.visitMaxs(0, 0);
318     }
319 }