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

mybatis / generator / 1646

21 Apr 2025 10:17PM UTC coverage: 88.157% (-0.2%) from 88.328%
1646

push

github

hazendaz
[ci] Run auto formatting

2518 of 3412 branches covered (73.8%)

994 of 1117 new or added lines in 164 files covered. (88.99%)

23 existing lines in 12 files now uncovered.

10578 of 11999 relevant lines covered (88.16%)

0.88 hits per line

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

27.85
/core/mybatis-generator-core/src/main/java/org/mybatis/generator/api/MyBatisGenerator.java
1
/*
2
 *    Copyright 2006-2025 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.ClassloaderUtility.getCustomClassloader;
19
import static org.mybatis.generator.internal.util.messages.Messages.getString;
20

21
import java.io.BufferedWriter;
22
import java.io.File;
23
import java.io.FileOutputStream;
24
import java.io.IOException;
25
import java.io.OutputStreamWriter;
26
import java.nio.charset.Charset;
27
import java.sql.SQLException;
28
import java.util.ArrayList;
29
import java.util.HashSet;
30
import java.util.List;
31
import java.util.Objects;
32
import java.util.Set;
33

34
import org.mybatis.generator.codegen.RootClassInfo;
35
import org.mybatis.generator.config.Configuration;
36
import org.mybatis.generator.config.Context;
37
import org.mybatis.generator.config.MergeConstants;
38
import org.mybatis.generator.exception.InvalidConfigurationException;
39
import org.mybatis.generator.exception.ShellException;
40
import org.mybatis.generator.internal.DefaultShellCallback;
41
import org.mybatis.generator.internal.ObjectFactory;
42
import org.mybatis.generator.internal.XmlFileMergerJaxp;
43

44
/**
45
 * This class is the main interface to MyBatis generator. A typical execution of the tool involves these steps:
46
 * <ol>
47
 * <li>Create a Configuration object. The Configuration can be the result of a parsing the XML configuration file, or it
48
 * can be created solely in Java.</li>
49
 * <li>Create a MyBatisGenerator object</li>
50
 * <li>Call one of the generate() methods</li>
51
 * </ol>
52
 *
53
 * @author Jeff Butler
54
 *
55
 * @see org.mybatis.generator.config.xml.ConfigurationParser
56
 */
57
public class MyBatisGenerator {
58

59
    private static final ProgressCallback NULL_PROGRESS_CALLBACK = new ProgressCallback() {
1✔
60
    };
61

62
    private final Configuration configuration;
63

64
    private final ShellCallback shellCallback;
65

66
    private final List<GeneratedJavaFile> generatedJavaFiles = new ArrayList<>();
1✔
67

68
    private final List<GeneratedXmlFile> generatedXmlFiles = new ArrayList<>();
1✔
69

70
    private final List<GeneratedKotlinFile> generatedKotlinFiles = new ArrayList<>();
1✔
71

72
    /**
73
     * Any kind of generated file generated by plugin methods contextGenerateAdditionalFiles.
74
     */
75
    private final List<GeneratedFile> otherGeneratedFiles = new ArrayList<>();
1✔
76

77
    private final List<String> warnings;
78

79
    private final Set<String> projects = new HashSet<>();
1✔
80

81
    /**
82
     * Constructs a MyBatisGenerator object.
83
     *
84
     * @param configuration
85
     *            The configuration for this invocation
86
     * @param shellCallback
87
     *            an instance of a ShellCallback interface. You may specify <code>null</code> in which case the
88
     *            DefaultShellCallback will be used.
89
     * @param warnings
90
     *            Any warnings generated during execution will be added to this list. Warnings do not affect the running
91
     *            of the tool, but they may affect the results. A typical warning is an unsupported data type. In that
92
     *            case, the column will be ignored and generation will continue. You may specify <code>null</code> if
93
     *            you do not want warnings returned.
94
     *
95
     * @throws InvalidConfigurationException
96
     *             if the specified configuration is invalid
97
     */
98
    public MyBatisGenerator(Configuration configuration, ShellCallback shellCallback, List<String> warnings)
99
            throws InvalidConfigurationException {
100
        super();
1✔
101
        if (configuration == null) {
1!
102
            throw new IllegalArgumentException(getString("RuntimeError.2")); //$NON-NLS-1$
×
103
        } else {
104
            this.configuration = configuration;
1✔
105
        }
106

107
        this.shellCallback = Objects.requireNonNullElseGet(shellCallback, () -> new DefaultShellCallback(false));
1✔
108

109
        this.warnings = Objects.requireNonNullElseGet(warnings, ArrayList::new);
1✔
110

111
        this.configuration.validate();
1✔
112
    }
1✔
113

114
    /**
115
     * This is the main method for generating code. This method is long-running, but progress can be provided and the
116
     * method can be canceled through the ProgressCallback interface. This version of the method runs all configured
117
     * contexts.
118
     *
119
     * @param callback
120
     *            an instance of the ProgressCallback interface, or <code>null</code> if you do not require progress
121
     *            information
122
     *
123
     * @throws SQLException
124
     *             the SQL exception
125
     * @throws IOException
126
     *             Signals that an I/O exception has occurred.
127
     * @throws InterruptedException
128
     *             if the method is canceled through the ProgressCallback
129
     */
130
    public void generate(ProgressCallback callback) throws SQLException, IOException, InterruptedException {
UNCOV
131
        generate(callback, null, null, true);
×
132
    }
×
133

134
    /**
135
     * This is the main method for generating code. This method is long-running, but progress can be provided and the
136
     * method can be canceled through the ProgressCallback interface.
137
     *
138
     * @param callback
139
     *            an instance of the ProgressCallback interface, or <code>null</code> if you do not require progress
140
     *            information
141
     * @param contextIds
142
     *            a set of Strings containing context ids to run. Only the contexts with an id specified in this list
143
     *            will be run. If the list is null or empty, then all contexts are run.
144
     *
145
     * @throws SQLException
146
     *             the SQL exception
147
     * @throws IOException
148
     *             Signals that an I/O exception has occurred.
149
     * @throws InterruptedException
150
     *             if the method is canceled through the ProgressCallback
151
     */
152
    public void generate(ProgressCallback callback, Set<String> contextIds)
153
            throws SQLException, IOException, InterruptedException {
154
        generate(callback, contextIds, null, true);
×
155
    }
×
156

157
    /**
158
     * This is the main method for generating code. This method is long-running, but progress can be provided and the
159
     * method can be cancelled through the ProgressCallback interface.
160
     *
161
     * @param callback
162
     *            an instance of the ProgressCallback interface, or <code>null</code> if you do not require progress
163
     *            information
164
     * @param contextIds
165
     *            a set of Strings containing context ids to run. Only the contexts with an id specified in this list
166
     *            will be run. If the list is null or empty, then all contexts are run.
167
     * @param fullyQualifiedTableNames
168
     *            a set of table names to generate. The elements of the set must be Strings that exactly match what's
169
     *            specified in the configuration. For example, if table name = "foo" and schema = "bar", then the fully
170
     *            qualified table name is "foo.bar". If the Set is null or empty, then all tables in the configuration
171
     *            will be used for code generation.
172
     *
173
     * @throws SQLException
174
     *             the SQL exception
175
     * @throws IOException
176
     *             Signals that an I/O exception has occurred.
177
     * @throws InterruptedException
178
     *             if the method is canceled through the ProgressCallback
179
     */
180
    public void generate(ProgressCallback callback, Set<String> contextIds, Set<String> fullyQualifiedTableNames)
181
            throws SQLException, IOException, InterruptedException {
182
        generate(callback, contextIds, fullyQualifiedTableNames, true);
×
183
    }
×
184

185
    /**
186
     * This is the main method for generating code. This method is long-running, but progress can be provided and the
187
     * method can be cancelled through the ProgressCallback interface.
188
     *
189
     * @param callback
190
     *            an instance of the ProgressCallback interface, or <code>null</code> if you do not require progress
191
     *            information
192
     * @param contextIds
193
     *            a set of Strings containing context ids to run. Only the contexts with an id specified in this list
194
     *            will be run. If the list is null or empty, then all contexts are run.
195
     * @param fullyQualifiedTableNames
196
     *            a set of table names to generate. The elements of the set must be Strings that exactly match what's
197
     *            specified in the configuration. For example, if table name = "foo" and schema = "bar", then the fully
198
     *            qualified table name is "foo.bar". If the Set is null or empty, then all tables in the configuration
199
     *            will be used for code generation.
200
     * @param writeFiles
201
     *            if true, then the generated files will be written to disk. If false, then the generator runs but
202
     *            nothing is written
203
     *
204
     * @throws SQLException
205
     *             the SQL exception
206
     * @throws IOException
207
     *             Signals that an I/O exception has occurred.
208
     * @throws InterruptedException
209
     *             if the method is canceled through the ProgressCallback
210
     */
211
    public void generate(ProgressCallback callback, Set<String> contextIds, Set<String> fullyQualifiedTableNames,
212
            boolean writeFiles) throws SQLException, IOException, InterruptedException {
213

214
        if (callback == null) {
1!
215
            callback = NULL_PROGRESS_CALLBACK;
1✔
216
        }
217

218
        generatedJavaFiles.clear();
1✔
219
        generatedXmlFiles.clear();
1✔
220
        ObjectFactory.reset();
1✔
221
        RootClassInfo.reset();
1✔
222

223
        // calculate the contexts to run
224
        List<Context> contextsToRun;
225
        if (contextIds == null || contextIds.isEmpty()) {
1!
226
            contextsToRun = configuration.getContexts();
1✔
227
        } else {
228
            contextsToRun = new ArrayList<>();
×
229
            for (Context context : configuration.getContexts()) {
×
230
                if (contextIds.contains(context.getId())) {
×
231
                    contextsToRun.add(context);
×
232
                }
233
            }
×
234
        }
235

236
        // setup custom classloader if required
237
        if (!configuration.getClassPathEntries().isEmpty()) {
1!
238
            ClassLoader classLoader = getCustomClassloader(configuration.getClassPathEntries());
×
239
            ObjectFactory.addExternalClassLoader(classLoader);
×
240
        }
241

242
        // now run the introspections...
243
        int totalSteps = 0;
1✔
244
        for (Context context : contextsToRun) {
1✔
245
            totalSteps += context.getIntrospectionSteps();
1✔
246
        }
1✔
247
        callback.introspectionStarted(totalSteps);
1✔
248

249
        for (Context context : contextsToRun) {
1✔
250
            context.introspectTables(callback, warnings, fullyQualifiedTableNames);
1✔
251
        }
1✔
252

253
        // now run the generates
254
        totalSteps = 0;
1✔
255
        for (Context context : contextsToRun) {
1✔
256
            totalSteps += context.getGenerationSteps();
1✔
257
        }
1✔
258
        callback.generationStarted(totalSteps);
1✔
259

260
        for (Context context : contextsToRun) {
1✔
261
            context.generateFiles(callback, generatedJavaFiles, generatedXmlFiles, generatedKotlinFiles,
1✔
262
                    otherGeneratedFiles, warnings);
263
        }
1✔
264

265
        // now save the files
266
        if (writeFiles) {
1!
NEW
267
            callback.saveStarted(generatedXmlFiles.size() + generatedJavaFiles.size());
×
268

269
            for (GeneratedXmlFile gxf : generatedXmlFiles) {
×
270
                projects.add(gxf.getTargetProject());
×
271
                writeGeneratedXmlFile(gxf, callback);
×
272
            }
×
273

274
            for (GeneratedJavaFile gjf : generatedJavaFiles) {
×
275
                projects.add(gjf.getTargetProject());
×
276
                writeGeneratedJavaFile(gjf, callback);
×
277
            }
×
278

279
            for (GeneratedKotlinFile gkf : generatedKotlinFiles) {
×
280
                projects.add(gkf.getTargetProject());
×
281
                writeGeneratedFile(gkf, callback);
×
282
            }
×
283

284
            for (GeneratedFile gf : otherGeneratedFiles) {
×
285
                projects.add(gf.getTargetProject());
×
286
                writeGeneratedFile(gf, callback);
×
287
            }
×
288

289
            for (String project : projects) {
×
290
                shellCallback.refreshProject(project);
×
291
            }
×
292
        }
293

294
        callback.done();
1✔
295
    }
1✔
296

297
    private void writeGeneratedJavaFile(GeneratedJavaFile gjf, ProgressCallback callback)
298
            throws InterruptedException, IOException {
299
        File targetFile;
300
        String source;
301
        try {
NEW
302
            File directory = shellCallback.getDirectory(gjf.getTargetProject(), gjf.getTargetPackage());
×
303
            targetFile = new File(directory, gjf.getFileName());
×
304
            if (targetFile.exists()) {
×
305
                if (shellCallback.isMergeSupported()) {
×
NEW
306
                    source = shellCallback.mergeJavaFile(gjf.getFormattedContent(), targetFile,
×
NEW
307
                            MergeConstants.getOldElementTags(), gjf.getFileEncoding());
×
308
                } else if (shellCallback.isOverwriteEnabled()) {
×
309
                    source = gjf.getFormattedContent();
×
310
                    warnings.add(getString("Warning.11", //$NON-NLS-1$
×
311
                            targetFile.getAbsolutePath()));
×
312
                } else {
313
                    source = gjf.getFormattedContent();
×
NEW
314
                    targetFile = getUniqueFileName(directory, gjf.getFileName());
×
NEW
315
                    warnings.add(getString("Warning.2", targetFile.getAbsolutePath())); //$NON-NLS-1$
×
316
                }
317
            } else {
318
                source = gjf.getFormattedContent();
×
319
            }
320

321
            callback.checkCancel();
×
NEW
322
            callback.startTask(getString("Progress.15", targetFile.getName())); //$NON-NLS-1$
×
323
            writeFile(targetFile, source, gjf.getFileEncoding());
×
324
        } catch (ShellException e) {
×
325
            warnings.add(e.getMessage());
×
326
        }
×
327
    }
×
328

329
    private void writeGeneratedFile(GeneratedFile gf, ProgressCallback callback)
330
            throws InterruptedException, IOException {
331
        File targetFile;
332
        String source;
333
        try {
NEW
334
            File directory = shellCallback.getDirectory(gf.getTargetProject(), gf.getTargetPackage());
×
335
            targetFile = new File(directory, gf.getFileName());
×
336
            if (targetFile.exists()) {
×
337
                if (shellCallback.isOverwriteEnabled()) {
×
338
                    source = gf.getFormattedContent();
×
339
                    warnings.add(getString("Warning.11", //$NON-NLS-1$
×
340
                            targetFile.getAbsolutePath()));
×
341
                } else {
342
                    source = gf.getFormattedContent();
×
NEW
343
                    targetFile = getUniqueFileName(directory, gf.getFileName());
×
NEW
344
                    warnings.add(getString("Warning.2", targetFile.getAbsolutePath())); //$NON-NLS-1$
×
345
                }
346
            } else {
347
                source = gf.getFormattedContent();
×
348
            }
349

350
            callback.checkCancel();
×
NEW
351
            callback.startTask(getString("Progress.15", targetFile.getName())); //$NON-NLS-1$
×
352
            writeFile(targetFile, source, gf.getFileEncoding());
×
353
        } catch (ShellException e) {
×
354
            warnings.add(e.getMessage());
×
355
        }
×
356
    }
×
357

358
    private void writeGeneratedXmlFile(GeneratedXmlFile gxf, ProgressCallback callback)
359
            throws InterruptedException, IOException {
360
        File targetFile;
361
        String source;
362
        try {
NEW
363
            File directory = shellCallback.getDirectory(gxf.getTargetProject(), gxf.getTargetPackage());
×
364
            targetFile = new File(directory, gxf.getFileName());
×
365
            if (targetFile.exists()) {
×
366
                if (gxf.isMergeable()) {
×
NEW
367
                    source = XmlFileMergerJaxp.getMergedSource(gxf, targetFile);
×
UNCOV
368
                } else if (shellCallback.isOverwriteEnabled()) {
×
369
                    source = gxf.getFormattedContent();
×
370
                    warnings.add(getString("Warning.11", //$NON-NLS-1$
×
371
                            targetFile.getAbsolutePath()));
×
372
                } else {
373
                    source = gxf.getFormattedContent();
×
NEW
374
                    targetFile = getUniqueFileName(directory, gxf.getFileName());
×
NEW
375
                    warnings.add(getString("Warning.2", targetFile.getAbsolutePath())); //$NON-NLS-1$
×
376
                }
377
            } else {
378
                source = gxf.getFormattedContent();
×
379
            }
380

381
            callback.checkCancel();
×
NEW
382
            callback.startTask(getString("Progress.15", targetFile.getName())); //$NON-NLS-1$
×
383
            writeFile(targetFile, source, gxf.getFileEncoding());
×
384
        } catch (ShellException e) {
×
385
            warnings.add(e.getMessage());
×
386
        }
×
387
    }
×
388

389
    /**
390
     * Writes, or overwrites, the contents of the specified file.
391
     *
392
     * @param file
393
     *            the file
394
     * @param content
395
     *            the content
396
     * @param fileEncoding
397
     *            the file encoding
398
     *
399
     * @throws IOException
400
     *             Signals that an I/O exception has occurred.
401
     */
402
    private void writeFile(File file, String content, String fileEncoding) throws IOException {
403
        try (FileOutputStream fos = new FileOutputStream(file, false)) {
×
404
            OutputStreamWriter osw;
405
            if (fileEncoding == null) {
×
406
                osw = new OutputStreamWriter(fos);
×
407
            } else {
408
                osw = new OutputStreamWriter(fos, Charset.forName(fileEncoding));
×
409
            }
410

411
            try (BufferedWriter bw = new BufferedWriter(osw)) {
×
412
                bw.write(content);
×
413
            }
414
        }
415
    }
×
416

417
    /**
418
     * Gets the unique file name.
419
     *
420
     * @param directory
421
     *            the directory
422
     * @param fileName
423
     *            the file name
424
     *
425
     * @return the unique file name
426
     */
427
    private File getUniqueFileName(File directory, String fileName) {
428
        File answer = null;
×
429

430
        // try up to 1000 times to generate a unique file name
431
        StringBuilder sb = new StringBuilder();
×
432
        for (int i = 1; i < 1000; i++) {
×
433
            sb.setLength(0);
×
434
            sb.append(fileName);
×
435
            sb.append('.');
×
436
            sb.append(i);
×
437

438
            File testFile = new File(directory, sb.toString());
×
439
            if (!testFile.exists()) {
×
440
                answer = testFile;
×
441
                break;
×
442
            }
443
        }
444

445
        if (answer == null) {
×
NEW
446
            throw new RuntimeException(getString("RuntimeError.3", directory.getAbsolutePath())); //$NON-NLS-1$
×
447
        }
448

449
        return answer;
×
450
    }
451

452
    /**
453
     * Returns the list of generated Java files after a call to one of the generate methods. This is useful if you
454
     * prefer to process the generated files yourself and do not want the generator to write them to disk.
455
     *
456
     * @return the list of generated Java files
457
     */
458
    public List<GeneratedJavaFile> getGeneratedJavaFiles() {
459
        return generatedJavaFiles;
1✔
460
    }
461

462
    /**
463
     * Returns the list of generated Kotlin files after a call to one of the generate methods. This is useful if you
464
     * prefer to process the generated files yourself and do not want the generator to write them to disk.
465
     *
466
     * @return the list of generated Kotlin files
467
     */
468
    public List<GeneratedKotlinFile> getGeneratedKotlinFiles() {
469
        return generatedKotlinFiles;
1✔
470
    }
471

472
    /**
473
     * Returns the list of generated XML files after a call to one of the generate methods. This is useful if you prefer
474
     * to process the generated files yourself and do not want the generator to write them to disk.
475
     *
476
     * @return the list of generated XML files
477
     */
478
    public List<GeneratedXmlFile> getGeneratedXmlFiles() {
479
        return generatedXmlFiles;
1✔
480
    }
481
}
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