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

mybatis / generator / 1990

28 Jan 2026 08:49PM UTC coverage: 89.789% (-0.2%) from 89.986%
1990

push

github

web-flow
Merge pull request #1344 from DanielLiu1123/feat-merge-mapper

Support Java file merge

2251 of 3031 branches covered (74.27%)

61 of 99 new or added lines in 4 files covered. (61.62%)

11519 of 12829 relevant lines covered (89.79%)

0.9 hits per line

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

72.62
/core/mybatis-generator-core/src/main/java/org/mybatis/generator/internal/JavaFileMerger.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.internal;
17

18
import static org.mybatis.generator.internal.util.messages.Messages.getString;
19

20
import java.io.File;
21
import java.io.IOException;
22
import java.nio.charset.Charset;
23
import java.nio.charset.StandardCharsets;
24
import java.nio.file.Files;
25
import java.util.LinkedHashSet;
26
import java.util.Set;
27

28
import com.github.javaparser.JavaParser;
29
import com.github.javaparser.ParseResult;
30
import com.github.javaparser.ast.CompilationUnit;
31
import com.github.javaparser.ast.ImportDeclaration;
32
import com.github.javaparser.ast.body.BodyDeclaration;
33
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
34
import com.github.javaparser.ast.body.TypeDeclaration;
35
import com.github.javaparser.ast.expr.AnnotationExpr;
36
import org.jspecify.annotations.Nullable;
37
import org.mybatis.generator.exception.ShellException;
38

39
/**
40
 * This class handles the task of merging changes into an existing Java file using JavaParser.
41
 * It supports merging by removing methods and fields that have specific JavaDoc tags or annotations.
42
 *
43
 * @author Freeman
44
 */
45
public class JavaFileMerger {
46

47
    private JavaFileMerger() {
48
    }
49

50
    /**
51
     * Merge a newly generated Java file with an existing Java file.
52
     *
53
     * @param newFileSource the source of the newly generated Java file
54
     * @param existingFile  the existing Java file
55
     * @param javadocTags   the JavaDoc tags that denote which methods and fields in the old file to delete
56
     * @param fileEncoding  the file encoding for reading existing Java files
57
     * @return the merged source, properly formatted
58
     * @throws ShellException if the file cannot be merged for some reason
59
     */
60
    public static String getMergedSource(String newFileSource, File existingFile,
61
                                         String[] javadocTags, @Nullable String fileEncoding) throws ShellException {
62
        try {
NEW
63
            String existingFileContent = readFileContent(existingFile, fileEncoding);
×
NEW
64
            return getMergedSource(newFileSource, existingFileContent, javadocTags);
×
NEW
65
        } catch (IOException e) {
×
NEW
66
            throw new ShellException(getString("Warning.13", existingFile.getName()), e);
×
67
        }
68
    }
69

70
    /**
71
     * Merge a newly generated Java file with existing Java file content.
72
     *
73
     * @param newFileSource       the source of the newly generated Java file
74
     * @param existingFileContent the content of the existing Java file
75
     * @param javadocTags         the JavaDoc tags that denote which methods and fields in the old file to delete
76
     * @return the merged source, properly formatted
77
     * @throws ShellException if the file cannot be merged for some reason
78
     */
79
    public static String getMergedSource(String newFileSource, String existingFileContent,
80
                                         String[] javadocTags) throws ShellException {
81
        try {
82
            JavaParser javaParser = new JavaParser();
1✔
83

84
            // Parse the new file
85
            ParseResult<CompilationUnit> newParseResult = javaParser.parse(newFileSource);
1✔
86
            if (!newParseResult.isSuccessful()) {
1!
NEW
87
                throw new ShellException("Failed to parse new Java file: " + newParseResult.getProblems());
×
88
            }
89
            CompilationUnit newCompilationUnit = newParseResult.getResult().orElseThrow();
1✔
90

91
            // Parse the existing file
92
            ParseResult<CompilationUnit> existingParseResult = javaParser.parse(existingFileContent);
1✔
93
            if (!existingParseResult.isSuccessful()) {
1!
NEW
94
                throw new ShellException("Failed to parse existing Java file: " + existingParseResult.getProblems());
×
95
            }
96
            CompilationUnit existingCompilationUnit = existingParseResult.getResult().orElseThrow();
1✔
97

98
            // Perform the merge
99
            CompilationUnit mergedCompilationUnit = performMerge(newCompilationUnit, existingCompilationUnit, javadocTags);
1✔
100

101
            return mergedCompilationUnit.toString();
1✔
NEW
102
        } catch (Exception e) {
×
NEW
103
            throw new ShellException("Error merging Java files: " + e.getMessage(), e);
×
104
        }
105
    }
106

107
    private static CompilationUnit performMerge(CompilationUnit newCompilationUnit,
108
                                                CompilationUnit existingCompilationUnit,
109
                                                String[] javadocTags) {
110
        // Start with the new compilation unit as the base (to get new generated elements first)
111
        CompilationUnit mergedCompilationUnit = newCompilationUnit.clone();
1✔
112

113
        // Merge imports
114
        mergeImports(existingCompilationUnit, mergedCompilationUnit);
1✔
115

116
        // Add preserved (non-generated) elements from existing file at the end
117
        addPreservedElements(existingCompilationUnit, mergedCompilationUnit, javadocTags);
1✔
118

119
        return mergedCompilationUnit;
1✔
120
    }
121

122
    private static boolean isGeneratedElement(BodyDeclaration<?> member, String[] javadocTags) {
123
        return hasGeneratedAnnotation(member) || hasGeneratedJavadocTag(member, javadocTags);
1✔
124
    }
125

126
    private static boolean hasGeneratedAnnotation(BodyDeclaration<?> member) {
127
        for (AnnotationExpr annotation : member.getAnnotations()) {
1✔
128
            String annotationName = annotation.getNameAsString();
1✔
129
            // Check for @Generated annotation (both javax and jakarta packages)
130
            if ("Generated".equals(annotationName) ||
1!
NEW
131
                "javax.annotation.Generated".equals(annotationName) ||
×
NEW
132
                "jakarta.annotation.Generated".equals(annotationName)) {
×
133
                return true;
1✔
134
            }
NEW
135
        }
×
136
        return false;
1✔
137
    }
138

139
    private static boolean hasGeneratedJavadocTag(BodyDeclaration<?> member, String[] javadocTags) {
140
        // Check if the member has a comment and if it contains any of the javadoc tags
141
        if (member.getComment().isPresent()) {
1✔
142
            String commentContent = member.getComment().orElseThrow().getContent();
1✔
143
            for (String tag : javadocTags) {
1✔
144
                if (commentContent.contains(tag)) {
1✔
145
                    return true;
1✔
146
                }
147
            }
148
        }
149
        return false;
1✔
150
    }
151

152
    private static void mergeImports(CompilationUnit existingCompilationUnit,
153
                                     CompilationUnit mergedCompilationUnit) {
154
        record ImportInfo(String name, boolean isStatic, boolean isAsterisk) implements Comparable<ImportInfo> {
1✔
155
            @Override
156
            public int compareTo(ImportInfo other) {
157
                // Static imports come last
158
                if (this.isStatic != other.isStatic) {
1!
NEW
159
                    return this.isStatic ? 1 : -1;
×
160
                }
161

162
                // Within the same category (static or non-static), sort by import order priority
163
                int priorityThis = getImportPriority(this.name);
1✔
164
                int priorityOther = getImportPriority(other.name);
1✔
165

166
                if (priorityThis != priorityOther) {
1!
NEW
167
                    return Integer.compare(priorityThis, priorityOther);
×
168
                }
169

170
                // Within the same priority, use natural ordering (case-insensitive)
171
                return String.CASE_INSENSITIVE_ORDER.compare(this.name, other.name);
1✔
172
            }
173
        }
174

175
        // Collect all imports from both compilation units
176
        Set<ImportInfo> allImports = new LinkedHashSet<>();
1✔
177

178
        // Add imports from new file
179
        for (ImportDeclaration importDecl : mergedCompilationUnit.getImports()) {
1✔
180
            allImports.add(new ImportInfo(importDecl.getNameAsString(), importDecl.isStatic(), importDecl.isAsterisk()));
1✔
181
        }
1✔
182

183
        // Add imports from existing file (avoiding duplicates)
184
        for (ImportDeclaration importDecl : existingCompilationUnit.getImports()) {
1✔
185
            allImports.add(new ImportInfo(importDecl.getNameAsString(), importDecl.isStatic(), importDecl.isAsterisk()));
1✔
186
        }
1✔
187

188
        // Clear existing imports and add sorted imports
189
        mergedCompilationUnit.getImports().clear();
1✔
190

191
        // Sort imports according to best practices and add them back
192
        allImports.stream()
1✔
193
                .sorted()
1✔
194
                .forEach(importInfo -> mergedCompilationUnit.addImport(
1✔
195
                        importInfo.name(), importInfo.isStatic(), importInfo.isAsterisk()));
1✔
196
    }
1✔
197

198
    private static int getImportPriority(String importName) {
199
        if (importName.startsWith("java.")) {
1!
200
            return 10;
1✔
NEW
201
        } else if (importName.startsWith("javax.")) {
×
NEW
202
            return 20;
×
NEW
203
        } else if (importName.startsWith("jakarta.")) {
×
NEW
204
            return 30;
×
205
        } else {
NEW
206
            return 40; // Third-party and project imports
×
207
        }
208
    }
209

210
    private static void addPreservedElements(CompilationUnit existingCompilationUnit, CompilationUnit mergedCompilationUnit, String[] javadocTags) {
211
        // Find the main type declarations
212
        TypeDeclaration<?> existingTypeDeclaration = findMainTypeDeclaration(existingCompilationUnit);
1✔
213
        TypeDeclaration<?> mergedTypeDeclaration = findMainTypeDeclaration(mergedCompilationUnit);
1✔
214

215
        if (existingTypeDeclaration instanceof ClassOrInterfaceDeclaration existingClassDeclaration &&
1!
216
            mergedTypeDeclaration instanceof ClassOrInterfaceDeclaration mergedClassDeclaration) {
1✔
217

218
            // Add only non-generated members from the existing class to the end of merged class
219
            for (BodyDeclaration<?> member : existingClassDeclaration.getMembers()) {
1✔
220
                if (!isGeneratedElement(member, javadocTags)) {
1✔
221
                    mergedClassDeclaration.addMember(member.clone());
1✔
222
                }
223
            }
1✔
224
        }
225
    }
1✔
226

227
    private static TypeDeclaration<?> findMainTypeDeclaration(CompilationUnit compilationUnit) {
228
        // Return the first public type declaration, or the first type declaration if no public one exists
229
        TypeDeclaration<?> firstType = null;
1✔
230
        for (TypeDeclaration<?> typeDeclaration : compilationUnit.getTypes()) {
1!
231
            if (firstType == null) {
1!
232
                firstType = typeDeclaration;
1✔
233
            }
234
            if (typeDeclaration.isPublic()) {
1!
235
                return typeDeclaration;
1✔
236
            }
NEW
237
        }
×
NEW
238
        return firstType;
×
239
    }
240

241
    private static String readFileContent(File file, @Nullable String fileEncoding) throws IOException {
NEW
242
        if (fileEncoding != null) {
×
NEW
243
            return Files.readString(file.toPath(), Charset.forName(fileEncoding));
×
244
        } else {
NEW
245
            return Files.readString(file.toPath(), StandardCharsets.UTF_8);
×
246
        }
247
    }
248
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc