View Javadoc
1   /*
2    * JavaBean Tester (https://github.com/hazendaz/javabean-tester)
3    *
4    * Copyright 2012-2025 Hazendaz.
5    *
6    * All rights reserved. This program and the accompanying materials
7    * are made available under the terms of The Apache Software License,
8    * Version 2.0 which accompanies this distribution, and is available at
9    * http://www.apache.org/licenses/LICENSE-2.0.txt
10   *
11   * Contributors:
12   *     CodeBox (Rob Dawson).
13   *     Hazendaz (Jeremy Landis).
14   */
15  package com.codebox.builders;
16  
17  import java.util.HashMap;
18  import java.util.Map;
19  import java.util.Map.Entry;
20  import javassist.CannotCompileException;
21  import javassist.ClassPool;
22  import javassist.CtClass;
23  import javassist.CtField;
24  import javassist.CtMethod;
25  import javassist.NotFoundException;
26  
27  import org.slf4j.Logger;
28  import org.slf4j.LoggerFactory;
29  
30  /**
31   * The Class ExtensionBuilder.
32   *
33   * @param <T>
34   *            the generic type
35   */
36  public class ExtensionBuilder<T> {
37  
38      /** The Constant LOGGER. */
39      private static final Logger LOGGER = LoggerFactory.getLogger(ExtensionBuilder.class);
40  
41      /**
42       * Generate.
43       *
44       * @param clazz
45       *            the clazz
46       *
47       * @return the class
48       *
49       * @throws NotFoundException
50       *             the not found exception
51       * @throws CannotCompileException
52       *             the cannot compile exception
53       */
54      public Class<?> generate(final Class<T> clazz) throws NotFoundException, CannotCompileException {
55          try {
56              // If extension already recreated, return it
57              return Class.forName(clazz.getName() + "Extension");
58          } catch (final ClassNotFoundException e) {
59              // No extension exists, so create it
60              ExtensionBuilder.LOGGER.trace("No extension exists, so create it", e);
61          }
62  
63          final ClassPool pool = ClassPool.getDefault();
64          final CtClass cc = pool.makeClass(clazz.getName() + "Extension");
65  
66          // add super class
67          cc.setSuperclass(ExtensionBuilder.resolveCtClass(clazz));
68  
69          final Map<String, Class<?>> properties = new HashMap<>();
70          properties.put("jbExtension1", String.class);
71          properties.put("jbExtension2", String.class);
72          properties.put("jbExtension3", String.class);
73          properties.put("jbExtension4", String.class);
74  
75          for (final Entry<String, Class<?>> entry : properties.entrySet()) {
76  
77              // Add field
78              cc.addField(new CtField(ExtensionBuilder.resolveCtClass(entry.getValue()), entry.getKey(), cc));
79  
80              // Add getter
81              cc.addMethod(ExtensionBuilder.generateGetter(cc, entry.getKey(), entry.getValue()));
82  
83              // Add setter
84              cc.addMethod(ExtensionBuilder.generateSetter(cc, entry.getKey(), entry.getValue()));
85          }
86  
87          return cc.toClass();
88      }
89  
90      /**
91       * Generate getter.
92       *
93       * @param declaringClass
94       *            the declaring class
95       * @param fieldName
96       *            the field name
97       * @param fieldClass
98       *            the field class
99       *
100      * @return the ct method
101      *
102      * @throws CannotCompileException
103      *             the cannot compile exception
104      */
105     private static CtMethod generateGetter(final CtClass declaringClass, final String fieldName,
106             final Class<?> fieldClass) throws CannotCompileException {
107         String methodSrc = """
108                 public %s get%s() {
109                     return this.%s;
110                 }
111                 """.formatted(fieldClass.getName(), fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1),
112                 fieldName);
113         return CtMethod.make(methodSrc, declaringClass);
114     }
115 
116     /**
117      * Generate setter.
118      *
119      * @param declaringClass
120      *            the declaring class
121      * @param fieldName
122      *            the field name
123      * @param fieldClass
124      *            the field class
125      *
126      * @return the ct method
127      *
128      * @throws CannotCompileException
129      *             the cannot compile exception
130      */
131     private static CtMethod generateSetter(final CtClass declaringClass, final String fieldName,
132             final Class<?> fieldClass) throws CannotCompileException {
133         String methodSrc = """
134                 public void set%s(%s %s) {
135                     this.%s = %s;
136                 }
137                 """.formatted(fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1), fieldClass.getName(),
138                 fieldName, fieldName, fieldName);
139         return CtMethod.make(methodSrc, declaringClass);
140     }
141 
142     /**
143      * Resolve ct class.
144      *
145      * @param clazz
146      *            the clazz
147      *
148      * @return the ct class
149      *
150      * @throws NotFoundException
151      *             the not found exception
152      */
153     private static CtClass resolveCtClass(final Class<?> clazz) throws NotFoundException {
154         return ClassPool.getDefault().get(clazz.getName());
155     }
156 
157 }