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.config;
17  
18  import static org.mybatis.generator.internal.util.StringUtility.composeFullyQualifiedTableName;
19  import static org.mybatis.generator.internal.util.StringUtility.isTrue;
20  import static org.mybatis.generator.internal.util.StringUtility.stringHasValue;
21  
22  import java.util.ArrayList;
23  import java.util.Collections;
24  import java.util.HashMap;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Objects;
28  import java.util.Optional;
29  import java.util.Properties;
30  
31  import org.jspecify.annotations.Nullable;
32  import org.mybatis.generator.api.KnownRuntime;
33  import org.mybatis.generator.internal.util.messages.Messages;
34  
35  public class TableConfiguration extends PropertyHolder {
36      private final boolean insertStatementEnabled;
37      private final boolean selectByPrimaryKeyStatementEnabled;
38      private final boolean selectByExampleStatementEnabled;
39      private final boolean updateByPrimaryKeyStatementEnabled;
40      private final boolean deleteByPrimaryKeyStatementEnabled;
41      private final boolean deleteByExampleStatementEnabled;
42      private final boolean countByExampleStatementEnabled;
43      private final boolean updateByExampleStatementEnabled;
44      private final List<ColumnOverride> columnOverrides;
45      // this is a Map for validation purposes. Initially, all items will be FALSE. When accessed, an item will
46      // be made TRUE. This allows us to generate warning for columns configured to be ignored but not found.
47      private final Map<IgnoredColumn, Boolean> ignoredColumns;
48      private final @Nullable GeneratedKey generatedKey;
49      private final @Nullable String catalog;
50      private final @Nullable String schema;
51      private final String tableName;
52      private final @Nullable String domainObjectName;
53      private final @Nullable String alias;
54      private final @Nullable ModelType modelType;
55      private final boolean wildcardEscapingEnabled;
56      private final boolean delimitIdentifiers;
57      private final @Nullable DomainObjectRenamingRule domainObjectRenamingRule;
58      private final @Nullable ColumnRenamingRule columnRenamingRule;
59      private final boolean isAllColumnDelimitingEnabled;
60      private final @Nullable String mapperName;
61      private final @Nullable String sqlProviderName;
62      private final List<IgnoredColumnPattern> ignoredColumnPatterns;
63      private final String fullyQualifiedName;
64  
65      protected TableConfiguration(Builder builder) {
66          super(builder);
67  
68          catalog = builder.catalog;
69          schema = builder.schema;
70          tableName = Objects.requireNonNull(builder.tableName);
71          domainObjectName = builder.domainObjectName;
72          alias = builder.alias;
73          modelType = builder.modelType;
74          insertStatementEnabled = builder.insertStatementEnabled;
75          selectByPrimaryKeyStatementEnabled = builder.selectByPrimaryKeyStatementEnabled;
76          selectByExampleStatementEnabled = builder.selectByExampleStatementEnabled;
77          updateByPrimaryKeyStatementEnabled = builder.updateByPrimaryKeyStatementEnabled;
78          deleteByPrimaryKeyStatementEnabled = builder.deleteByPrimaryKeyStatementEnabled;
79          deleteByExampleStatementEnabled = builder.deleteByExampleStatementEnabled;
80          countByExampleStatementEnabled = builder.countByExampleStatementEnabled;
81          updateByExampleStatementEnabled = builder.updateByExampleStatementEnabled;
82          wildcardEscapingEnabled = builder.wildcardEscapingEnabled;
83          delimitIdentifiers = builder.delimitIdentifiers;
84          isAllColumnDelimitingEnabled = builder.isAllColumnDelimitingEnabled;
85          mapperName = builder.mapperName;
86          sqlProviderName = builder.sqlProviderName;
87          columnOverrides = Collections.unmodifiableList(builder.columnOverrides);
88          ignoredColumns = builder.ignoredColumns;
89          generatedKey = builder.generatedKey;
90          domainObjectRenamingRule = builder.domainObjectRenamingRule;
91          columnRenamingRule = builder.columnRenamingRule;
92          ignoredColumnPatterns = Collections.unmodifiableList(builder.ignoredColumnPatterns);
93          fullyQualifiedName = composeFullyQualifiedTableName(catalog, schema, tableName, '.');
94      }
95  
96      public boolean isDeleteByPrimaryKeyStatementEnabled() {
97          return deleteByPrimaryKeyStatementEnabled;
98      }
99  
100     public boolean isInsertStatementEnabled() {
101         return insertStatementEnabled;
102     }
103 
104     public boolean isSelectByPrimaryKeyStatementEnabled() {
105         return selectByPrimaryKeyStatementEnabled;
106     }
107 
108     public boolean isUpdateByPrimaryKeyStatementEnabled() {
109         return updateByPrimaryKeyStatementEnabled;
110     }
111 
112     public boolean isColumnIgnored(String columnName) {
113         for (Map.Entry<IgnoredColumn, Boolean> entry : ignoredColumns.entrySet()) {
114             if (entry.getKey().matches(columnName)) {
115                 entry.setValue(Boolean.TRUE);
116                 return true;
117             }
118         }
119 
120         for (IgnoredColumnPattern ignoredColumnPattern : ignoredColumnPatterns) {
121             if (ignoredColumnPattern.matches(columnName)) {
122                 return true;
123             }
124         }
125 
126         return false;
127     }
128 
129     @Override
130     public boolean equals(Object obj) {
131         if (this == obj) {
132             return true;
133         }
134 
135         if (!(obj instanceof TableConfiguration other)) {
136             return false;
137         }
138 
139         return Objects.equals(this.catalog, other.catalog)
140                 && Objects.equals(this.schema, other.schema)
141                 && Objects.equals(this.tableName, other.tableName);
142     }
143 
144     @Override
145     public int hashCode() {
146         return Objects.hash(catalog, schema, tableName);
147     }
148 
149     public boolean isSelectByExampleStatementEnabled() {
150         return selectByExampleStatementEnabled;
151     }
152 
153     /**
154      * May return null if the column has not been overridden.
155      *
156      * @param columnName
157      *            the column name
158      * @return the column override (if any) related to this column
159      */
160     public Optional<ColumnOverride> getColumnOverride(String columnName) {
161         for (ColumnOverride co : columnOverrides) {
162             if (co.isColumnNameDelimited()) {
163                 if (columnName.equals(co.getColumnName())) {
164                     return Optional.of(co);
165                 }
166             } else {
167                 if (columnName.equalsIgnoreCase(co.getColumnName())) {
168                     return Optional.of(co);
169                 }
170             }
171         }
172 
173         return Optional.empty();
174     }
175 
176     public Optional<GeneratedKey> getGeneratedKey() {
177         return Optional.ofNullable(generatedKey);
178     }
179 
180     public boolean isDeleteByExampleStatementEnabled() {
181         return deleteByExampleStatementEnabled;
182     }
183 
184     public boolean areAnyStatementsEnabled() {
185         return selectByExampleStatementEnabled
186                 || selectByPrimaryKeyStatementEnabled || insertStatementEnabled
187                 || updateByPrimaryKeyStatementEnabled
188                 || deleteByExampleStatementEnabled
189                 || deleteByPrimaryKeyStatementEnabled
190                 || countByExampleStatementEnabled
191                 || updateByExampleStatementEnabled;
192     }
193 
194     public @Nullable String getAlias() {
195         return alias;
196     }
197 
198     public @Nullable String getCatalog() {
199         return catalog;
200     }
201 
202     public @Nullable String getDomainObjectName() {
203         return domainObjectName;
204     }
205 
206     public @Nullable String getSchema() {
207         return schema;
208     }
209 
210     public String getTableName() {
211         return tableName;
212     }
213 
214     public String getFullyQualifiedName() {
215         return fullyQualifiedName;
216     }
217 
218     public List<ColumnOverride> getColumnOverrides() {
219         return columnOverrides;
220     }
221 
222     /**
223      * Returns a List of Strings. The values are the columns
224      * that were specified to be ignored in the table but do not exist in the
225      * table.
226      *
227      * @return a List of Strings - the columns that were improperly configured
228      *         as ignored columns
229      */
230     public List<String> getIgnoredColumnsInError() {
231         List<String> answer = new ArrayList<>();
232 
233         for (Map.Entry<IgnoredColumn, Boolean> entry : ignoredColumns.entrySet()) {
234             if (Boolean.FALSE.equals(entry.getValue())) {
235                 answer.add(entry.getKey().getColumnName());
236             }
237         }
238 
239         return answer;
240     }
241 
242     public Optional<ModelType> getModelType() {
243         return Optional.ofNullable(modelType);
244     }
245 
246     public boolean isWildcardEscapingEnabled() {
247         return wildcardEscapingEnabled;
248     }
249 
250     @Override
251     public String toString() {
252         return fullyQualifiedName;
253     }
254 
255     public boolean isDelimitIdentifiers() {
256         return delimitIdentifiers;
257     }
258 
259     public boolean isCountByExampleStatementEnabled() {
260         return countByExampleStatementEnabled;
261     }
262 
263     public boolean isUpdateByExampleStatementEnabled() {
264         return updateByExampleStatementEnabled;
265     }
266 
267     public void validate(List<String> errors, int listPosition, Context context, KnownRuntime knownRuntime) {
268         if (!stringHasValue(tableName)) {
269             errors.add(Messages.getString(
270                     "ValidationError.6", Integer.toString(listPosition))); //$NON-NLS-1$
271         }
272 
273         if (generatedKey != null) {
274             // if the model type is immutable or record, then we cannot have generated keys
275             ModelType mt = getModelType().orElseGet(context::getDefaultModelType);
276             if (mt == ModelType.RECORD) {
277                 errors.add(Messages.getString("ValidationError.30", fullyQualifiedName, context.getId(), //$NON-NLS-1$
278                         "record")); //$NON-NLS-1$
279             }
280 
281             // we're going to allow generated keys for Kotlin even if the rest of the model is immutable
282             if (isImmutable(context) && knownRuntime != KnownRuntime.MYBATIS3_KOTLIN) {
283                 errors.add(Messages.getString("ValidationError.30", fullyQualifiedName, context.getId(), //$NON-NLS-1$
284                         "immutable")); //$NON-NLS-1$
285             }
286 
287             generatedKey.validate(errors, fullyQualifiedName, context.getId());
288         }
289 
290         if (domainObjectRenamingRule != null) {
291             domainObjectRenamingRule.validate(errors, fullyQualifiedName);
292         }
293 
294         if (columnRenamingRule != null) {
295             columnRenamingRule.validate(errors, fullyQualifiedName);
296         }
297 
298         for (ColumnOverride columnOverride : columnOverrides) {
299             columnOverride.validate(errors, fullyQualifiedName);
300         }
301 
302         for (IgnoredColumn ignoredColumn : ignoredColumns.keySet()) {
303             ignoredColumn.validate(errors, fullyQualifiedName);
304         }
305 
306         for (IgnoredColumnPattern ignoredColumnPattern : ignoredColumnPatterns) {
307             ignoredColumnPattern.validate(errors, fullyQualifiedName);
308         }
309     }
310 
311     public boolean isImmutable(Context context) {
312         Properties properties;
313 
314         if (getProperties().containsKey(PropertyRegistry.ANY_IMMUTABLE)) {
315             properties = getProperties();
316         } else {
317             properties = context.getModelGeneratorConfiguration().getProperties();
318         }
319 
320         return isTrue(properties.getProperty(PropertyRegistry.ANY_IMMUTABLE));
321     }
322 
323     public boolean generateKotlinV1Model(Context context) {
324         Properties properties;
325 
326         if (getProperties().containsKey(PropertyRegistry.GENERATE_KOTLIN_V1_MODEL)) {
327             properties = getProperties();
328         } else {
329             properties = context.getModelGeneratorConfiguration().getProperties();
330         }
331 
332         return isTrue(properties.getProperty(PropertyRegistry.GENERATE_KOTLIN_V1_MODEL));
333     }
334 
335     public @Nullable DomainObjectRenamingRule getDomainObjectRenamingRule() {
336         return domainObjectRenamingRule;
337     }
338 
339     public Optional<ColumnRenamingRule> getColumnRenamingRule() {
340         return Optional.ofNullable(columnRenamingRule);
341     }
342 
343     public boolean isAllColumnDelimitingEnabled() {
344         return isAllColumnDelimitingEnabled;
345     }
346 
347     public @Nullable String getMapperName() {
348         return mapperName;
349     }
350 
351     public @Nullable String getSqlProviderName() {
352         return sqlProviderName;
353     }
354 
355     public @Nullable String getDynamicSqlSupportClassName() {
356         return getProperty(PropertyRegistry.TABLE_DYNAMIC_SQL_SUPPORT_CLASS_NAME);
357     }
358 
359     public @Nullable String getDynamicSqlTableObjectName() {
360         return getProperty(PropertyRegistry.TABLE_DYNAMIC_SQL_TABLE_OBJECT_NAME);
361     }
362 
363     public static class Builder extends AbstractBuilder<Builder> {
364         private @Nullable ModelType modelType;
365         private @Nullable String catalog;
366         private @Nullable String schema;
367         private @Nullable String tableName;
368         private @Nullable String domainObjectName;
369         private @Nullable String alias;
370         private boolean insertStatementEnabled = true;
371         private boolean selectByPrimaryKeyStatementEnabled = true;
372         private boolean selectByExampleStatementEnabled = true;
373         private boolean updateByPrimaryKeyStatementEnabled = true;
374         private boolean deleteByPrimaryKeyStatementEnabled = true;
375         private boolean deleteByExampleStatementEnabled = true;
376         private boolean countByExampleStatementEnabled = true;
377         private boolean updateByExampleStatementEnabled = true;
378         private boolean wildcardEscapingEnabled;
379         private boolean delimitIdentifiers;
380         private boolean isAllColumnDelimitingEnabled;
381         private @Nullable String mapperName;
382         private @Nullable String sqlProviderName;
383         private @Nullable GeneratedKey generatedKey;
384         private @Nullable DomainObjectRenamingRule domainObjectRenamingRule;
385         private @Nullable ColumnRenamingRule columnRenamingRule;
386         private final List<IgnoredColumnPattern> ignoredColumnPatterns = new ArrayList<>();
387         private final List<ColumnOverride> columnOverrides = new ArrayList<>();
388         private final Map<IgnoredColumn, Boolean> ignoredColumns = new HashMap<>();
389 
390         public TableConfiguration build() {
391             return new TableConfiguration(this);
392         }
393 
394         @Override
395         protected Builder getThis() {
396             return this;
397         }
398 
399         public Builder withModelType(@Nullable String tableModelType) {
400             this.modelType = tableModelType == null ? null : ModelType.getModelType(tableModelType);
401             return getThis();
402         }
403 
404         public Builder withCatalog(@Nullable String catalog) {
405             this.catalog = catalog;
406             return this;
407         }
408 
409         public Builder withSchema(@Nullable String schema) {
410             this.schema = schema;
411             return this;
412         }
413 
414         public Builder withTableName(@Nullable String tableName) {
415             this.tableName = tableName;
416             return this;
417         }
418 
419         public Builder withDomainObjectName(@Nullable String domainObjectName) {
420             this.domainObjectName = domainObjectName;
421             return this;
422         }
423 
424         public Builder withAlias(@Nullable String alias) {
425             this.alias = alias;
426             return this;
427         }
428 
429         @SuppressWarnings("UnusedReturnValue")
430         public Builder withInsertStatementEnabled(boolean insertStatementEnabled) {
431             this.insertStatementEnabled = insertStatementEnabled;
432             return this;
433         }
434 
435         @SuppressWarnings("UnusedReturnValue")
436         public Builder withSelectByPrimaryKeyStatementEnabled(boolean selectByPrimaryKeyStatementEnabled) {
437             this.selectByPrimaryKeyStatementEnabled = selectByPrimaryKeyStatementEnabled;
438             return this;
439         }
440 
441         @SuppressWarnings("UnusedReturnValue")
442         public Builder withSelectByExampleStatementEnabled(boolean selectByExampleStatementEnabled) {
443             this.selectByExampleStatementEnabled = selectByExampleStatementEnabled;
444             return this;
445         }
446 
447         @SuppressWarnings("UnusedReturnValue")
448         public Builder withUpdateByPrimaryKeyStatementEnabled(boolean updateByPrimaryKeyStatementEnabled) {
449             this.updateByPrimaryKeyStatementEnabled = updateByPrimaryKeyStatementEnabled;
450             return this;
451         }
452 
453         @SuppressWarnings("UnusedReturnValue")
454         public Builder withDeleteByPrimaryKeyStatementEnabled(boolean deleteByPrimaryKeyStatementEnabled) {
455             this.deleteByPrimaryKeyStatementEnabled = deleteByPrimaryKeyStatementEnabled;
456             return this;
457         }
458 
459         @SuppressWarnings("UnusedReturnValue")
460         public Builder withDeleteByExampleStatementEnabled(boolean deleteByExampleStatementEnabled) {
461             this.deleteByExampleStatementEnabled = deleteByExampleStatementEnabled;
462             return this;
463         }
464 
465         @SuppressWarnings("UnusedReturnValue")
466         public Builder withCountByExampleStatementEnabled(boolean countByExampleStatementEnabled) {
467             this.countByExampleStatementEnabled = countByExampleStatementEnabled;
468             return this;
469         }
470 
471         @SuppressWarnings("UnusedReturnValue")
472         public Builder withUpdateByExampleStatementEnabled(boolean updateByExampleStatementEnabled) {
473             this.updateByExampleStatementEnabled = updateByExampleStatementEnabled;
474             return this;
475         }
476 
477         @SuppressWarnings("UnusedReturnValue")
478         public Builder withWildcardEscapingEnabled(boolean wildcardEscapingEnabled) {
479             this.wildcardEscapingEnabled = wildcardEscapingEnabled;
480             return this;
481         }
482 
483         @SuppressWarnings("UnusedReturnValue")
484         public Builder withDelimitIdentifiers(boolean delimitIdentifiers) {
485             this.delimitIdentifiers = delimitIdentifiers;
486             return this;
487         }
488 
489         @SuppressWarnings("UnusedReturnValue")
490         public Builder withAllColumnDelimitingEnabled(boolean isAllColumnDelimitingEnabled) {
491             this.isAllColumnDelimitingEnabled = isAllColumnDelimitingEnabled;
492             return this;
493         }
494 
495         public Builder withMapperName(@Nullable String mapperName) {
496             this.mapperName = mapperName;
497             return this;
498         }
499 
500         public Builder withSqlProviderName(@Nullable String sqlProviderName) {
501             this.sqlProviderName = sqlProviderName;
502             return this;
503         }
504 
505         @SuppressWarnings("UnusedReturnValue")
506         public Builder withGeneratedKey(@Nullable GeneratedKey generatedKey) {
507             this.generatedKey = generatedKey;
508             return this;
509         }
510 
511         @SuppressWarnings("UnusedReturnValue")
512         public Builder withDomainObjectRenamingRule(@Nullable DomainObjectRenamingRule domainObjectRenamingRule) {
513             this.domainObjectRenamingRule = domainObjectRenamingRule;
514             return this;
515         }
516 
517         @SuppressWarnings("UnusedReturnValue")
518         public Builder withColumnRenamingRule(@Nullable ColumnRenamingRule columnRenamingRule) {
519             this.columnRenamingRule = columnRenamingRule;
520             return this;
521         }
522 
523         @SuppressWarnings("UnusedReturnValue")
524         public Builder withIgnoredColumnPattern(IgnoredColumnPattern ignoredColumnPattern) {
525             this.ignoredColumnPatterns.add(ignoredColumnPattern);
526             return this;
527         }
528 
529         @SuppressWarnings("UnusedReturnValue")
530         public Builder withIgnoredColumn(IgnoredColumn ignoredColumn) {
531             this.ignoredColumns.put(ignoredColumn, Boolean.FALSE);
532             return this;
533         }
534 
535         @SuppressWarnings("UnusedReturnValue")
536         public Builder withColumnOverride(ColumnOverride columnOverride) {
537             this.columnOverrides.add(columnOverride);
538             return this;
539         }
540     }
541 }