• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mybatis / generator / 1941

12 Jan 2026 04:39PM UTC coverage: 88.75% (+0.4%) from 88.365%
1941

Pull #1412

github

web-flow
Merge b9b8be318 into 98a4124a3
Pull Request #1412: Adopt JSpecify

2331 of 3162 branches covered (73.72%)

1800 of 1949 new or added lines in 202 files covered. (92.36%)

18 existing lines in 10 files now uncovered.

11384 of 12827 relevant lines covered (88.75%)

0.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.62
/core/mybatis-generator-core/src/main/java/org/mybatis/generator/api/FullyQualifiedTable.java
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.composeFullyQualifiedTableName;
19
import static org.mybatis.generator.internal.util.StringUtility.stringHasValue;
20

21
import java.util.Objects;
22
import java.util.Optional;
23
import java.util.regex.Matcher;
24
import java.util.regex.Pattern;
25

26
import org.jspecify.annotations.Nullable;
27
import org.mybatis.generator.config.Context;
28
import org.mybatis.generator.config.DomainObjectRenamingRule;
29
import org.mybatis.generator.internal.util.JavaBeansUtil;
30

31
public class FullyQualifiedTable {
32

33
    private final @Nullable String introspectedCatalog;
34
    private final @Nullable String introspectedSchema;
35
    private final String introspectedTableName;
36
    private final @Nullable String runtimeCatalog;
37
    private final @Nullable String runtimeSchema;
38
    private final @Nullable String runtimeTableName;
39
    private @Nullable String configuredDomainObjectName;
40
    private @Nullable String domainObjectSubPackage;
41
    private final @Nullable String alias;
42
    private final boolean ignoreQualifiersAtRuntime;
43
    private final String beginningDelimiter;
44
    private final String endingDelimiter;
45
    private final @Nullable DomainObjectRenamingRule domainObjectRenamingRule;
46

47
    public FullyQualifiedTable(Builder builder) {
48
        super();
1✔
49
        this.introspectedCatalog = builder.introspectedCatalog;
1✔
50
        this.introspectedSchema = builder.introspectedSchema;
1✔
51
        this.introspectedTableName = Objects.requireNonNull(builder.introspectedTableName);
1✔
52
        this.ignoreQualifiersAtRuntime = builder.ignoreQualifiersAtRuntime;
1✔
53
        this.runtimeCatalog = builder.runtimeCatalog;
1✔
54
        this.runtimeSchema = builder.runtimeSchema;
1✔
55
        this.runtimeTableName = builder.runtimeTableName;
1✔
56
        this.domainObjectRenamingRule = builder.domainObjectRenamingRule;
1✔
57

58
        if (stringHasValue(builder.domainObjectName)) {
1✔
59
            int index = builder.domainObjectName.lastIndexOf('.');
1✔
60
            if (index == -1) {
1✔
61
                this.configuredDomainObjectName = builder.domainObjectName;
1✔
62
            } else {
63
                this.configuredDomainObjectName = builder.domainObjectName.substring(index + 1);
1✔
64
                this.domainObjectSubPackage = builder.domainObjectName.substring(0, index);
1✔
65
            }
66
        }
67

68
        if (builder.alias == null) {
1✔
69
            this.alias = null;
1✔
70
        } else {
71
            this.alias = builder.alias.trim();
1✔
72
        }
73

74
        if (builder.delimitIdentifiers && builder.context != null) {
1!
75
            beginningDelimiter =  builder.context.getBeginningDelimiter();
1✔
76
            endingDelimiter = builder.context.getEndingDelimiter();
1✔
77
        } else {
78
            beginningDelimiter = ""; //$NON-NLS-1$
1✔
79
            endingDelimiter = ""; //$NON-NLS-1$
1✔
80

81
        }
82
    }
1✔
83

84
    public Optional<String> getIntrospectedCatalog() {
85
        return Optional.ofNullable(introspectedCatalog);
1✔
86
    }
87

88
    public Optional<String> getIntrospectedSchema() {
89
        return Optional.ofNullable(introspectedSchema);
1✔
90
    }
91

92
    public String getIntrospectedTableName() {
93
        return introspectedTableName;
1✔
94
    }
95

96
    public String getFullyQualifiedTableNameAtRuntime() {
97
        StringBuilder localCatalog = new StringBuilder();
1✔
98
        if (!ignoreQualifiersAtRuntime) {
1!
99
            if (stringHasValue(runtimeCatalog)) {
1!
100
                localCatalog.append(runtimeCatalog);
×
101
            } else if (stringHasValue(introspectedCatalog)) {
1!
102
                localCatalog.append(introspectedCatalog);
×
103
            }
104
        }
105
        if (!localCatalog.isEmpty()) {
1!
106
            addDelimiters(localCatalog);
×
107
        }
108

109
        StringBuilder localSchema = new StringBuilder();
1✔
110
        if (!ignoreQualifiersAtRuntime) {
1!
111
            if (stringHasValue(runtimeSchema)) {
1!
112
                localSchema.append(runtimeSchema);
×
113
            } else if (stringHasValue(introspectedSchema)) {
1✔
114
                localSchema.append(introspectedSchema);
1✔
115
            }
116
        }
117
        if (!localSchema.isEmpty()) {
1✔
118
            addDelimiters(localSchema);
1✔
119
        }
120

121
        StringBuilder localTableName = new StringBuilder();
1✔
122
        if (stringHasValue(runtimeTableName)) {
1!
123
            localTableName.append(runtimeTableName);
×
124
        } else {
125
            localTableName.append(introspectedTableName);
1✔
126
        }
127
        addDelimiters(localTableName);
1✔
128

129
        return composeFullyQualifiedTableName(localCatalog
1✔
130
                .toString(), localSchema.toString(), localTableName.toString(),
1✔
131
                '.');
132
    }
133

134
    public String getAliasedFullyQualifiedTableNameAtRuntime() {
135
        StringBuilder sb = new StringBuilder();
1✔
136

137
        sb.append(getFullyQualifiedTableNameAtRuntime());
1✔
138

139
        if (stringHasValue(alias)) {
1✔
140
            sb.append(' ');
1✔
141
            sb.append(alias);
1✔
142
        }
143

144
        return sb.toString();
1✔
145
    }
146

147
    public String getDomainObjectName() {
148
        if (stringHasValue(configuredDomainObjectName)) {
1✔
149
            return configuredDomainObjectName;
1✔
150
        }
151

152
        String finalDomainObjectName;
153
        if (stringHasValue(runtimeTableName)) {
1!
154
            finalDomainObjectName = JavaBeansUtil.getCamelCaseString(runtimeTableName, true);
×
155
        } else {
156
            finalDomainObjectName = JavaBeansUtil.getCamelCaseString(introspectedTableName, true);
1✔
157
        }
158

159
        if (domainObjectRenamingRule != null) {
1✔
160
            Pattern pattern = domainObjectRenamingRule.pattern();
1✔
161
            String replaceString = domainObjectRenamingRule.replaceString();
1✔
162
            Matcher matcher = pattern.matcher(finalDomainObjectName);
1✔
163
            finalDomainObjectName = JavaBeansUtil.getFirstCharacterUppercase(matcher.replaceAll(replaceString));
1✔
164
        }
165
        return finalDomainObjectName;
1✔
166
    }
167

168
    @Override
169
    public boolean equals(Object obj) {
170
        if (this == obj) {
×
171
            return true;
×
172
        }
173

174
        if (!(obj instanceof FullyQualifiedTable other)) {
×
175
            return false;
×
176
        }
177

178
        return Objects.equals(this.introspectedTableName, other.introspectedTableName)
×
179
                && Objects.equals(this.introspectedCatalog, other.introspectedCatalog)
×
180
                && Objects.equals(this.introspectedSchema, other.introspectedSchema);
×
181
    }
182

183
    @Override
184
    public int hashCode() {
185
        return Objects.hash(introspectedTableName, introspectedCatalog, introspectedCatalog);
1✔
186
    }
187

188
    @Override
189
    public String toString() {
190
        return composeFullyQualifiedTableName(
1✔
191
                introspectedCatalog, introspectedSchema, introspectedTableName,
192
                '.');
193
    }
194

195
    public Optional<String> getAlias() {
NEW
196
        return Optional.ofNullable(alias);
×
197
    }
198

199
    /**
200
     * Calculates a Java package fragment based on the table catalog and schema.
201
     * If qualifiers are ignored, then this method will return an empty string.
202
     *
203
     * <p>This method is used for determining the sub package for Java client and
204
     * SQL map (XML) objects.  It ignores any sub-package added to the
205
     * domain object name in the table configuration.
206
     *
207
     * @param isSubPackagesEnabled
208
     *            the is sub packages enabled
209
     * @return the subpackage for this table
210
     */
211
    public String getSubPackageForClientOrSqlMap(boolean isSubPackagesEnabled) {
212
        StringBuilder sb = new StringBuilder();
1✔
213
        if (!ignoreQualifiersAtRuntime && isSubPackagesEnabled) {
1!
214
            if (stringHasValue(runtimeCatalog)) {
1!
215
                sb.append('.');
×
216
                sb.append(runtimeCatalog.toLowerCase());
×
217
            } else if (stringHasValue(introspectedCatalog)) {
1!
218
                sb.append('.');
×
219
                sb.append(introspectedCatalog.toLowerCase());
×
220
            }
221

222
            if (stringHasValue(runtimeSchema)) {
1!
223
                sb.append('.');
×
224
                sb.append(runtimeSchema.toLowerCase());
×
225
            } else if (stringHasValue(introspectedSchema)) {
1✔
226
                sb.append('.');
1✔
227
                sb.append(introspectedSchema.toLowerCase());
1✔
228
            }
229
        }
230

231
        // TODO - strip characters that are not valid in package names
232
        return sb.toString();
1✔
233
    }
234

235
    /**
236
     * Calculates a Java package fragment based on the table catalog and schema.
237
     * If qualifiers are ignored, then this method will return an empty string.
238
     *
239
     * <p>This method is used for determining the sub package for Java model objects only.
240
     * It takes into account the possibility that a sub-package was added to the
241
     * domain object name in the table configuration.
242
     *
243
     * @param isSubPackagesEnabled
244
     *            the is sub packages enabled
245
     * @return the subpackage for this table
246
     */
247
    public String getSubPackageForModel(boolean isSubPackagesEnabled) {
248
        StringBuilder sb = new StringBuilder();
1✔
249
        sb.append(getSubPackageForClientOrSqlMap(isSubPackagesEnabled));
1✔
250

251
        if (stringHasValue(domainObjectSubPackage)) {
1✔
252
            sb.append('.');
1✔
253
            sb.append(domainObjectSubPackage);
1✔
254
        }
255

256
        return sb.toString();
1✔
257
    }
258

259
    private void addDelimiters(StringBuilder sb) {
260
        if (stringHasValue(beginningDelimiter)) {
1✔
261
            sb.insert(0, beginningDelimiter);
1✔
262
        }
263

264
        if (stringHasValue(endingDelimiter)) {
1✔
265
            sb.append(endingDelimiter);
1✔
266
        }
267
    }
1✔
268

269
    public Optional<String> getDomainObjectSubPackage() {
270
        return Optional.ofNullable(domainObjectSubPackage);
1✔
271
    }
272

273
    public static class Builder {
1✔
274
        private @Nullable String introspectedCatalog;
275
        private @Nullable String introspectedSchema;
276
        private @Nullable String introspectedTableName;
277
        private @Nullable String runtimeCatalog;
278
        private @Nullable String runtimeSchema;
279
        private @Nullable String runtimeTableName;
280
        private @Nullable String domainObjectName;
281
        private @Nullable String alias;
282
        private boolean ignoreQualifiersAtRuntime;
283
        private boolean delimitIdentifiers;
284
        private @Nullable DomainObjectRenamingRule domainObjectRenamingRule;
285
        private @Nullable Context context;
286

287
        /**
288
         * Sets the actual catalog of the table as returned from DatabaseMetaData.
289
         *
290
         * <p>This value should only be set if the user configured a catalog. Otherwise, the
291
         * DatabaseMetaData is reporting some database default that we don't want in the generated code.
292
         *
293
         * @param introspectedCatalog the introspected catalog
294
         * @return this builder
295
         */
296
        public Builder withIntrospectedCatalog(@Nullable String introspectedCatalog) {
297
            this.introspectedCatalog = introspectedCatalog;
1✔
298
            return this;
1✔
299
        }
300

301
        /**
302
         * Sets the actual schema of the table as returned from DatabaseMetaData.
303
         *
304
         * <p>This value should only be set if the user configured a schema. Otherwise, the
305
         * DatabaseMetaData is reporting some database default that we don't want in the generated code.
306
         *
307
         * @param introspectedSchema the introspected schema
308
         * @return this builder
309
         */
310
        public Builder withIntrospectedSchema(@Nullable String introspectedSchema) {
311
            this.introspectedSchema = introspectedSchema;
1✔
312
            return this;
1✔
313
        }
314

315
        /**
316
         * Sets the actual table name as returned from DatabaseMetaData.
317
         *
318
         * @param introspectedTableName the introspected table name
319
         * @return this builder
320
         */
321
        public Builder withIntrospectedTableName(String introspectedTableName) {
322
            this.introspectedTableName = introspectedTableName;
1✔
323
            return this;
1✔
324
        }
325

326
        /**
327
         * Sets the runtime catalog.
328
         *
329
         * <p>This is used to "rename" the catalog in the generated SQL. This is useful, for example, when
330
         *    generating code against one catalog that should run with a different catalog.
331
         *
332
         * @param runtimeCatalog the runtime catalog
333
         * @return this builder
334
         */
335
        public Builder withRuntimeCatalog(@Nullable String runtimeCatalog) {
336
            this.runtimeCatalog = runtimeCatalog;
1✔
337
            return this;
1✔
338
        }
339

340
        /**
341
         * Sets the runtime schema.
342
         *
343
         * <p>This is used to "rename" the schema in the generated SQL. This is useful, for example, when
344
         *    generating code against one schema that should run with a different schema.
345
         *
346
         * @param runtimeSchema the runtime schema
347
         * @return this builder
348
         */
349
        public Builder withRuntimeSchema(@Nullable String runtimeSchema) {
350
            this.runtimeSchema = runtimeSchema;
1✔
351
            return this;
1✔
352
        }
353

354
        /**
355
         * Sets the runtime table name.
356
         *
357
         * <p>This is used to "rename" the table in the generated SQL. This is useful, for example, when generating
358
         *    code to run with an Oracle synonym. The user would have to specify the actual table name and schema
359
         *    for generation, but would want to use the synonym name in the generated SQL
360
         *
361
         * @param runtimeTableName the runtime table name
362
         * @return this builder
363
         */
364
        public Builder withRuntimeTableName(@Nullable String runtimeTableName) {
365
            this.runtimeTableName = runtimeTableName;
1✔
366
            return this;
1✔
367
        }
368

369
        /**
370
         * Set the configured domain object name for this table.
371
         *
372
         * <p>If nothing is configured, we'll build the domain object named based on the tableName or runtimeTableName.
373
         *
374
         * @param domainObjectName the domain object name
375
         * @return this builder
376
         */
377
        public Builder withDomainObjectName(@Nullable String domainObjectName) {
378
            this.domainObjectName = domainObjectName;
1✔
379
            return this;
1✔
380
        }
381

382
        /**
383
         * Sets a configured alias for the table. This alias will be added to the table name in the SQL.
384
         *
385
         * @param alias the alias
386
         * @return this builder
387
         */
388
        public Builder withAlias(@Nullable String alias) {
389
            this.alias = alias;
1✔
390
            return this;
1✔
391
        }
392

393
        /**
394
         * If true, then the catalog and schema qualifiers will be ignored when composing fully qualified names
395
         * in the generated SQL.
396
         *
397
         * <p>This is used, for example, when the user needs to specify a specific schema for
398
         * generating code but does not want the schema in the generated SQL
399
         *
400
         * @param ignoreQualifiersAtRuntime whether to ignore qualifiers at runtime
401
         * @return this builder
402
         */
403
        public Builder withIgnoreQualifiersAtRuntime(boolean ignoreQualifiersAtRuntime) {
404
            this.ignoreQualifiersAtRuntime = ignoreQualifiersAtRuntime;
1✔
405
            return this;
1✔
406
        }
407

408
        /**
409
         * If true, then the table identifiers will be delimited at runtime.
410
         *
411
         * <p>The delimiter characters are obtained from the Context.
412
         *
413
         * @param delimitIdentifiers whether to delimit identifiers at runtime
414
         * @return this builder
415
         */
416
        public Builder withDelimitIdentifiers(boolean delimitIdentifiers) {
417
            this.delimitIdentifiers = delimitIdentifiers;
1✔
418
            return this;
1✔
419
        }
420

421
        /**
422
         * Sets a domain object renaming rule.
423
         *
424
         * <p>This is ignored is a domain object name is configured.
425
         *
426
         * <p>If a domain object name is not configured, we'll build the domain object named based on the tableName
427
         * or runtimeTableName, and then we use the domain object renaming rule to generate the final domain object
428
         * name.
429
         *
430
         * @param domainObjectRenamingRule the domain object renaming rule
431
         * @return this builder
432
         */
433
        public Builder withDomainObjectRenamingRule(@Nullable DomainObjectRenamingRule domainObjectRenamingRule) {
434
            this.domainObjectRenamingRule = domainObjectRenamingRule;
1✔
435
            return this;
1✔
436
        }
437

438
        public Builder withContext(Context context) {
439
            this.context = context;
1✔
440
            return this;
1✔
441
        }
442

443
        public FullyQualifiedTable build() {
444
            return new FullyQualifiedTable(this);
1✔
445
        }
446
    }
447
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc