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

mybatis / generator / 1664

24 May 2025 05:50PM UTC coverage: 88.307% (-0.02%) from 88.328%
1664

Pull #1312

github

web-flow
Merge ab39f821e into 806759db0
Pull Request #1312: Use NIO classes to resolve modernizer issues

2518 of 3410 branches covered (73.84%)

0 of 44 new or added lines in 7 files covered. (0.0%)

3 existing lines in 2 files now uncovered.

11192 of 12674 relevant lines covered (88.31%)

0.88 hits per line

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

25.14
/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.IOException;
24
import java.io.OutputStream;
25
import java.io.OutputStreamWriter;
26
import java.nio.charset.Charset;
27
import java.nio.file.Files;
28
import java.nio.file.Path;
29
import java.nio.file.StandardOpenOption;
30
import java.sql.SQLException;
31
import java.util.ArrayList;
32
import java.util.HashSet;
33
import java.util.List;
34
import java.util.Objects;
35
import java.util.Set;
36

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

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

62
    private static final ProgressCallback NULL_PROGRESS_CALLBACK = new ProgressCallback() {
1✔
63
    };
64

65
    private final Configuration configuration;
66

67
    private final ShellCallback shellCallback;
68

69
    private final List<GeneratedJavaFile> generatedJavaFiles = new ArrayList<>();
1✔
70

71
    private final List<GeneratedXmlFile> generatedXmlFiles = new ArrayList<>();
1✔
72

73
    private final List<GeneratedKotlinFile> generatedKotlinFiles = new ArrayList<>();
1✔
74

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

80
    private final List<String> warnings;
81

82
    private final Set<String> projects = new HashSet<>();
1✔
83

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

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

114
        this.warnings = Objects.requireNonNullElseGet(warnings, ArrayList::new);
1✔
115

116
        this.configuration.validate();
1✔
117
    }
1✔
118

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

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

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

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

218
        if (callback == null) {
1!
219
            callback = NULL_PROGRESS_CALLBACK;
1✔
220
        }
221

222
        generatedJavaFiles.clear();
1✔
223
        generatedXmlFiles.clear();
1✔
224
        ObjectFactory.reset();
1✔
225
        RootClassInfo.reset();
1✔
226

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

240
        // setup custom classloader if required
241
        if (!configuration.getClassPathEntries().isEmpty()) {
1!
242
            ClassLoader classLoader = getCustomClassloader(configuration.getClassPathEntries());
×
243
            ObjectFactory.addExternalClassLoader(classLoader);
×
244
        }
245

246
        // now run the introspections...
247
        int totalSteps = 0;
1✔
248
        for (Context context : contextsToRun) {
1✔
249
            totalSteps += context.getIntrospectionSteps();
1✔
250
        }
1✔
251
        callback.introspectionStarted(totalSteps);
1✔
252

253
        for (Context context : contextsToRun) {
1✔
254
            context.introspectTables(callback, warnings,
1✔
255
                    fullyQualifiedTableNames);
256
        }
1✔
257

258
        // now run the generates
259
        totalSteps = 0;
1✔
260
        for (Context context : contextsToRun) {
1✔
261
            totalSteps += context.getGenerationSteps();
1✔
262
        }
1✔
263
        callback.generationStarted(totalSteps);
1✔
264

265
        for (Context context : contextsToRun) {
1✔
266
            context.generateFiles(callback, generatedJavaFiles,
1✔
267
                    generatedXmlFiles, generatedKotlinFiles, otherGeneratedFiles, warnings);
268
        }
1✔
269

270
        // now save the files
271
        if (writeFiles) {
1!
272
            callback.saveStarted(generatedXmlFiles.size()
×
273
                    + generatedJavaFiles.size());
×
274

275
            for (GeneratedXmlFile gxf : generatedXmlFiles) {
×
276
                projects.add(gxf.getTargetProject());
×
277
                writeGeneratedXmlFile(gxf, callback);
×
278
            }
×
279

280
            for (GeneratedJavaFile gjf : generatedJavaFiles) {
×
281
                projects.add(gjf.getTargetProject());
×
282
                writeGeneratedJavaFile(gjf, callback);
×
283
            }
×
284

285
            for (GeneratedKotlinFile gkf : generatedKotlinFiles) {
×
286
                projects.add(gkf.getTargetProject());
×
287
                writeGeneratedFile(gkf, callback);
×
288
            }
×
289

290
            for (GeneratedFile gf : otherGeneratedFiles) {
×
291
                projects.add(gf.getTargetProject());
×
292
                writeGeneratedFile(gf, callback);
×
293
            }
×
294

295
            for (String project : projects) {
×
296
                shellCallback.refreshProject(project);
×
297
            }
×
298
        }
299

300
        callback.done();
1✔
301
    }
1✔
302

303
    private void writeGeneratedJavaFile(GeneratedJavaFile gjf, ProgressCallback callback)
304
            throws InterruptedException, IOException {
305
        Path targetFile;
306
        String source;
307
        try {
308
            File directory = shellCallback.getDirectory(gjf
×
309
                    .getTargetProject(), gjf.getTargetPackage());
×
NEW
310
            targetFile = directory.toPath().resolve(gjf.getFileName());
×
NEW
311
            if (Files.exists(targetFile)) {
×
312
                if (shellCallback.isMergeSupported()) {
×
313
                    source = shellCallback.mergeJavaFile(gjf
×
NEW
314
                            .getFormattedContent(), targetFile.toFile(),
×
315
                            MergeConstants.getOldElementTags(),
×
316
                            gjf.getFileEncoding());
×
317
                } else if (shellCallback.isOverwriteEnabled()) {
×
318
                    source = gjf.getFormattedContent();
×
319
                    warnings.add(getString("Warning.11", //$NON-NLS-1$
×
NEW
320
                            targetFile.toFile().getAbsolutePath()));
×
321
                } else {
322
                    source = gjf.getFormattedContent();
×
323
                    targetFile = getUniqueFileName(directory, gjf
×
324
                            .getFileName());
×
325
                    warnings.add(getString(
×
NEW
326
                            "Warning.2", targetFile.toFile().getAbsolutePath())); //$NON-NLS-1$
×
327
                }
328
            } else {
329
                source = gjf.getFormattedContent();
×
330
            }
331

332
            callback.checkCancel();
×
333
            callback.startTask(getString(
×
NEW
334
                    "Progress.15", targetFile.toString())); //$NON-NLS-1$
×
NEW
335
            writeFile(targetFile.toFile(), source, gjf.getFileEncoding());
×
336
        } catch (ShellException e) {
×
337
            warnings.add(e.getMessage());
×
338
        }
×
339
    }
×
340

341
    private void writeGeneratedFile(GeneratedFile gf, ProgressCallback callback)
342
            throws InterruptedException, IOException {
343
        Path targetFile;
344
        String source;
345
        try {
346
            File directory = shellCallback.getDirectory(gf
×
347
                    .getTargetProject(), gf.getTargetPackage());
×
NEW
348
            targetFile = directory.toPath().resolve(gf.getFileName());
×
NEW
349
            if (Files.exists(targetFile)) {
×
350
                if (shellCallback.isOverwriteEnabled()) {
×
351
                    source = gf.getFormattedContent();
×
352
                    warnings.add(getString("Warning.11", //$NON-NLS-1$
×
NEW
353
                            targetFile.toFile().getAbsolutePath()));
×
354
                } else {
355
                    source = gf.getFormattedContent();
×
356
                    targetFile = getUniqueFileName(directory, gf
×
357
                            .getFileName());
×
358
                    warnings.add(getString(
×
NEW
359
                            "Warning.2", targetFile.toFile().getAbsolutePath())); //$NON-NLS-1$
×
360
                }
361
            } else {
362
                source = gf.getFormattedContent();
×
363
            }
364

365
            callback.checkCancel();
×
366
            callback.startTask(getString(
×
NEW
367
                    "Progress.15", targetFile.toString())); //$NON-NLS-1$
×
NEW
368
            writeFile(targetFile.toFile(), source, gf.getFileEncoding());
×
369
        } catch (ShellException e) {
×
370
            warnings.add(e.getMessage());
×
371
        }
×
372
    }
×
373

374
    private void writeGeneratedXmlFile(GeneratedXmlFile gxf, ProgressCallback callback)
375
            throws InterruptedException, IOException {
376
        Path targetFile;
377
        String source;
378
        try {
379
            File directory = shellCallback.getDirectory(gxf
×
380
                    .getTargetProject(), gxf.getTargetPackage());
×
NEW
381
            targetFile = directory.toPath().resolve(gxf.getFileName());
×
NEW
382
            if (Files.exists(targetFile)) {
×
383
                if (gxf.isMergeable()) {
×
384
                    source = XmlFileMergerJaxp.getMergedSource(gxf,
×
NEW
385
                            targetFile.toFile());
×
386
                } else if (shellCallback.isOverwriteEnabled()) {
×
387
                    source = gxf.getFormattedContent();
×
388
                    warnings.add(getString("Warning.11", //$NON-NLS-1$
×
NEW
389
                            targetFile.toFile().getAbsolutePath()));
×
390
                } else {
391
                    source = gxf.getFormattedContent();
×
392
                    targetFile = getUniqueFileName(directory, gxf
×
393
                            .getFileName());
×
394
                    warnings.add(getString(
×
NEW
395
                            "Warning.2", targetFile.toFile().getAbsolutePath())); //$NON-NLS-1$
×
396
                }
397
            } else {
398
                source = gxf.getFormattedContent();
×
399
            }
400

401
            callback.checkCancel();
×
402
            callback.startTask(getString(
×
NEW
403
                    "Progress.15", targetFile.toString())); //$NON-NLS-1$
×
NEW
404
            writeFile(targetFile.toFile(), source, gxf.getFileEncoding());
×
405
        } catch (ShellException e) {
×
406
            warnings.add(e.getMessage());
×
407
        }
×
408
    }
×
409

410
    /**
411
     * Writes, or overwrites, the contents of the specified file.
412
     *
413
     * @param file
414
     *            the file
415
     * @param content
416
     *            the content
417
     * @param fileEncoding
418
     *            the file encoding
419
     * @throws IOException
420
     *             Signals that an I/O exception has occurred.
421
     */
422
    private void writeFile(File file, String content, String fileEncoding) throws IOException {
NEW
423
        try (OutputStream fos = Files.newOutputStream(file.toPath(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
×
424
            OutputStreamWriter osw;
425
            if (fileEncoding == null) {
×
426
                osw = new OutputStreamWriter(fos);
×
427
            } else {
428
                osw = new OutputStreamWriter(fos, Charset.forName(fileEncoding));
×
429
            }
430

431
            try (BufferedWriter bw = new BufferedWriter(osw)) {
×
432
                bw.write(content);
×
433
            }
434
        }
435
    }
×
436

437
    /**
438
     * Gets the unique file name.
439
     *
440
     * @param directory
441
     *            the directory
442
     * @param fileName
443
     *            the file name
444
     * @return the unique file name
445
     */
446
    private Path getUniqueFileName(File directory, String fileName) {
NEW
447
        Path answer = null;
×
448

449
        // try up to 1000 times to generate a unique file name
450
        StringBuilder sb = new StringBuilder();
×
451
        for (int i = 1; i < 1000; i++) {
×
452
            sb.setLength(0);
×
453
            sb.append(fileName);
×
454
            sb.append('.');
×
455
            sb.append(i);
×
456

NEW
457
            Path testFile = directory.toPath().resolve(sb.toString());
×
NEW
458
            if (Files.notExists(testFile)) {
×
459
                answer = testFile;
×
460
                break;
×
461
            }
462
        }
463

464
        if (answer == null) {
×
465
            throw new RuntimeException(getString(
×
466
                    "RuntimeError.3", directory.getAbsolutePath())); //$NON-NLS-1$
×
467
        }
468

469
        return answer;
×
470
    }
471

472
    /**
473
     * Returns the list of generated Java files after a call to one of the generate methods.
474
     * This is useful if you prefer to process the generated files yourself and do not want
475
     * the generator to write them to disk.
476
     *
477
     * @return the list of generated Java files
478
     */
479
    public List<GeneratedJavaFile> getGeneratedJavaFiles() {
480
        return generatedJavaFiles;
1✔
481
    }
482

483
    /**
484
     * Returns the list of generated Kotlin files after a call to one of the generate methods.
485
     * This is useful if you prefer to process the generated files yourself and do not want
486
     * the generator to write them to disk.
487
     *
488
     * @return the list of generated Kotlin files
489
     */
490
    public List<GeneratedKotlinFile> getGeneratedKotlinFiles() {
491
        return generatedKotlinFiles;
1✔
492
    }
493

494
    /**
495
     * Returns the list of generated XML files after a call to one of the generate methods.
496
     * This is useful if you prefer to process the generated files yourself and do not want
497
     * the generator to write them to disk.
498
     *
499
     * @return the list of generated XML files
500
     */
501
    public List<GeneratedXmlFile> getGeneratedXmlFiles() {
502
        return generatedXmlFiles;
1✔
503
    }
504
}
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

© 2025 Coveralls, Inc