1
1
package ai .timefold .solver .spring .boot .autoconfigure ;
2
2
3
- import java .lang .reflect .Array ;
4
- import java .lang .reflect .Field ;
5
- import java .time .Duration ;
6
3
import java .util .ArrayList ;
7
4
import java .util .HashMap ;
8
5
import java .util .HashSet ;
9
- import java .util .IdentityHashMap ;
10
6
import java .util .List ;
11
7
import java .util .Map ;
12
8
import java .util .Set ;
19
15
import ai .timefold .solver .core .config .solver .SolverManagerConfig ;
20
16
import ai .timefold .solver .spring .boot .autoconfigure .config .SolverManagerProperties ;
21
17
import ai .timefold .solver .spring .boot .autoconfigure .config .TimefoldProperties ;
18
+ import ai .timefold .solver .spring .boot .autoconfigure .util .PojoInliner ;
22
19
23
- import org .apache .commons .text .StringEscapeUtils ;
24
20
import org .springframework .aot .generate .DefaultMethodReference ;
25
21
import org .springframework .aot .generate .GeneratedClass ;
26
22
import org .springframework .aot .generate .GenerationContext ;
@@ -82,221 +78,6 @@ private void registerType(ReflectionHints reflectionHints, Class<?> type) {
82
78
// and surrounding it by double quotes.
83
79
// - $T: Format as a fully qualified type, which allows you to use
84
80
// classes without importing them.
85
-
86
- /**
87
- * Serializes a Pojo to code that uses its no-args constructor
88
- * and setters to create the object.
89
- *
90
- * @param pojo The object to be serialized.
91
- * @param initializerCode The code block builder of the initializer
92
- * @param complexPojoToIdentifier A map that stores objects already recorded
93
- * @return A string that can be used in a {@link CodeBlock.Builder} to access the object
94
- */
95
- public static String pojoToCode (Object pojo ,
96
- CodeBlock .Builder initializerCode ,
97
- Map <Object , String > complexPojoToIdentifier ) {
98
- // First, check for primitives
99
- if (pojo == null ) {
100
- return "null" ;
101
- }
102
- if (pojo instanceof Boolean value ) {
103
- return value .toString ();
104
- }
105
- if (pojo instanceof Byte value ) {
106
- return value .toString ();
107
- }
108
- if (pojo instanceof Character value ) {
109
- return "\\ u" + Integer .toHexString (value | 0x10000 ).substring (1 );
110
- }
111
- if (pojo instanceof Short value ) {
112
- return value .toString ();
113
- }
114
- if (pojo instanceof Integer value ) {
115
- return value .toString ();
116
- }
117
- if (pojo instanceof Long value ) {
118
- // Add long suffix to number string
119
- return value + "L" ;
120
- }
121
- if (pojo instanceof Float value ) {
122
- // Add float suffix to number string
123
- return value + "f" ;
124
- }
125
- if (pojo instanceof Double value ) {
126
- // Add double suffix to number string
127
- return value + "d" ;
128
- }
129
-
130
- // Check for builtin classes
131
- if (pojo instanceof String value ) {
132
- return "\" " + StringEscapeUtils .escapeJava (value ) + "\" " ;
133
- }
134
- if (pojo instanceof Class <?> value ) {
135
- return value .getName () + ".class" ;
136
- }
137
- if (pojo instanceof ClassLoader ) {
138
- // We don't support serializing ClassLoaders, so replace it
139
- // with the context class loader
140
- return "Thread.currentThread().getContextClassLoader()" ;
141
- }
142
- if (pojo instanceof Duration value ) {
143
- return Duration .class .getName () + ".ofNanos(" + value .toNanos () + "L)" ;
144
- }
145
- if (pojo .getClass ().isEnum ()) {
146
- // Use field access to read the enum
147
- Class <?> enumClass = pojo .getClass ();
148
- Enum <?> pojoEnum = (Enum <?>) pojo ;
149
- return enumClass .getName () + "." + pojoEnum .name ();
150
- }
151
- return complexPojoToCode (pojo , initializerCode , complexPojoToIdentifier );
152
- }
153
-
154
- /**
155
- * Return a string that can be used in a {@link CodeBlock.Builder} to access a complex object
156
- *
157
- * @param pojo The object to be accessed
158
- * @param complexPojoToIdentifier A Map from complex POJOs to their key in the map.
159
- * @return A string that can be used in a {@link CodeBlock.Builder} to access the object.
160
- */
161
- private static String getComplexPojo (Object pojo , Map <Object , String > complexPojoToIdentifier ) {
162
- return "((" + pojo .getClass ().getName () + ") " + COMPLEX_POJO_MAP_FIELD_NAME + ".get(\" "
163
- + complexPojoToIdentifier .get (pojo ) + "\" ))" ;
164
- }
165
-
166
- /**
167
- * Serializes collections and complex POJOs to code
168
- */
169
- private static String complexPojoToCode (Object pojo , CodeBlock .Builder initializerCode ,
170
- Map <Object , String > complexPojoToIdentifier ) {
171
- // If we already serialized the object, we should just return
172
- // the code string
173
- if (complexPojoToIdentifier .containsKey (pojo )) {
174
- return getComplexPojo (pojo , complexPojoToIdentifier );
175
- }
176
- // Object is not serialized yet
177
- // Create a new variable to store its value when setting its fields
178
- String newIdentifier = "$obj" + complexPojoToIdentifier .size ();
179
- complexPojoToIdentifier .put (pojo , newIdentifier );
180
- initializerCode .add ("\n $T $L;" , pojo .getClass (), newIdentifier );
181
-
182
- // First, check if it is a collection type
183
- if (pojo .getClass ().isArray ()) {
184
- return arrayToCode (newIdentifier , pojo , initializerCode , complexPojoToIdentifier );
185
- }
186
- if (pojo instanceof List <?> value ) {
187
- return listToCode (newIdentifier , value , initializerCode , complexPojoToIdentifier );
188
- }
189
- if (pojo instanceof Set <?> value ) {
190
- return setToCode (newIdentifier , value , initializerCode , complexPojoToIdentifier );
191
- }
192
- if (pojo instanceof Map <?, ?> value ) {
193
- return mapToCode (newIdentifier , value , initializerCode , complexPojoToIdentifier );
194
- }
195
-
196
- // Not a collection type, so serialize by creating a new instance and settings its fields
197
- initializerCode .add ("\n $L = new $T();" , newIdentifier , pojo .getClass ());
198
- initializerCode .add ("\n $L.put($S, $L);" , COMPLEX_POJO_MAP_FIELD_NAME , newIdentifier , newIdentifier );
199
- setComplexPojoFields (pojo .getClass (), newIdentifier , pojo , initializerCode , complexPojoToIdentifier );
200
- return getComplexPojo (pojo , complexPojoToIdentifier );
201
- }
202
-
203
- private static String arrayToCode (String newIdentifier , Object array , CodeBlock .Builder initializerCode ,
204
- Map <Object , String > complexPojoToIdentifier ) {
205
- // Get the length of the array
206
- int length = Array .getLength (array );
207
-
208
- // Create a new array from the component type with the given length
209
- initializerCode .add ("\n $L = new $T[$L];" , newIdentifier , array .getClass ().getComponentType (), Integer .toString (length ));
210
- initializerCode .add ("\n $L.put($S, $L);" , COMPLEX_POJO_MAP_FIELD_NAME , newIdentifier , newIdentifier );
211
- for (int i = 0 ; i < length ; i ++) {
212
- // Set the elements of the array
213
- initializerCode .add ("\n $L[$L] = $L;" ,
214
- newIdentifier ,
215
- Integer .toString (i ),
216
- pojoToCode (Array .get (array , i ), initializerCode , complexPojoToIdentifier ));
217
- }
218
- return getComplexPojo (array , complexPojoToIdentifier );
219
- }
220
-
221
- private static String listToCode (String newIdentifier , List <?> list , CodeBlock .Builder initializerCode ,
222
- Map <Object , String > complexPojoToIdentifier ) {
223
- // Create an ArrayList
224
- initializerCode .add ("\n $L = new $T($L);" , newIdentifier , ArrayList .class , Integer .toString (list .size ()));
225
- initializerCode .add ("\n $L.put($S, $L);" , COMPLEX_POJO_MAP_FIELD_NAME , newIdentifier , newIdentifier );
226
- for (Object item : list ) {
227
- // Add each item of the list to the ArrayList
228
- initializerCode .add ("\n $L.add($L);" ,
229
- newIdentifier ,
230
- pojoToCode (item , initializerCode , complexPojoToIdentifier ));
231
- }
232
- return getComplexPojo (list , complexPojoToIdentifier );
233
- }
234
-
235
- private static String setToCode (String newIdentifier , Set <?> set , CodeBlock .Builder initializerCode ,
236
- Map <Object , String > complexPojoToIdentifier ) {
237
- // Create a new HashSet
238
- initializerCode .add ("\n $L = new $T($L);" , newIdentifier , HashSet .class , Integer .toString (set .size ()));
239
- initializerCode .add ("\n $L.put($S, $L);" , COMPLEX_POJO_MAP_FIELD_NAME , newIdentifier , newIdentifier );
240
- for (Object item : set ) {
241
- // Add each item of the set to the HashSet
242
- initializerCode .add ("\n $L.add($L);" ,
243
- newIdentifier ,
244
- pojoToCode (item , initializerCode , complexPojoToIdentifier ));
245
- }
246
- return getComplexPojo (set , complexPojoToIdentifier );
247
- }
248
-
249
- private static String mapToCode (String newIdentifier , Map <?, ?> map , CodeBlock .Builder initializerCode ,
250
- Map <Object , String > complexPojoToIdentifier ) {
251
- // Create a HashMap
252
- initializerCode .add ("\n $L = new $T($L);" , newIdentifier , HashMap .class , Integer .toString (map .size ()));
253
- initializerCode .add ("\n $L.put($S, $L);" , COMPLEX_POJO_MAP_FIELD_NAME , newIdentifier , newIdentifier );
254
- for (Map .Entry <?, ?> entry : map .entrySet ()) {
255
- // Put each entry of the map into the HashMap
256
- initializerCode .add ("\n $L.put($L,$L);" ,
257
- newIdentifier ,
258
- pojoToCode (entry .getKey (), initializerCode , complexPojoToIdentifier ),
259
- pojoToCode (entry .getValue (), initializerCode , complexPojoToIdentifier ));
260
- }
261
- return getComplexPojo (map , complexPojoToIdentifier );
262
- }
263
-
264
- /**
265
- * Sets the fields of pojo declared in pojoClass and all its superclasses.
266
- *
267
- * @param pojoClass A class assignable to pojo containing some of its fields.
268
- * @param identifier The name of the variable storing the serialized pojo.
269
- * @param pojo The object being serialized.
270
- * @param initializerCode The {@link CodeBlock.Builder} to use to generate code in the initializer.
271
- * @param complexPojoToIdentifier A map from complex POJOs to their variable name.
272
- */
273
- private static void setComplexPojoFields (Class <?> pojoClass , String identifier , Object pojo ,
274
- CodeBlock .Builder initializerCode , Map <Object , String > complexPojoToIdentifier ) {
275
- if (pojoClass == Object .class ) {
276
- // We are the top-level, no more fields to set
277
- return ;
278
- }
279
- for (Field field : pojo .getClass ().getDeclaredFields ()) {
280
- if (java .lang .reflect .Modifier .isStatic (field .getModifiers ())) {
281
- // We do not want to write static fields
282
- continue ;
283
- }
284
- // Set the field accessible so we can read its value
285
- field .setAccessible (true );
286
- try {
287
- // Convert the field value to code, and call the setter
288
- // corresponding to the field with the serialized field value.
289
- initializerCode .add ("\n $L.set$L$L($L);" , identifier ,
290
- Character .toUpperCase (field .getName ().charAt (0 )),
291
- field .getName ().substring (1 ),
292
- pojoToCode (field .get (pojo ), initializerCode , complexPojoToIdentifier ));
293
- } catch (IllegalAccessException e ) {
294
- throw new RuntimeException (e );
295
- }
296
- }
297
- setComplexPojoFields (pojoClass .getSuperclass (), identifier , pojo , initializerCode , complexPojoToIdentifier );
298
- }
299
-
300
81
@ Override
301
82
public void applyTo (GenerationContext generationContext , BeanFactoryInitializationCode beanFactoryInitializationCode ) {
302
83
var reflectionHints = generationContext .getRuntimeHints ().reflection ();
@@ -319,15 +100,15 @@ public void applyTo(GenerationContext generationContext, BeanFactoryInitializati
319
100
// Create a generated class to hold all the solver configs
320
101
GeneratedClass generatedClass = generationContext .getGeneratedClasses ().addForFeature ("timefold-aot" ,
321
102
builder -> {
103
+ PojoInliner pojoInliner = new PojoInliner ();
322
104
builder .addField (Map .class , "solverConfigMap" , Modifier .STATIC );
323
105
builder .addField (Map .class , COMPLEX_POJO_MAP_FIELD_NAME , Modifier .STATIC );
324
106
325
107
// Handwrite the SolverConfig map in the initializer
326
108
CodeBlock .Builder staticInitializer = CodeBlock .builder ();
327
- Map <Object , String > complexPojoToIdentifier = new IdentityHashMap <>();
328
109
staticInitializer .add ("$L = new $T();" , COMPLEX_POJO_MAP_FIELD_NAME , HashMap .class );
329
110
staticInitializer .add ("\n solverConfigMap = $L;" ,
330
- complexPojoToCode (solverConfigMap , staticInitializer , complexPojoToIdentifier ));
111
+ pojoInliner . getInlinedPojo (solverConfigMap , staticInitializer ));
331
112
builder .addStaticBlock (staticInitializer .build ());
332
113
333
114
// getSolverConfig fetches the SolverConfig with the given name from the map
0 commit comments