View Javadoc
1   /*
2    *    Copyright 2006-2026 the original author or authors.
3    *
4    *    Licensed under the Apache License, Version 2.0 (the "License");
5    *    you may not use this file except in compliance with the License.
6    *    You may obtain a copy of the License at
7    *
8    *       https://www.apache.org/licenses/LICENSE-2.0
9    *
10   *    Unless required by applicable law or agreed to in writing, software
11   *    distributed under the License is distributed on an "AS IS" BASIS,
12   *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *    See the License for the specific language governing permissions and
14   *    limitations under the License.
15   */
16  package org.mybatis.generator.api;
17  
18  import static org.mybatis.generator.internal.util.StringUtility.ifStringHasValueElse;
19  import static org.mybatis.generator.internal.util.StringUtility.isTrue;
20  import static org.mybatis.generator.internal.util.StringUtility.mapStringValueOrElseGet;
21  import static org.mybatis.generator.internal.util.StringUtility.stringValueOrElseGet;
22  import static org.mybatis.generator.internal.util.messages.Messages.getString;
23  
24  import java.util.HashMap;
25  import java.util.Map;
26  import java.util.Objects;
27  import java.util.Optional;
28  import java.util.Properties;
29  
30  import org.jspecify.annotations.Nullable;
31  import org.mybatis.generator.config.ClientGeneratorConfiguration;
32  import org.mybatis.generator.config.Context;
33  import org.mybatis.generator.config.GeneratedKey;
34  import org.mybatis.generator.config.ModelGeneratorConfiguration;
35  import org.mybatis.generator.config.ModelType;
36  import org.mybatis.generator.config.PropertyHolder;
37  import org.mybatis.generator.config.PropertyRegistry;
38  import org.mybatis.generator.config.SqlMapGeneratorConfiguration;
39  import org.mybatis.generator.config.TableConfiguration;
40  import org.mybatis.generator.exception.InternalException;
41  import org.mybatis.generator.internal.rules.Rules;
42  
43  /**
44   * This class holds calculated attributes for all code generator implementations. The class
45   * is intended to be a super class of IntrospectedTable and is matched one-to-one with that
46   * class (for purposes of preserving compatibility with the existing plugin methods). It is a separate class
47   * because there are different responsibilities between IntrospectedTable and this class.
48   *
49   * @author Jeff Butler
50   */
51  public abstract class CodeGenerationAttributes {
52      protected final TableConfiguration tableConfiguration;
53      protected final FullyQualifiedTable fullyQualifiedTable;
54      protected final Context context;
55      protected final KnownRuntime knownRuntime;
56  
57      /**
58       * Plugins may use attributes to capture table-related state between
59       * the different plugin calls.
60       */
61      protected final Map<String, Object> attributes = new HashMap<>();
62  
63      // these attributes are not final because we allow plugins to alter their values
64      private String aliasedFullyQualifiedRuntimeTableName;
65      private String baseColumnListId;
66      private String baseRecordType;
67      private String baseResultMapId;
68      private String blobColumnListId;
69      private String countByExampleStatementId;
70      private String deleteByExampleStatementId;
71      private String deleteByPrimaryKeyStatementId;
72      private String exampleType;
73      private String exampleWhereClauseId;
74      private String fullyQualifiedTableNameAtRuntime;
75      private String insertSelectiveStatementId;
76      private String insertStatementId;
77      private String kotlinRecordType;
78      /** also used as XML Mapper namespace if a Java mapper is generated. */
79      private @Nullable String myBatis3JavaMapperType;
80      private @Nullable String myBatis3SqlMapNamespace;
81      private @Nullable String myBatis3SqlProviderType;
82      private String myBatis3UpdateByExampleWhereClauseId;
83      private String myBatis3XmlMapperFileName;
84      private @Nullable String myBatis3XmlMapperPackage;
85      private String myBatisDynamicSQLTableObjectName;
86      private @Nullable String myBatisDynamicSqlSupportType;
87      private String primaryKeyType;
88      private String recordWithBLOBsType;
89      private String resultMapWithBLOBsId;
90      private Rules rules;
91      private String selectAllStatementId;
92      private String selectByExampleStatementId;
93      private String selectByExampleWithBLOBsStatementId;
94      private String selectByPrimaryKeyStatementId;
95      private String updateByExampleStatementId;
96      private String updateByExampleSelectiveStatementId;
97      private String updateByExampleWithBLOBsStatementId;
98      private String updateByPrimaryKeySelectiveStatementId;
99      private String updateByPrimaryKeyStatementId;
100     private String updateByPrimaryKeyWithBLOBsStatementId;
101 
102     protected abstract Rules calculateRules();
103 
104     protected CodeGenerationAttributes(AbstractBuilder<?> builder) {
105         this.knownRuntime = Objects.requireNonNull(builder.knownRuntime);
106         this.tableConfiguration = Objects.requireNonNull(builder.tableConfiguration);
107         this.fullyQualifiedTable = Objects.requireNonNull(builder.fullyQualifiedTable);
108         this.context = Objects.requireNonNull(builder.context);
109 
110         String modelPackage = calculateModelPackage();
111 
112         aliasedFullyQualifiedRuntimeTableName = fullyQualifiedTable.getAliasedFullyQualifiedTableNameAtRuntime();
113         baseColumnListId = "Base_Column_List"; //$NON-NLS-1$
114         baseRecordType = calculateBaseRecordType(modelPackage);
115         baseResultMapId = "BaseResultMap"; //$NON-NLS-1$
116         blobColumnListId = "Blob_Column_List"; //$NON-NLS-1$
117         countByExampleStatementId = "countByExample"; //$NON-NLS-1$
118         deleteByExampleStatementId = "deleteByExample"; //$NON-NLS-1$
119         deleteByPrimaryKeyStatementId = "deleteByPrimaryKey"; //$NON-NLS-1$
120         exampleType = calculateExampleType(modelPackage);
121         exampleWhereClauseId = "Example_Where_Clause"; //$NON-NLS-1$
122         fullyQualifiedTableNameAtRuntime = fullyQualifiedTable.getFullyQualifiedTableNameAtRuntime();
123         insertSelectiveStatementId = "insertSelective"; //$NON-NLS-1$
124         insertStatementId = "insert"; //$NON-NLS-1$
125         kotlinRecordType = calculateKotlinRecordType(modelPackage);
126         myBatis3UpdateByExampleWhereClauseId = "Update_By_Example_Where_Clause"; //$NON-NLS-1$
127         myBatis3XmlMapperFileName = calculateMyBatis3XmlMapperFileName();
128         myBatisDynamicSQLTableObjectName = calculateMyBatisDynamicSQLTableObjectName();
129         primaryKeyType = calculatePrimaryKeyType(modelPackage);
130         recordWithBLOBsType = calculateRecordWithBLOBsType(modelPackage);
131         resultMapWithBLOBsId = "ResultMapWithBLOBs"; //$NON-NLS-1$
132         rules = calculateRules();
133         selectAllStatementId = "selectAll"; //$NON-NLS-1$
134         selectByExampleStatementId = "selectByExample"; //$NON-NLS-1$
135         selectByExampleWithBLOBsStatementId = "selectByExampleWithBLOBs"; //$NON-NLS-1$
136         selectByPrimaryKeyStatementId = "selectByPrimaryKey"; //$NON-NLS-1$
137         updateByExampleStatementId = "updateByExample"; //$NON-NLS-1$
138         updateByExampleSelectiveStatementId = "updateByExampleSelective"; //$NON-NLS-1$
139         updateByExampleWithBLOBsStatementId = "updateByExampleWithBLOBs"; //$NON-NLS-1$
140         updateByPrimaryKeySelectiveStatementId = "updateByPrimaryKeySelective"; //$NON-NLS-1$
141         updateByPrimaryKeyStatementId = "updateByPrimaryKey"; //$NON-NLS-1$
142         updateByPrimaryKeyWithBLOBsStatementId = "updateByPrimaryKeyWithBLOBs"; //$NON-NLS-1$
143 
144         context.getSqlMapGeneratorConfiguration().ifPresent(config ->
145                 myBatis3XmlMapperPackage = calculateSqlMapPackage(config));
146 
147         context.getClientGeneratorConfiguration().ifPresent(config -> {
148             myBatis3JavaMapperType = calculateMyBatis3JavaMapperType(config);
149             myBatis3SqlProviderType = calculateMyBatis3SqlProviderType(config);
150             myBatisDynamicSqlSupportType = calculateMyBatisDynamicSqlSupportType(config);
151         });
152 
153         myBatis3SqlMapNamespace = calculateMyBatis3SqlMapNamespace();
154     }
155 
156     public FullyQualifiedTable getFullyQualifiedTable() {
157         return fullyQualifiedTable;
158     }
159 
160     public Optional<GeneratedKey> getGeneratedKey() {
161         return getTableConfiguration().getGeneratedKey();
162     }
163 
164     public @Nullable String getTableConfigurationProperty(String property) {
165         return getTableConfiguration().getProperty(property);
166     }
167 
168     public @Nullable Object getAttribute(String name) {
169         return attributes.get(name);
170     }
171 
172     public Object removeAttribute(String name) {
173         return attributes.remove(name);
174     }
175 
176     public void setAttribute(String name, Object value) {
177         attributes.put(name, value);
178     }
179 
180     public void setAliasedFullyQualifiedRuntimeTableName(String aliasedFullyQualifiedRuntimeTableName) {
181         this.aliasedFullyQualifiedRuntimeTableName = aliasedFullyQualifiedRuntimeTableName;
182     }
183 
184     public String getAliasedFullyQualifiedRuntimeTableName() {
185         return aliasedFullyQualifiedRuntimeTableName;
186     }
187 
188     public void setBaseColumnListId(String baseColumnListId) {
189         this.baseColumnListId = baseColumnListId;
190     }
191 
192     public String getBaseColumnListId() {
193         return baseColumnListId;
194     }
195 
196     public void setBaseRecordType(String baseRecordType) {
197         this.baseRecordType = baseRecordType;
198     }
199 
200     /**
201      * Gets the base record type.
202      *
203      * @return the type for the record (the class that holds non-primary key and non-BLOB fields). Note that the value
204      *         will be calculated regardless of whether the table has these columns or not.
205      */
206     public String getBaseRecordType() {
207         return baseRecordType;
208     }
209 
210     public void setBaseResultMapId(String baseResultMapId) {
211         this.baseResultMapId = baseResultMapId;
212     }
213 
214     public String getBaseResultMapId() {
215         return baseResultMapId;
216     }
217 
218     public void setBlobColumnListId(String blobColumnListId) {
219         this.blobColumnListId = blobColumnListId;
220     }
221 
222     public String getBlobColumnListId() {
223         return blobColumnListId;
224     }
225 
226     public void setCountByExampleStatementId(String countByExampleStatementId) {
227         this.countByExampleStatementId = countByExampleStatementId;
228     }
229 
230     public String getCountByExampleStatementId() {
231         return countByExampleStatementId;
232     }
233 
234     public void setDeleteByExampleStatementId(String deleteByExampleStatementId) {
235         this.deleteByExampleStatementId = deleteByExampleStatementId;
236     }
237 
238     public String getDeleteByExampleStatementId() {
239         return deleteByExampleStatementId;
240     }
241 
242     public void setDeleteByPrimaryKeyStatementId(String deleteByPrimaryKeyStatementId) {
243         this.deleteByPrimaryKeyStatementId = deleteByPrimaryKeyStatementId;
244     }
245 
246     public String getDeleteByPrimaryKeyStatementId() {
247         return deleteByPrimaryKeyStatementId;
248     }
249 
250     public void setExampleType(String exampleType) {
251         this.exampleType = exampleType;
252     }
253 
254     public String getExampleType() {
255         return exampleType;
256     }
257 
258     public void setExampleWhereClauseId(String exampleWhereClauseId) {
259         this.exampleWhereClauseId = exampleWhereClauseId;
260     }
261 
262     public String getExampleWhereClauseId() {
263         return exampleWhereClauseId;
264     }
265 
266     public void setFullyQualifiedTableNameAtRuntime(String fullyQualifiedTableNameAtRuntime) {
267         this.fullyQualifiedTableNameAtRuntime = fullyQualifiedTableNameAtRuntime;
268     }
269 
270     public String getFullyQualifiedTableNameAtRuntime() {
271         return fullyQualifiedTableNameAtRuntime;
272     }
273 
274     public void setInsertSelectiveStatementId(String insertSelectiveStatementId) {
275         this.insertSelectiveStatementId = insertSelectiveStatementId;
276     }
277 
278     public String getInsertSelectiveStatementId() {
279         return insertSelectiveStatementId;
280     }
281 
282     public void setInsertStatementId(String insertStatementId) {
283         this.insertStatementId = insertStatementId;
284     }
285 
286     public String getInsertStatementId() {
287         return insertStatementId;
288     }
289 
290     public void setKotlinRecordType(String kotlinRecordType) {
291         this.kotlinRecordType = kotlinRecordType;
292     }
293 
294     public String getKotlinRecordType() {
295         return kotlinRecordType;
296     }
297 
298     public void setMyBatis3JavaMapperType(String myBatis3JavaMapperType) {
299         this.myBatis3JavaMapperType = myBatis3JavaMapperType;
300     }
301 
302     public String getMyBatis3JavaMapperType() {
303         return requireNonNullElseInternalError(myBatis3JavaMapperType,
304                 getString("RuntimeError.25", context.getId())); //$NON-NLS-1$
305     }
306 
307     public void setMyBatis3SqlMapNamespace(String myBatis3SqlMapNamespace) {
308         this.myBatis3SqlMapNamespace = myBatis3SqlMapNamespace;
309 
310     }
311 
312     public String getMyBatis3SqlMapNamespace() {
313         return requireNonNullElseInternalError(myBatis3SqlMapNamespace,
314                 getString("RuntimeError.24", context.getId())); //$NON-NLS-1$
315     }
316 
317     public void setMyBatis3SqlProviderType(String myBatis3SqlProviderType) {
318         this.myBatis3SqlProviderType = myBatis3SqlProviderType;
319     }
320 
321     public String getMyBatis3SqlProviderType() {
322         return requireNonNullElseInternalError(myBatis3SqlProviderType,
323                 getString("RuntimeError.27", context.getId())); //$NON-NLS-1$
324     }
325 
326     public void setMyBatis3UpdateByExampleWhereClauseId(String myBatis3UpdateByExampleWhereClauseId) {
327         this.myBatis3UpdateByExampleWhereClauseId = myBatis3UpdateByExampleWhereClauseId;
328     }
329 
330     public String getMyBatis3UpdateByExampleWhereClauseId() {
331         return myBatis3UpdateByExampleWhereClauseId;
332     }
333 
334     public void setMyBatis3XmlMapperFileName(String myBatis3XmlMapperFileName) {
335         this.myBatis3XmlMapperFileName = myBatis3XmlMapperFileName;
336     }
337 
338     public String getMyBatis3XmlMapperFileName() {
339         return myBatis3XmlMapperFileName;
340     }
341 
342     public void setMyBatis3XmlMapperPackage(String myBatis3XmlMapperPackage) {
343         this.myBatis3XmlMapperPackage = myBatis3XmlMapperPackage;
344     }
345 
346     public String getMyBatis3XmlMapperPackage() {
347         return requireNonNullElseInternalError(myBatis3XmlMapperPackage,
348                 getString("RuntimeError.24", context.getId())); //$NON-NLS-1$
349 
350     }
351 
352     public void setMyBatisDynamicSqlSupportType(String myBatisDynamicSqlSupportType) {
353         this.myBatisDynamicSqlSupportType = myBatisDynamicSqlSupportType;
354     }
355 
356     public String getMyBatisDynamicSqlSupportType() {
357         return requireNonNullElseInternalError(myBatisDynamicSqlSupportType,
358                 getString("RuntimeError.26", context.getId())); //$NON-NLS-1$
359     }
360 
361     public void setMyBatisDynamicSQLTableObjectName(String myBatisDynamicSQLTableObjectName) {
362         this.myBatisDynamicSQLTableObjectName = myBatisDynamicSQLTableObjectName;
363     }
364 
365     public String getMyBatisDynamicSQLTableObjectName() {
366         return myBatisDynamicSQLTableObjectName;
367     }
368 
369     public void setPrimaryKeyType(String primaryKeyType) {
370         this.primaryKeyType = primaryKeyType;
371     }
372 
373     public String getPrimaryKeyType() {
374         return primaryKeyType;
375     }
376 
377     public void setRecordWithBLOBsType(String recordWithBLOBsType) {
378         this.recordWithBLOBsType = recordWithBLOBsType;
379     }
380 
381     public String getRecordWithBLOBsType() {
382         return recordWithBLOBsType;
383     }
384 
385     public void setResultMapWithBLOBsId(String resultMapWithBLOBsId) {
386         this.resultMapWithBLOBsId = resultMapWithBLOBsId;
387     }
388 
389     public String getResultMapWithBLOBsId() {
390         return resultMapWithBLOBsId;
391     }
392 
393     /**
394      * This method exists to give plugins the opportunity to replace the calculated rules if necessary.
395      *
396      * @param rules
397      *            the new rules
398      */
399     public void setRules(Rules rules) {
400         this.rules = rules;
401     }
402 
403     public Rules getRules() {
404         return Objects.requireNonNull(rules);
405     }
406 
407     public void setSelectAllStatementId(String selectAllStatementId) {
408         this.selectAllStatementId = selectAllStatementId;
409     }
410 
411     public String getSelectAllStatementId() {
412         return selectAllStatementId;
413     }
414 
415     public void setSelectByExampleStatementId(String selectByExampleStatementId) {
416         this.selectByExampleStatementId = selectByExampleStatementId;
417     }
418 
419     public String getSelectByExampleStatementId() {
420         return selectByExampleStatementId;
421     }
422 
423     public void setSelectByExampleWithBLOBsStatementId(String selectByExampleWithBLOBsStatementId) {
424         this.selectByExampleWithBLOBsStatementId = selectByExampleWithBLOBsStatementId;
425     }
426 
427     public String getSelectByExampleWithBLOBsStatementId() {
428         return selectByExampleWithBLOBsStatementId;
429     }
430 
431     public void setSelectByPrimaryKeyStatementId(String selectByPrimaryKeyStatementId) {
432         this.selectByPrimaryKeyStatementId = selectByPrimaryKeyStatementId;
433     }
434 
435     public String getSelectByPrimaryKeyStatementId() {
436         return selectByPrimaryKeyStatementId;
437     }
438 
439     public void setUpdateByExampleStatementId(String updateByExampleStatementId) {
440         this.updateByExampleStatementId = updateByExampleStatementId;
441     }
442 
443     public String getUpdateByExampleStatementId() {
444         return updateByExampleStatementId;
445     }
446 
447     public void setUpdateByExampleSelectiveStatementId(String updateByExampleSelectiveStatementId) {
448         this.updateByExampleSelectiveStatementId = updateByExampleSelectiveStatementId;
449     }
450 
451     public String getUpdateByExampleSelectiveStatementId() {
452         return updateByExampleSelectiveStatementId;
453     }
454 
455     public void setUpdateByExampleWithBLOBsStatementId(String updateByExampleWithBLOBsStatementId) {
456         this.updateByExampleWithBLOBsStatementId = updateByExampleWithBLOBsStatementId;
457     }
458 
459     public String getUpdateByExampleWithBLOBsStatementId() {
460         return updateByExampleWithBLOBsStatementId;
461     }
462 
463     public void setUpdateByPrimaryKeySelectiveStatementId(String updateByPrimaryKeySelectiveStatementId) {
464         this. updateByPrimaryKeySelectiveStatementId = updateByPrimaryKeySelectiveStatementId;
465     }
466 
467     public String getUpdateByPrimaryKeySelectiveStatementId() {
468         return updateByPrimaryKeySelectiveStatementId;
469     }
470 
471     public void setUpdateByPrimaryKeyStatementId(String updateByPrimaryKeyStatementId) {
472         this.updateByPrimaryKeyStatementId = updateByPrimaryKeyStatementId;
473     }
474 
475     public String getUpdateByPrimaryKeyStatementId() {
476         return updateByPrimaryKeyStatementId;
477     }
478 
479     public void setUpdateByPrimaryKeyWithBLOBsStatementId(String updateByPrimaryKeyWithBLOBsStatementId) {
480         this.updateByPrimaryKeyWithBLOBsStatementId = updateByPrimaryKeyWithBLOBsStatementId;
481     }
482 
483     public String getUpdateByPrimaryKeyWithBLOBsStatementId() {
484         return updateByPrimaryKeyWithBLOBsStatementId;
485     }
486 
487 
488     private boolean isSubPackagesEnabled(PropertyHolder propertyHolder) {
489         return isTrue(propertyHolder.getProperty(PropertyRegistry.ANY_ENABLE_SUB_PACKAGES));
490     }
491 
492     protected String calculateClientInterfacePackage(ClientGeneratorConfiguration config) {
493         return config.getTargetPackage()
494                 + getFullyQualifiedTable().getSubPackageForClientOrSqlMap(isSubPackagesEnabled(config));
495     }
496 
497     protected String calculateDynamicSqlSupportPackage(ClientGeneratorConfiguration c) {
498         return mapStringValueOrElseGet(c.getProperty(PropertyRegistry.CLIENT_DYNAMIC_SQL_SUPPORT_PACKAGE),
499                 s -> s + getFullyQualifiedTable().getSubPackageForClientOrSqlMap(isSubPackagesEnabled(c)),
500                 () -> calculateClientInterfacePackage(c));
501     }
502 
503     private String calculateMyBatisDynamicSQLTableObjectName() {
504         return stringValueOrElseGet(getTableConfiguration().getDynamicSqlTableObjectName(),
505                 () -> getFullyQualifiedTable().getDomainObjectName());
506     }
507 
508     private String calculateMyBatis3JavaMapperType(ClientGeneratorConfiguration config) {
509         StringBuilder sb = new StringBuilder();
510         sb.append(calculateClientInterfacePackage(config));
511         sb.append('.');
512         ifStringHasValueElse(getTableConfiguration().getMapperName(), sb::append, () -> {
513             getFullyQualifiedTable().getDomainObjectSubPackage().ifPresent(sp -> sb.append(sp).append('.'));
514             sb.append(getFullyQualifiedTable().getDomainObjectName());
515             sb.append("Mapper"); //$NON-NLS-1$
516         });
517         return sb.toString();
518     }
519 
520     private String calculateMyBatis3SqlProviderType(ClientGeneratorConfiguration config) {
521         StringBuilder sb = new StringBuilder();
522         sb.append(calculateClientInterfacePackage(config));
523         sb.append('.');
524         ifStringHasValueElse(getTableConfiguration().getSqlProviderName(), sb::append, () -> {
525             getFullyQualifiedTable().getDomainObjectSubPackage().ifPresent(sp -> sb.append(sp).append('.'));
526             sb.append(getFullyQualifiedTable().getDomainObjectName());
527             sb.append("SqlProvider"); //$NON-NLS-1$
528         });
529         return sb.toString();
530     }
531 
532     private String calculateMyBatisDynamicSqlSupportType(ClientGeneratorConfiguration config) {
533         StringBuilder sb = new StringBuilder();
534         sb.append(calculateDynamicSqlSupportPackage(config));
535         sb.append('.');
536         ifStringHasValueElse(getTableConfiguration().getDynamicSqlSupportClassName(), sb::append, () -> {
537             getFullyQualifiedTable().getDomainObjectSubPackage().ifPresent(sp -> sb.append(sp).append('.'));
538             sb.append(getFullyQualifiedTable().getDomainObjectName());
539             sb.append("DynamicSqlSupport"); //$NON-NLS-1$
540         });
541         return sb.toString();
542     }
543 
544     private String calculateModelPackage() {
545         ModelGeneratorConfiguration config = context.getModelGeneratorConfiguration();
546 
547         return config.getTargetPackage() + getFullyQualifiedTable().getSubPackageForModel(isSubPackagesEnabled(config));
548     }
549 
550     private String calculatePrimaryKeyType(String modelPackage) {
551         return modelPackage + '.' + getFullyQualifiedTable().getDomainObjectName() + "Key"; //$NON-NLS-1$
552     }
553 
554     private String calculateBaseRecordType(String modelPackage) {
555         return modelPackage + '.' + getFullyQualifiedTable().getDomainObjectName();
556     }
557 
558     private String calculateKotlinRecordType(String modelPackage) {
559         return modelPackage + '.' + getFullyQualifiedTable().getDomainObjectName();
560     }
561 
562     private String calculateRecordWithBLOBsType(String modelPackage) {
563         return modelPackage + '.' + getFullyQualifiedTable().getDomainObjectName() + "WithBLOBs"; //$NON-NLS-1$
564     }
565 
566     private String calculateExampleType(String modelPackage) {
567         return calculateModelExamplePackage(modelPackage) + '.'
568                 + getFullyQualifiedTable().getDomainObjectName() + "Example"; //$NON-NLS-1$
569     }
570 
571     /**
572      * If property exampleTargetPackage specified, for example, use the specified value, else
573      * use the default value (targetPackage).
574      *
575      * @return the calculated package
576      */
577     protected String calculateModelExamplePackage(String modelPackage) {
578         ModelGeneratorConfiguration config = context.getModelGeneratorConfiguration();
579         return mapStringValueOrElseGet(config.getProperty(PropertyRegistry.MODEL_GENERATOR_EXAMPLE_PACKAGE),
580                 s -> s + getFullyQualifiedTable().getSubPackageForModel(isSubPackagesEnabled(config)),
581                 () -> modelPackage);
582     }
583 
584     private String calculateSqlMapPackage(SqlMapGeneratorConfiguration config) {
585         StringBuilder sb = new StringBuilder();
586         sb.append(config.getTargetPackage());
587         sb.append(getFullyQualifiedTable().getSubPackageForClientOrSqlMap(isSubPackagesEnabled(config)));
588         ifStringHasValueElse(getTableConfiguration().getMapperName(), mapperName -> {
589             int ind = mapperName.lastIndexOf('.');
590             if (ind != -1) {
591                 sb.append('.').append(mapperName, 0, ind);
592             }
593         }, () -> getFullyQualifiedTable().getDomainObjectSubPackage().ifPresent(sp -> sb.append('.').append(sp)));
594         return sb.toString();
595     }
596 
597     protected String calculateMyBatis3XmlMapperFileName() {
598         return mapStringValueOrElseGet(getTableConfiguration().getMapperName(), mapperName -> {
599             StringBuilder sb = new StringBuilder();
600             int ind = mapperName.lastIndexOf('.');
601             if (ind == -1) {
602                 sb.append(mapperName);
603             } else {
604                 sb.append(mapperName.substring(ind + 1));
605             }
606             sb.append(".xml"); //$NON-NLS-1$
607             return sb.toString();
608         }, () -> getFullyQualifiedTable().getDomainObjectName() + "Mapper.xml"); //$NON-NLS-1$
609     }
610 
611     // this method should be called after myBatis3JavaMapperType and myBatis3XmlMapperPackage values
612     // are initialized
613     private @Nullable String calculateMyBatis3SqlMapNamespace() {
614         if (myBatis3JavaMapperType == null) {
615             return calculateMyBatis3FallbackSqlMapNamespace();
616         } else {
617             return myBatis3JavaMapperType;
618         }
619     }
620 
621     private @Nullable String calculateMyBatis3FallbackSqlMapNamespace() {
622         if (myBatis3XmlMapperPackage == null) {
623             return null;
624         }
625 
626         StringBuilder sb = new StringBuilder();
627         sb.append(myBatis3XmlMapperPackage);
628         sb.append('.');
629         ifStringHasValueElse(getTableConfiguration().getMapperName(), sb::append, () -> {
630             sb.append(getFullyQualifiedTable().getDomainObjectName());
631             sb.append("Mapper"); //$NON-NLS-1$
632         });
633         return sb.toString();
634     }
635 
636     public TableConfiguration getTableConfiguration() {
637         return Objects.requireNonNull(tableConfiguration);
638     }
639 
640     public KnownRuntime getKnownRuntime() {
641         return knownRuntime;
642     }
643 
644     public boolean isImmutable() {
645         return getTableConfiguration().isImmutable(context);
646     }
647 
648     public boolean generateKotlinV1Model() {
649         return getTableConfiguration().generateKotlinV1Model(context);
650     }
651 
652     public boolean isConstructorBased() {
653         if (isImmutable()) {
654             return true;
655         }
656 
657         Properties properties;
658 
659         if (getTableConfiguration().getProperties().containsKey(PropertyRegistry.ANY_CONSTRUCTOR_BASED)) {
660             properties = getTableConfiguration().getProperties();
661         } else {
662             properties = context.getModelGeneratorConfiguration().getProperties();
663         }
664 
665         return isTrue(properties.getProperty(PropertyRegistry.ANY_CONSTRUCTOR_BASED));
666     }
667 
668     private <T> T requireNonNullElseInternalError(@Nullable T obj, String message) {
669         if (obj == null) {
670             throw new InternalException(message);
671         }
672 
673         return obj;
674     }
675 
676     public Context getContext() {
677         return context;
678     }
679 
680     public ModelType getModelType() {
681         return tableConfiguration.getModelType().orElseGet(context::getDefaultModelType);
682     }
683 
684     public boolean isRecordBased() {
685         return getModelType() == ModelType.RECORD;
686     }
687 
688     public Optional<String> findTableOrModelGeneratorProperty(String property) {
689         return mapStringValueOrElseGet(getTableConfigurationProperty(property),
690                 Optional::of,
691                 () -> Optional.ofNullable(context.getModelGeneratorConfiguration().getProperty(property)));
692     }
693 
694     public Optional<String> findTableOrClientGeneratorProperty(String property) {
695         return mapStringValueOrElseGet(getTableConfigurationProperty(property),
696                 Optional::of,
697                 () -> context.getClientGeneratorConfiguration()
698                         .map(c -> c.getProperty(property)));
699     }
700 
701     public boolean findTableOrClientGeneratorPropertyAsBoolean(String property) {
702         return findTableOrClientGeneratorProperty(property)
703                 .map(Boolean::parseBoolean)
704                 .orElse(false);
705     }
706 
707     public abstract static class AbstractBuilder<T extends AbstractBuilder<T>> {
708         private @Nullable KnownRuntime knownRuntime;
709         private @Nullable TableConfiguration tableConfiguration;
710         private @Nullable FullyQualifiedTable fullyQualifiedTable;
711         private @Nullable Context context;
712 
713         public T withKnownRuntime(KnownRuntime knownRuntime) {
714             this.knownRuntime = knownRuntime;
715             return getThis();
716         }
717 
718         public T withTableConfiguration(TableConfiguration tableConfiguration) {
719             this.tableConfiguration = tableConfiguration;
720             return getThis();
721         }
722 
723         public T withFullyQualifiedTable(FullyQualifiedTable fullyQualifiedTable) {
724             this.fullyQualifiedTable = fullyQualifiedTable;
725             return getThis();
726         }
727 
728         public T withContext(Context context) {
729             this.context = context;
730             return getThis();
731         }
732 
733         protected abstract T getThis();
734     }
735 }