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

mybatis / generator / 1995

30 Jan 2026 09:23PM UTC coverage: 89.791% (+0.002%) from 89.789%
1995

Pull #1431

github

web-flow
Merge b3fbc6220 into 8ce80c6db
Pull Request #1431: Refactor the Mergers and Associated Tests

2251 of 3031 branches covered (74.27%)

9 of 11 new or added lines in 1 file covered. (81.82%)

11522 of 12832 relevant lines covered (89.79%)

0.9 hits per line

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

73.56
/core/mybatis-generator-core/src/main/java/org/mybatis/generator/merge/java/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.merge.java;
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 {
63
            String existingFileContent = readFileContent(existingFile, fileEncoding);
×
64
            return getMergedSource(newFileSource, existingFileContent, javadocTags);
×
65
        } catch (IOException e) {
×
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!
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!
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 =
1✔
100
                    performMerge(newCompilationUnit, existingCompilationUnit, javadocTags);
1✔
101

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

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

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

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

120
        return mergedCompilationUnit;
1✔
121
    }
122

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

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

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

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

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

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

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

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

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

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

191
        // Clear existing imports and add sorted imports
192
        mergedCompilationUnit.getImports().clear();
1✔
193

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

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

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

219
        if (existingTypeDeclaration instanceof ClassOrInterfaceDeclaration existingClassDeclaration
1!
220
                && mergedTypeDeclaration instanceof ClassOrInterfaceDeclaration mergedClassDeclaration) {
1✔
221

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

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

245
    private static String readFileContent(File file, @Nullable String fileEncoding) throws IOException {
246
        if (fileEncoding != null) {
×
247
            return Files.readString(file.toPath(), Charset.forName(fileEncoding));
×
248
        } else {
249
            return Files.readString(file.toPath(), StandardCharsets.UTF_8);
×
250
        }
251
    }
252
}
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