📜 ⬆️ ⬇️

Java assembler, meta-programming and JPA

In this topic I want to share the first experience on writing a code generation system on the fly. The code implements some of the ideas described in the previous topic , and the code itself is used in one old, but working site management system .

Brief statement of the problem:

To perform this task was used:


Metamodel

Where to begin. It all starts with the construction of the object meta-model (not to be confused with the business model). So far it has been decided to dwell on the following model:
 - Configuration (complex types)
 - Type
  - Simple Type (simpletypetype - string / long / int / enum, enum class)
  - ComplexType (attributes, some properties)
 - Attribute (name, type, some properties, some JPA properties)

At the same time, for testing and, in general, for beauty, a class was made that allows building this configuration from XML Schema (the ideology of which was taken as the basis). For example from this:
<? xml version ="1.0" encoding ="utf-8" ? > < xs:schema xmlns:xs ="http://www.w3.org/2001/XMLSchema" xmlns:meta ="http://www.arptek.ru/core/meta" targetNamespace ="http://www.arptek.ru/core/meta/test" > < xs:complexType name ="Test" > < xs:annotation > < xs:appinfo > < meta:java.interface > ru.arptek.meta.SomeInterface </ meta:java.interface > </ xs:appinfo > </ xs:annotation > < xs:sequence > < xs:element name ="id" type ="xs:int" meta:jpa . Id ="true" /> < xs:element name ="string" type ="xs:string" /> < xs:element name ="boolean" type ="xs:boolean" /> < xs:element name ="integer" type ="xs:int" /> < xs:element name ="long" type ="xs:long" /> </ xs:sequence > </ xs:complexType > </ xs:schema > * This source code was highlighted with Source Code Highlighter .
  1. <? xml version ="1.0" encoding ="utf-8" ? > < xs:schema xmlns:xs ="http://www.w3.org/2001/XMLSchema" xmlns:meta ="http://www.arptek.ru/core/meta" targetNamespace ="http://www.arptek.ru/core/meta/test" > < xs:complexType name ="Test" > < xs:annotation > < xs:appinfo > < meta:java.interface > ru.arptek.meta.SomeInterface </ meta:java.interface > </ xs:appinfo > </ xs:annotation > < xs:sequence > < xs:element name ="id" type ="xs:int" meta:jpa . Id ="true" /> < xs:element name ="string" type ="xs:string" /> < xs:element name ="boolean" type ="xs:boolean" /> < xs:element name ="integer" type ="xs:int" /> < xs:element name ="long" type ="xs:long" /> </ xs:sequence > </ xs:complexType > </ xs:schema > * This source code was highlighted with Source Code Highlighter .
  2. <? xml version ="1.0" encoding ="utf-8" ? > < xs:schema xmlns:xs ="http://www.w3.org/2001/XMLSchema" xmlns:meta ="http://www.arptek.ru/core/meta" targetNamespace ="http://www.arptek.ru/core/meta/test" > < xs:complexType name ="Test" > < xs:annotation > < xs:appinfo > < meta:java.interface > ru.arptek.meta.SomeInterface </ meta:java.interface > </ xs:appinfo > </ xs:annotation > < xs:sequence > < xs:element name ="id" type ="xs:int" meta:jpa . Id ="true" /> < xs:element name ="string" type ="xs:string" /> < xs:element name ="boolean" type ="xs:boolean" /> < xs:element name ="integer" type ="xs:int" /> < xs:element name ="long" type ="xs:long" /> </ xs:sequence > </ xs:complexType > </ xs:schema > * This source code was highlighted with Source Code Highlighter .
  3. <? xml version ="1.0" encoding ="utf-8" ? > < xs:schema xmlns:xs ="http://www.w3.org/2001/XMLSchema" xmlns:meta ="http://www.arptek.ru/core/meta" targetNamespace ="http://www.arptek.ru/core/meta/test" > < xs:complexType name ="Test" > < xs:annotation > < xs:appinfo > < meta:java.interface > ru.arptek.meta.SomeInterface </ meta:java.interface > </ xs:appinfo > </ xs:annotation > < xs:sequence > < xs:element name ="id" type ="xs:int" meta:jpa . Id ="true" /> < xs:element name ="string" type ="xs:string" /> < xs:element name ="boolean" type ="xs:boolean" /> < xs:element name ="integer" type ="xs:int" /> < xs:element name ="long" type ="xs:long" /> </ xs:sequence > </ xs:complexType > </ xs:schema > * This source code was highlighted with Source Code Highlighter .
  4. <? xml version ="1.0" encoding ="utf-8" ? > < xs:schema xmlns:xs ="http://www.w3.org/2001/XMLSchema" xmlns:meta ="http://www.arptek.ru/core/meta" targetNamespace ="http://www.arptek.ru/core/meta/test" > < xs:complexType name ="Test" > < xs:annotation > < xs:appinfo > < meta:java.interface > ru.arptek.meta.SomeInterface </ meta:java.interface > </ xs:appinfo > </ xs:annotation > < xs:sequence > < xs:element name ="id" type ="xs:int" meta:jpa . Id ="true" /> < xs:element name ="string" type ="xs:string" /> < xs:element name ="boolean" type ="xs:boolean" /> < xs:element name ="integer" type ="xs:int" /> < xs:element name ="long" type ="xs:long" /> </ xs:sequence > </ xs:complexType > </ xs:schema > * This source code was highlighted with Source Code Highlighter .
  5. <? xml version ="1.0" encoding ="utf-8" ? > < xs:schema xmlns:xs ="http://www.w3.org/2001/XMLSchema" xmlns:meta ="http://www.arptek.ru/core/meta" targetNamespace ="http://www.arptek.ru/core/meta/test" > < xs:complexType name ="Test" > < xs:annotation > < xs:appinfo > < meta:java.interface > ru.arptek.meta.SomeInterface </ meta:java.interface > </ xs:appinfo > </ xs:annotation > < xs:sequence > < xs:element name ="id" type ="xs:int" meta:jpa . Id ="true" /> < xs:element name ="string" type ="xs:string" /> < xs:element name ="boolean" type ="xs:boolean" /> < xs:element name ="integer" type ="xs:int" /> < xs:element name ="long" type ="xs:long" /> </ xs:sequence > </ xs:complexType > </ xs:schema > * This source code was highlighted with Source Code Highlighter .
  6. <? xml version ="1.0" encoding ="utf-8" ? > < xs:schema xmlns:xs ="http://www.w3.org/2001/XMLSchema" xmlns:meta ="http://www.arptek.ru/core/meta" targetNamespace ="http://www.arptek.ru/core/meta/test" > < xs:complexType name ="Test" > < xs:annotation > < xs:appinfo > < meta:java.interface > ru.arptek.meta.SomeInterface </ meta:java.interface > </ xs:appinfo > </ xs:annotation > < xs:sequence > < xs:element name ="id" type ="xs:int" meta:jpa . Id ="true" /> < xs:element name ="string" type ="xs:string" /> < xs:element name ="boolean" type ="xs:boolean" /> < xs:element name ="integer" type ="xs:int" /> < xs:element name ="long" type ="xs:long" /> </ xs:sequence > </ xs:complexType > </ xs:schema > * This source code was highlighted with Source Code Highlighter .
  7. <? xml version ="1.0" encoding ="utf-8" ? > < xs:schema xmlns:xs ="http://www.w3.org/2001/XMLSchema" xmlns:meta ="http://www.arptek.ru/core/meta" targetNamespace ="http://www.arptek.ru/core/meta/test" > < xs:complexType name ="Test" > < xs:annotation > < xs:appinfo > < meta:java.interface > ru.arptek.meta.SomeInterface </ meta:java.interface > </ xs:appinfo > </ xs:annotation > < xs:sequence > < xs:element name ="id" type ="xs:int" meta:jpa . Id ="true" /> < xs:element name ="string" type ="xs:string" /> < xs:element name ="boolean" type ="xs:boolean" /> < xs:element name ="integer" type ="xs:int" /> < xs:element name ="long" type ="xs:long" /> </ xs:sequence > </ xs:complexType > </ xs:schema > * This source code was highlighted with Source Code Highlighter .
  8. <? xml version ="1.0" encoding ="utf-8" ? > < xs:schema xmlns:xs ="http://www.w3.org/2001/XMLSchema" xmlns:meta ="http://www.arptek.ru/core/meta" targetNamespace ="http://www.arptek.ru/core/meta/test" > < xs:complexType name ="Test" > < xs:annotation > < xs:appinfo > < meta:java.interface > ru.arptek.meta.SomeInterface </ meta:java.interface > </ xs:appinfo > </ xs:annotation > < xs:sequence > < xs:element name ="id" type ="xs:int" meta:jpa . Id ="true" /> < xs:element name ="string" type ="xs:string" /> < xs:element name ="boolean" type ="xs:boolean" /> < xs:element name ="integer" type ="xs:int" /> < xs:element name ="long" type ="xs:long" /> </ xs:sequence > </ xs:complexType > </ xs:schema > * This source code was highlighted with Source Code Highlighter .
  9. <? xml version ="1.0" encoding ="utf-8" ? > < xs:schema xmlns:xs ="http://www.w3.org/2001/XMLSchema" xmlns:meta ="http://www.arptek.ru/core/meta" targetNamespace ="http://www.arptek.ru/core/meta/test" > < xs:complexType name ="Test" > < xs:annotation > < xs:appinfo > < meta:java.interface > ru.arptek.meta.SomeInterface </ meta:java.interface > </ xs:appinfo > </ xs:annotation > < xs:sequence > < xs:element name ="id" type ="xs:int" meta:jpa . Id ="true" /> < xs:element name ="string" type ="xs:string" /> < xs:element name ="boolean" type ="xs:boolean" /> < xs:element name ="integer" type ="xs:int" /> < xs:element name ="long" type ="xs:long" /> </ xs:sequence > </ xs:complexType > </ xs:schema > * This source code was highlighted with Source Code Highlighter .
  10. <? xml version ="1.0" encoding ="utf-8" ? > < xs:schema xmlns:xs ="http://www.w3.org/2001/XMLSchema" xmlns:meta ="http://www.arptek.ru/core/meta" targetNamespace ="http://www.arptek.ru/core/meta/test" > < xs:complexType name ="Test" > < xs:annotation > < xs:appinfo > < meta:java.interface > ru.arptek.meta.SomeInterface </ meta:java.interface > </ xs:appinfo > </ xs:annotation > < xs:sequence > < xs:element name ="id" type ="xs:int" meta:jpa . Id ="true" /> < xs:element name ="string" type ="xs:string" /> < xs:element name ="boolean" type ="xs:boolean" /> < xs:element name ="integer" type ="xs:int" /> < xs:element name ="long" type ="xs:long" /> </ xs:sequence > </ xs:complexType > </ xs:schema > * This source code was highlighted with Source Code Highlighter .
  11. <? xml version ="1.0" encoding ="utf-8" ? > < xs:schema xmlns:xs ="http://www.w3.org/2001/XMLSchema" xmlns:meta ="http://www.arptek.ru/core/meta" targetNamespace ="http://www.arptek.ru/core/meta/test" > < xs:complexType name ="Test" > < xs:annotation > < xs:appinfo > < meta:java.interface > ru.arptek.meta.SomeInterface </ meta:java.interface > </ xs:appinfo > </ xs:annotation > < xs:sequence > < xs:element name ="id" type ="xs:int" meta:jpa . Id ="true" /> < xs:element name ="string" type ="xs:string" /> < xs:element name ="boolean" type ="xs:boolean" /> < xs:element name ="integer" type ="xs:int" /> < xs:element name ="long" type ="xs:long" /> </ xs:sequence > </ xs:complexType > </ xs:schema > * This source code was highlighted with Source Code Highlighter .
  12. <? xml version ="1.0" encoding ="utf-8" ? > < xs:schema xmlns:xs ="http://www.w3.org/2001/XMLSchema" xmlns:meta ="http://www.arptek.ru/core/meta" targetNamespace ="http://www.arptek.ru/core/meta/test" > < xs:complexType name ="Test" > < xs:annotation > < xs:appinfo > < meta:java.interface > ru.arptek.meta.SomeInterface </ meta:java.interface > </ xs:appinfo > </ xs:annotation > < xs:sequence > < xs:element name ="id" type ="xs:int" meta:jpa . Id ="true" /> < xs:element name ="string" type ="xs:string" /> < xs:element name ="boolean" type ="xs:boolean" /> < xs:element name ="integer" type ="xs:int" /> < xs:element name ="long" type ="xs:long" /> </ xs:sequence > </ xs:complexType > </ xs:schema > * This source code was highlighted with Source Code Highlighter .
  13. <? xml version ="1.0" encoding ="utf-8" ? > < xs:schema xmlns:xs ="http://www.w3.org/2001/XMLSchema" xmlns:meta ="http://www.arptek.ru/core/meta" targetNamespace ="http://www.arptek.ru/core/meta/test" > < xs:complexType name ="Test" > < xs:annotation > < xs:appinfo > < meta:java.interface > ru.arptek.meta.SomeInterface </ meta:java.interface > </ xs:appinfo > </ xs:annotation > < xs:sequence > < xs:element name ="id" type ="xs:int" meta:jpa . Id ="true" /> < xs:element name ="string" type ="xs:string" /> < xs:element name ="boolean" type ="xs:boolean" /> < xs:element name ="integer" type ="xs:int" /> < xs:element name ="long" type ="xs:long" /> </ xs:sequence > </ xs:complexType > </ xs:schema > * This source code was highlighted with Source Code Highlighter .
  14. <? xml version ="1.0" encoding ="utf-8" ? > < xs:schema xmlns:xs ="http://www.w3.org/2001/XMLSchema" xmlns:meta ="http://www.arptek.ru/core/meta" targetNamespace ="http://www.arptek.ru/core/meta/test" > < xs:complexType name ="Test" > < xs:annotation > < xs:appinfo > < meta:java.interface > ru.arptek.meta.SomeInterface </ meta:java.interface > </ xs:appinfo > </ xs:annotation > < xs:sequence > < xs:element name ="id" type ="xs:int" meta:jpa . Id ="true" /> < xs:element name ="string" type ="xs:string" /> < xs:element name ="boolean" type ="xs:boolean" /> < xs:element name ="integer" type ="xs:int" /> < xs:element name ="long" type ="xs:long" /> </ xs:sequence > </ xs:complexType > </ xs:schema > * This source code was highlighted with Source Code Highlighter .
  15. <? xml version ="1.0" encoding ="utf-8" ? > < xs:schema xmlns:xs ="http://www.w3.org/2001/XMLSchema" xmlns:meta ="http://www.arptek.ru/core/meta" targetNamespace ="http://www.arptek.ru/core/meta/test" > < xs:complexType name ="Test" > < xs:annotation > < xs:appinfo > < meta:java.interface > ru.arptek.meta.SomeInterface </ meta:java.interface > </ xs:appinfo > </ xs:annotation > < xs:sequence > < xs:element name ="id" type ="xs:int" meta:jpa . Id ="true" /> < xs:element name ="string" type ="xs:string" /> < xs:element name ="boolean" type ="xs:boolean" /> < xs:element name ="integer" type ="xs:int" /> < xs:element name ="long" type ="xs:long" /> </ xs:sequence > </ xs:complexType > </ xs:schema > * This source code was highlighted with Source Code Highlighter .
  16. <? xml version ="1.0" encoding ="utf-8" ? > < xs:schema xmlns:xs ="http://www.w3.org/2001/XMLSchema" xmlns:meta ="http://www.arptek.ru/core/meta" targetNamespace ="http://www.arptek.ru/core/meta/test" > < xs:complexType name ="Test" > < xs:annotation > < xs:appinfo > < meta:java.interface > ru.arptek.meta.SomeInterface </ meta:java.interface > </ xs:appinfo > </ xs:annotation > < xs:sequence > < xs:element name ="id" type ="xs:int" meta:jpa . Id ="true" /> < xs:element name ="string" type ="xs:string" /> < xs:element name ="boolean" type ="xs:boolean" /> < xs:element name ="integer" type ="xs:int" /> < xs:element name ="long" type ="xs:long" /> </ xs:sequence > </ xs:complexType > </ xs:schema > * This source code was highlighted with Source Code Highlighter .
  17. <? xml version ="1.0" encoding ="utf-8" ? > < xs:schema xmlns:xs ="http://www.w3.org/2001/XMLSchema" xmlns:meta ="http://www.arptek.ru/core/meta" targetNamespace ="http://www.arptek.ru/core/meta/test" > < xs:complexType name ="Test" > < xs:annotation > < xs:appinfo > < meta:java.interface > ru.arptek.meta.SomeInterface </ meta:java.interface > </ xs:appinfo > </ xs:annotation > < xs:sequence > < xs:element name ="id" type ="xs:int" meta:jpa . Id ="true" /> < xs:element name ="string" type ="xs:string" /> < xs:element name ="boolean" type ="xs:boolean" /> < xs:element name ="integer" type ="xs:int" /> < xs:element name ="long" type ="xs:long" /> </ xs:sequence > </ xs:complexType > </ xs:schema > * This source code was highlighted with Source Code Highlighter .
  18. <? xml version ="1.0" encoding ="utf-8" ? > < xs:schema xmlns:xs ="http://www.w3.org/2001/XMLSchema" xmlns:meta ="http://www.arptek.ru/core/meta" targetNamespace ="http://www.arptek.ru/core/meta/test" > < xs:complexType name ="Test" > < xs:annotation > < xs:appinfo > < meta:java.interface > ru.arptek.meta.SomeInterface </ meta:java.interface > </ xs:appinfo > </ xs:annotation > < xs:sequence > < xs:element name ="id" type ="xs:int" meta:jpa . Id ="true" /> < xs:element name ="string" type ="xs:string" /> < xs:element name ="boolean" type ="xs:boolean" /> < xs:element name ="integer" type ="xs:int" /> < xs:element name ="long" type ="xs:long" /> </ xs:sequence > </ xs:complexType > </ xs:schema > * This source code was highlighted with Source Code Highlighter .
<? xml version ="1.0" encoding ="utf-8" ? > < xs:schema xmlns:xs ="http://www.w3.org/2001/XMLSchema" xmlns:meta ="http://www.arptek.ru/core/meta" targetNamespace ="http://www.arptek.ru/core/meta/test" > < xs:complexType name ="Test" > < xs:annotation > < xs:appinfo > < meta:java.interface > ru.arptek.meta.SomeInterface </ meta:java.interface > </ xs:appinfo > </ xs:annotation > < xs:sequence > < xs:element name ="id" type ="xs:int" meta:jpa . Id ="true" /> < xs:element name ="string" type ="xs:string" /> < xs:element name ="boolean" type ="xs:boolean" /> < xs:element name ="integer" type ="xs:int" /> < xs:element name ="long" type ="xs:long" /> </ xs:sequence > </ xs:complexType > </ xs:schema > * This source code was highlighted with Source Code Highlighter .
<? xml version ="1.0" encoding ="utf-8" ? > < xs:schema xmlns:xs ="http://www.w3.org/2001/XMLSchema" xmlns:meta ="http://www.arptek.ru/core/meta" targetNamespace ="http://www.arptek.ru/core/meta/test" > < xs:complexType name ="Test" > < xs:annotation > < xs:appinfo > < meta:java.interface > ru.arptek.meta.SomeInterface </ meta:java.interface > </ xs:appinfo > </ xs:annotation > < xs:sequence > < xs:element name ="id" type ="xs:int" meta:jpa . Id ="true" /> < xs:element name ="string" type ="xs:string" /> < xs:element name ="boolean" type ="xs:boolean" /> < xs:element name ="integer" type ="xs:int" /> < xs:element name ="long" type ="xs:long" /> </ xs:sequence > </ xs:complexType > </ xs:schema > * This source code was highlighted with Source Code Highlighter .

By the way, pay attention to the use of additional attributes and elements in the namespace "http: // www.arptek.ru/core/meta". This is the only way - additional attributes should always be in a separate namespace, and elements should be placed in xs: annotation / xs: appinfo in general. The code that converts this scheme into a configuration can be seen here .

Bean Class Generation

The generation code performs three tasks:

Some notes before starting the code:

We need to know how to perform simple operations that correspond to a particular Java code in assembly language. For example, the command
  return this.a; 

should be converted to the following code:
     0 aload_0
     1 getfield classname.a
     4 lreturn

In this case, the specific opcodes depend on which types have arguments. For int / boolean / long, different opcodes are used. But the good thing is that CGLib and ASM hide from us these particular features of the Java machine implementation and control themselves which types have arguments and which opcodes to use. For example, if we define a variable:
  ce.declare_field (Constants.ACC_PROTECTED, fieldName, type, null); 

That CGLib / ASM will remember its type, and when we will generate code using the commands
     codeEmitter.load_this (); 
     codeEmitter.getfield (constantName); 
     codeEmitter.return_value (); 

The generated code will have exactly those operands that work with the type of variable we specified. So, the return value of the class field was higher. More precisely, this code can be divided into two parts. Putting a field value on the stack:
     codeEmitter.load_this (); 
     codeEmitter.getfield (constantName); 

and return the top value of the stack:
     codeEmitter.return_value (); 

Putting the first function argument to the stack (for further assignment to something):
     codeEmitter.load_arg (0);

BUT! The problem is that when performing the putfield operation, there must be a value at the top of the stack, and below - a reference to the object that owns the field. Therefore, we first get a reference to the object, then add the argument to the stack, and only then assign the value to the field:
          codeEmitter.load_this (); 
          codeEmitter.load_arg (0); 
          codeEmitter.putfield (fieldName); 
          codeEmitter.return_value (); 

Method call For this, the stack must contain a link to the object and arguments (arguments from above):
                 codeEmitter.invoke_virtual (parentClass, new Signature (
                         getterName, fieldValueType, TYPES_EMPTY));

In this case, invoke_static is used to call static methods, invoke_interface is used to call methods that are defined in the interface (parentClass interface), and invoke_special is used to call a constructor (where the method name is "<init>"). By the way, the creation of an object and the call of a constructor are different operations, and they may well be separated by something. For example, creating a LinkedHashMap:
         Local local = e.make_local (TYPE_LINKED_HASH_MAP);

         e.new_instance (TYPE_LINKED_HASH_MAP);
         e.dup ();
         e.push (fieldClasses.size ());
         e.push (10);
         e.push (1f);
         e.invoke_constructor (TYPE_LINKED_HASH_MAP, SIG_MAP_INIT_INT_FLOAT);
         e.store_local (local);

Prepare a local variable where we will store the map. After that we create a new object in the heap (we allocate memory to it). Next, we duplicate the top argument of the stack with the dup () operation - since after the constructor call, one of the links will go away, and we still need to save the value. We also push the constructor call arguments to the stack (strictly observing the type, autoconversion int-> float will not work here), and call the constructor using the invoke_constructor (it will be converted to invoke_special). In the last line - save the result in a local variable. After that, our stack is empty.
')
So, class generation:
  1. ClassWriter cw = new DebuggingClassWriter (
  2. ClassWriter.COMPUTE_FRAMES);
  3. ClassEmitter ce = new ClassEmitter (cw);
  4. Type [] interfaces = getInterfacesTypes (complexType);
  5. ce.begin_class (Constants.V1_6, Constants.ACC_PUBLIC, className,
  6. Type.getType (complexType.getSuperClass ()), interfaces,
  7. Constants.SOURCE_FILE);
  8. if (complexType.isJpaEntity ()) {
  9. ce.visitAnnotation (ANNOTATION_DESCRIPTOR_ENTITY, true )
  10. .visitEnd ();
  11. }
  12. if (StringUtils.isNotEmpty (complexType.getJpaTableName ())) {
  13. final AnnotationVisitor av = ce.visitAnnotation (
  14. ANNOTATION_DESCRIPTOR_TABLE, true );
  15. av.visit ( "name" , complexType.getJpaTableName ());
  16. av.visitEnd ();
  17. }
  18. {
  19. if (options.generateFieldAccessors) {
  20. final String fieldsMapFieldName = "$ fields $" ;
  21. ce.declare_field (Constants.ACC_PRIVATE
  22. | Constants.ACC_FINAL, fieldsMapFieldName,
  23. TYPE_MAP, null );
  24. generateConstructorWithFieldsInit (mainClass, ce,
  25. fieldClasses, fieldsMapFieldName);
  26. generateGetFieldsMethod (ce, fieldsMapFieldName);
  27. generateGetFieldMethod (ce, fieldsMapFieldName);
  28. } else {
  29. EmitUtils.null_constructor (ce);
  30. }
  31. for (Attribute attribute: complexType.getAttributes ()) {
  32. processAttribute (ce, attribute);
  33. }
  34. }
  35. ce.end_class ();
  36. {
  37. byte [] bs = cw.toByteArray ();
  38. log.info ( "Generated class '" + className + "' of" + bs.length
  39. + "bytes" );
  40. result.put (className, bs);
  41. }
* This source code was highlighted with Source Code Highlighter .

The first and last lines organize the ClassEmitter -> ClassWriter -> ByteArrayOutputStream -> bytes chain. When generating class code, we use ClassEmitter, for methods - CodeEmitter, and for annotations - there is no such thing and AnnotationVisitor is used (it is important not to forget to close it also via visitEnd ()).

To create a class, select the name, version, parent class, interfaces, etc., as if we are writing a regular class, but in a different language. It is necessary, however, not to forget that, unlike normal Java-code, such code can be compiled, but not earned - because you forgot something or did not do so. For example, you forgot to add a return code from the procedure ... the class will be generated, but it will not work. That is, errors in such code cannot be traced at the compilation stage and it is necessary to do test cases. Although those who are already ready to accept the assembler already know about it :)

Lines 11–21 add to the JPA class the annotation Entity and Table . An important note that could save me a lot of time - the visitAnnotation method accepts a string, but not the name of the annotation class, but its description in the internal Java format. For example, for javax.persistence.Entity this will be “Ljavax / persistence / Entity;”. But they can be easily obtained using the Type class (it is used almost in every line):
1 private static final String ANNOTATION_DESCRIPTOR_ENTITY = Type.getType (
2 Entity. class ) .getDescriptor ();

Line 36 is needed to add a simple constructor (another case a bit later). Well, in loop 39-41, we add new fields, get / set methods, and default constants for each attribute:
  1. if (attribute.getType () instanceof EmptyType)
  2. return ;
  3. String fieldName = attribute.getFieldName ();
  4. String getterName = attribute.getGetterName ();
  5. String setterName = attribute.getSetterName ();
  6. final String javaMethodNamePart = WordUtils.capitalize (
  7. attribute.getAttrName (), new char [] { '_' }). replace ( "_" , "" );
  8. final Type type = getTypeFromXmlSchemaTypeName (attribute.getType ());
  9. if (fieldName == null )
  10. fieldName = "m $" + StringUtils.uncapitalize (javaMethodNamePart);
  11. if (getterName == null )
  12. getterName = "get" + javaMethodNamePart;
  13. if (setterName == null )
  14. setterName = "set" + javaMethodNamePart;
  15. final boolean jpaId = attribute.isJpaId ();
  16. final String strDefaultValue;
  17. if (! jpaId) {
  18. strDefaultValue = attribute.getStrDefaultValue ();
  19. } else {
  20. strDefaultValue = null ;
  21. }
  22. if (strDefaultValue == null ) {
  23. ce.declare_field (Constants.ACC_PRIVATE, fieldName, type, null );
  24. {
  25. // create getter
  26. CodeEmitter codeEmitter = ce.begin_method (Constants.ACC_PUBLIC,
  27. new Signature (getterName, type, TYPES_EMPTY),
  28. TYPES_EMPTY);
  29. proceedAttributeJpaColumnName (attribute, codeEmitter);
  30. proceedAttributeJpaLob (attribute, codeEmitter);
  31. proceedAttributeJpaId (attribute, codeEmitter);
  32. codeEmitter.load_this ();
  33. codeEmitter.getfield (fieldName);
  34. codeEmitter.return_value ();
  35. codeEmitter.end_method ();
  36. }
  37. {
  38. // create null safe setter
  39. CodeEmitter codeEmitter = ce.begin_method (Constants.ACC_PUBLIC,
  40. new Signature (setterName, Type.VOID_TYPE,
  41. new Type [] {type}), TYPES_EMPTY);
  42. codeEmitter.load_this ();
  43. codeEmitter.load_arg (0);
  44. codeEmitter.putfield (fieldName);
  45. codeEmitter.return_value ();
  46. codeEmitter.end_method ();
  47. }
  48. } else {
  49. final Object defaultValue = toValue (type, strDefaultValue);
  50. final String constantName = "DEFAULT_" + fieldName;
  51. ce.declare_field (Constants.ACC_PROTECTED | Constants.ACC_FINAL
  52. | Constants.ACC_STATIC, constantName, type, null );
  53. {
  54. CodeEmitter staticHook = ce.getStaticHook ();
  55. EmitUtils.push_object (staticHook, defaultValue);
  56. staticHook.putfield (constantName);
  57. }
  58. ce.declare_field (Constants.ACC_PROTECTED, fieldName, type, null );
  59. {
  60. // create getter
  61. final Signature signature = new Signature (getterName, type,
  62. TYPES_EMPTY);
  63. final CodeEmitter codeEmitter = ce.begin_method (
  64. Constants.ACC_PUBLIC, signature, TYPES_EMPTY);
  65. proceedAttributeJpaColumnName (attribute, codeEmitter);
  66. proceedAttributeJpaLob (attribute, codeEmitter);
  67. codeEmitter.load_this ();
  68. codeEmitter.getfield (fieldName);
  69. Label ifNull = codeEmitter.make_label ();
  70. codeEmitter.ifnull (ifNull);
  71. {
  72. codeEmitter.load_this ();
  73. codeEmitter.getfield (fieldName);
  74. codeEmitter.return_value ();
  75. }
  76. codeEmitter.mark (ifNull);
  77. {
  78. codeEmitter.load_this ();
  79. codeEmitter.getfield (constantName);
  80. codeEmitter.return_value ();
  81. }
  82. codeEmitter.end_method ();
  83. }
  84. {
  85. // create setter
  86. final Signature signature = new Signature (setterName,
  87. Type.VOID_TYPE, new Type [] {type});
  88. final CodeEmitter codeEmitter = ce.begin_method (
  89. Constants.ACC_PUBLIC, signature, TYPES_EMPTY);
  90. codeEmitter.load_this ();
  91. codeEmitter.load_arg (0);
  92. codeEmitter.putfield (fieldName);
  93. codeEmitter.return_value ();
  94. codeEmitter.end_method ();
  95. }
  96. }
* This source code was highlighted with Source Code Highlighter .

Check 1-2 is needed in case the field is “empty”. Such fields allow you to include an attribute in a class that will not be stored in the same table, and, for example, will manage its own state. In lines 4-18, the variables are initialized and future names for internal variables and methods are defined. Note that for methods and variables that are generated by a computer, it is customary to use the "$" sign, whereas in custom code it is recommended to avoid it. Of course, get / set methods are left without this sign, since they will be called from Hibernate and business code.

For JPA ID, the default values ​​are not used, so just in case we check that it was not set, in lines 23-27. The code generation for the case when the default value is set and not is different. The case when it is not there is, of course, simpler and more straightforward - the field itself is added to the class in line 30, the get method in lines 32-46, set - 48-58.

The default case requires:


Access to a set of fields

The code above successfully (hopefully) generated a “bin” - a class with fields and get / set methods. But still it is necessary to combine two methods: getField (name) and getFields (). These methods should give access to the object's fields through the collection, but there should not be any reflection in order not to slow down the system by calling the native-methods.
To do this, for each object attribute, we will additionally generate a class that contains two methods — getValue () and setValue (), which delegate these calls to the main class. The generation of such a class is quite simple, but interesting because now the class constructor is not empty, but takes a single parameter - a reference to the parent object:
  1. final String holderFieldName = "$ parent $" ;
  2. ce.declare_field (Constants.ACC_PRIVATE | Constants.ACC_FINAL,
  3. holderFieldName, parentClass, null );
  4. {
  5. CodeEmitter e = ce.begin_method (Constants.ACC_PUBLIC,
  6. new Signature ( "<init>" , Type.VOID_TYPE,
  7. new Type [] {parentClass}), null );
  8. e.load_this ();
  9. e.super_invoke_constructor ();
  10. e.load_this ();
  11. e.load_arg (0);
  12. e.putfield (holderFieldName);
  13. e.return_value ();
  14. e.end_method ();
  15. }
* This source code was highlighted with Source Code Highlighter .


The link itself is stored in the $ parent $ variable. The get / set methods are made quite simple:
  1. {
  2. // create getter
  3. CodeEmitter codeEmitter = ce.begin_method (Constants.ACC_PUBLIC,
  4. new Signature ( "getValue" , fieldValueType, TYPES_EMPTY),
  5. TYPES_EMPTY);
  6. codeEmitter.load_this ();
  7. codeEmitter.getfield (holderFieldName);
  8. codeEmitter.invoke_virtual (parentClass, new Signature (
  9. getterName, fieldValueType, TYPES_EMPTY));
  10. codeEmitter.return_value ();
  11. codeEmitter.end_method ();
  12. }
  13. {
  14. // create null safe setter
  15. CodeEmitter codeEmitter = ce.begin_method (Constants.ACC_PUBLIC,
  16. new Signature ( "setValue" , Type.VOID_TYPE,
  17. new Type [] {fieldValueType}), TYPES_EMPTY);
  18. codeEmitter.load_this ();
  19. codeEmitter.getfield (holderFieldName);
  20. codeEmitter.load_arg (0);
  21. codeEmitter.invoke_virtual (parentClass, new Signature (
  22. setterName, Type.VOID_TYPE,
  23. new Type [] {fieldValueType}));
  24. codeEmitter.return_value ();
  25. codeEmitter.end_method ();
  26. }
* This source code was highlighted with Source Code Highlighter .


These classes for each attribute are generated in a loop and stored in Map <Attribute, String = class name>:
  1. ClassWriter cw = new DebuggingClassWriter (
  2. ClassWriter.COMPUTE_FRAMES);
  3. ClassEmitter ce = new ClassEmitter (cw);
  4. String fieldClassName = processAttributeField (className,
  5. mainClass, ce, attribute);
  6. fieldClasses.put (attribute, ce.getClassType ());
* This source code was highlighted with Source Code Highlighter .


What for? So that later in the constructor of the parent class, create instances of the child objects and place them in the internal field $ fields $:
  1. public void generateConstructorWithFieldsInit (final Type mainClass,
  2. ClassEmitter ce, Map <Attribute, Type> fieldClasses,
  3. final String fieldsMapFieldName) {
  4. CodeEmitter e = ce.begin_method (Constants.ACC_PUBLIC, TypeUtils
  5. .parseConstructor ( "" ), null );
  6. e.load_this ();
  7. e.super_invoke_constructor ();
  8. Local local = e.make_local (TYPE_LINKED_HASH_MAP);
  9. e.new_instance (TYPE_LINKED_HASH_MAP);
  10. e.dup ();
  11. e.push (fieldClasses.size ());
  12. e.push (1f);
  13. e.invoke_constructor (TYPE_LINKED_HASH_MAP, SIG_MAP_INIT_INT_FLOAT);
  14. e.store_local (local);
  15. for (Entry <Attribute, Type> fieldTypes: fieldClasses.entrySet ()) {
  16. e.load_local (local);
  17. e.push (fieldTypes.getKey (). getAttrName ());
  18. // = new <...> Field (this);
  19. e.new_instance (fieldTypes.getValue ());
  20. e.dup ();
  21. e.load_this ();
  22. e.invoke_constructor (fieldTypes.getValue (), new Signature ( "<init>" ,
  23. Type.VOID_TYPE, new Type [] {mainClass}));
  24. // $ fields $ .put (fieldName, field);
  25. e.invoke_virtual (TYPE_LINKED_HASH_MAP, SIG_MAP_PUT);
  26. }
  27. e.load_this ();
  28. e.load_local (local);
  29. e.invoke_static (TYPE_COLLECTIONS, SIG_COLLECTIONS_UNMODIFIABLEMAP);
  30. e.putfield (fieldsMapFieldName);
  31. e.return_value ();
  32. e.end_method ();
  33. }
* This source code was highlighted with Source Code Highlighter .


In the first lines, we create LinkedHashMap (with the parameterization using Generic, we hammer - it is almost not in the assembler). Then we fill it with child fields Fields, well, then, just in case, we wrap it in Collections.unmodifiableMap and save the final field. By the way, notice that the last method is called using invoke_static, and even the constructor is completed with the return_value operation, although it returns VOID in accordance with the specification (lines 4-5).

And finally, we will create the getField (name) and getFields () methods, not forgetting to provide them with Transient annotation, so that the JPA does not think that these are object fields:
  1. public void generateGetFieldMethod (ClassEmitter ce,
  2. final String fieldsMapFieldName) {
  3. CodeEmitter e = ce.begin_method (Constants.ACC_PUBLIC,
  4. SIG_FIELDSHOLDER_GETFIELD, null );
  5. e.visitAnnotation (ANNOTATION_DESCRIPTOR_TRANSIENT, true ) .visitEnd ();
  6. e.load_this ();
  7. e.getfield (fieldsMapFieldName);
  8. e.load_arg (0);
  9. e.checkcast (TYPE_OBJECT);
  10. e.invoke_interface (TYPE_MAP, SIG_MAP_GET);
  11. e.checkcast (TYPE_FIELD);
  12. e.return_value ();
  13. e.end_method ();
  14. }
  15. public void generateGetFieldsMethod (ClassEmitter ce,
  16. final String fieldsMapFieldName) {
  17. CodeEmitter e = ce.begin_method (Constants.ACC_PUBLIC,
  18. SIG_FIELDSHOLDER_GETFIELDS, null );
  19. e.visitAnnotation (ANNOTATION_DESCRIPTOR_TRANSIENT, true ) .visitEnd ();
  20. e.load_this ();
  21. e.getfield (fieldsMapFieldName);
  22. e.return_value ();
  23. e.end_method ();
  24. }
* This source code was highlighted with Source Code Highlighter .


Loading derived classes

After generating byte [] for each class you need to load them into memory. This is done by defining your ClassLoader (or you can save it to disk and load it from there using some URLClass Loader):
  1. protected Map <String, Class <? >> loadClasses (
  2. final Map <String, byte []> generated) {
  3. final Thread currentThread = Thread.currentThread ();
  4. final ClassLoader classLoader = currentThread.getContextClassLoader ();
  5. final Map <String, Class <? >> classes = new ClassLoader (classLoader) {
  6. protected Map <String, Class <? >> loadClasses () {
  7. Map <String, Class <? >> result = new LinkedHashMap <String, Class <? >> (
  8. generated.size ());
  9. for (Entry <String, byte []> entry: generated.entrySet ()) {
  10. final String className = entry.getKey ();
  11. final byte [] byteCode = entry.getValue ();
  12. log.info ( "Defining class" + className + "..." );
  13. final Class <?> cls = defineClass (className, byteCode, 0,
  14. byteCode.length);
  15. result.put (cls.getName (), cls);
  16. }
  17. return result;
  18. }
  19. } .loadClasses ();
  20. return classes;
  21. }
* This source code was highlighted with Source Code Highlighter .

By the way, you should not forget about this ClassLoader - you will need it later to load these classes into Hibernate, because it will not work if the current ClassLoader cannot load those classes that are passed to it as parameters.

Use in practice

Some links that may be interesting for further study:

As a result

Source: https://habr.com/ru/post/91239/


All Articles