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

mybatis / generator / 2212

14 May 2026 03:01PM UTC coverage: 91.803% (-0.04%) from 91.84%
2212

Pull #1514

github

web-flow
Merge f807ae416 into 66161b118
Pull Request #1514: Allow Configuration for the Java Merger

2519 of 3223 branches covered (78.16%)

311 of 325 new or added lines in 24 files covered. (95.69%)

11 existing lines in 4 files now uncovered.

12208 of 13298 relevant lines covered (91.8%)

0.92 hits per line

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

93.89
/core/mybatis-generator-core/src/main/java/org/mybatis/generator/merge/java/JavaMergeUtilities.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.util.ArrayList;
21
import java.util.Collections;
22
import java.util.List;
23
import java.util.stream.Collectors;
24

25
import com.github.javaparser.JavaParser;
26
import com.github.javaparser.ParseResult;
27
import com.github.javaparser.Problem;
28
import com.github.javaparser.ast.CompilationUnit;
29
import com.github.javaparser.ast.ImportDeclaration;
30
import com.github.javaparser.ast.body.BodyDeclaration;
31
import com.github.javaparser.ast.body.FieldDeclaration;
32
import com.github.javaparser.ast.body.TypeDeclaration;
33
import com.github.javaparser.ast.body.VariableDeclarator;
34
import com.github.javaparser.ast.comments.Comment;
35
import com.github.javaparser.ast.expr.AnnotationExpr;
36
import com.github.javaparser.ast.expr.Expression;
37
import com.github.javaparser.ast.expr.MemberValuePair;
38
import com.github.javaparser.ast.expr.StringLiteralExpr;
39
import com.github.javaparser.ast.nodeTypes.NodeWithSimpleName;
40
import com.github.javaparser.ast.type.ClassOrInterfaceType;
41
import org.jspecify.annotations.Nullable;
42
import org.mybatis.generator.api.MyBatisGenerator;
43
import org.mybatis.generator.config.MergeConstants;
44
import org.mybatis.generator.exception.MergeException;
45

46
public class JavaMergeUtilities {
47
    private JavaMergeUtilities() {
48
        // utility class, no instances
49
    }
50

51
    /**
52
     * Compare two compilation units and find imports that are in the existing file but not in the new file.
53
     * We assume this means they are required for some custom method, so we will add them to the new
54
     * file if there are other items to merge. This may create unused imports in the new file if the
55
     * initial assumption is incorrect, but better safe than sorry.
56
     *
57
     * @param existingCompilationUnit compilation unit representing the existing file
58
     * @param newCompilationUnit compilation unit representing the new file
59
     */
60
    public static List<ImportDeclaration> findCustomImports(CompilationUnit existingCompilationUnit,
61
                                                            CompilationUnit newCompilationUnit) {
62
        List<ImportDeclaration> customImports = new ArrayList<>();
1✔
63

64
        List<String> newFileImports = newCompilationUnit.getImports().stream()
1✔
65
                .map(JavaMergeUtilities::stringify).toList();
1✔
66

67
        for (ImportDeclaration id : existingCompilationUnit.getImports()) {
1✔
68
            if (!newFileImports.contains(stringify(id))) {
1✔
69
                customImports.add(id);
1✔
70
            }
71
        }
1✔
72

73
        return customImports;
1✔
74
    }
75

76
    /**
77
     * Compare two members to see if they are "functionally equivalent". This is defined as:
78
     *
79
     * <ul>
80
     *     <li>Members are the same type</li>
81
     *     <li>Members have the same signature or basic declaration</li>
82
     * </ul>
83
     *
84
     * @param member1 the first member
85
     * @param member2 the second member
86
     * @return true if the members are functionally equivalent
87
     */
88
    private static boolean membersMatch(BodyDeclaration<?> member1, BodyDeclaration<?> member2) {
89
        if (member1.isTypeDeclaration() && member2.isTypeDeclaration()) {
1✔
90
            return member1.asTypeDeclaration().getNameAsString()
1✔
91
                    .equals(member2.asTypeDeclaration().getNameAsString());
1✔
92
        } else if (member1.isCallableDeclaration() && member2.isCallableDeclaration()) {
1✔
93
            return member1.asCallableDeclaration().getSignature().asString()
1✔
94
                    .equals(member2.asCallableDeclaration().getSignature().asString());
1✔
95
        } else if (member1.isFieldDeclaration() && member2.isFieldDeclaration()) {
1✔
96
            return stringify(member1.asFieldDeclaration()).equals(stringify(member2.asFieldDeclaration()));
1✔
97
        }
98

99
        return false;
1✔
100
    }
101

102
    public static List<ClassOrInterfaceType> findCustomSuperInterfaces(BodyDeclaration<?> existingType,
103
                                                                       BodyDeclaration<?> newType) {
104
        List<ClassOrInterfaceType> customSuperInterfaces = new ArrayList<>();
1✔
105

106
        List<String> newFileSuperInterfaces = findSuperInterfaces(newType).stream()
1✔
107
                .map(NodeWithSimpleName::getNameAsString).toList();
1✔
108

109
        for (ClassOrInterfaceType id : findSuperInterfaces(existingType)) {
1✔
110
            if (!newFileSuperInterfaces.contains(id.getNameAsString())) {
1✔
111
                customSuperInterfaces.add(id);
1✔
112
            }
113
        }
1✔
114

115
        return customSuperInterfaces;
1✔
116
    }
117

118
    private static List<ClassOrInterfaceType> findSuperInterfaces(BodyDeclaration<?> bodyDeclaration) {
119
        if (bodyDeclaration.isClassOrInterfaceDeclaration()) {
1✔
120
            return bodyDeclaration.asClassOrInterfaceDeclaration().getImplementedTypes();
1✔
121
        } else if (bodyDeclaration.isEnumDeclaration()) {
1✔
122
            return bodyDeclaration.asEnumDeclaration().getImplementedTypes();
1✔
123
        } else if (bodyDeclaration.isRecordDeclaration()) {
1!
124
            return bodyDeclaration.asRecordDeclaration().getImplementedTypes();
1✔
125
        }
126

127
        return Collections.emptyList();
×
128
    }
129

130
    public static void addSuperInterface(BodyDeclaration<?> bodyDeclaration, ClassOrInterfaceType superInterface) {
131
        if (bodyDeclaration.isClassOrInterfaceDeclaration()) {
1✔
132
            bodyDeclaration.asClassOrInterfaceDeclaration().addImplementedType(superInterface);
1✔
133
        } else if (bodyDeclaration.isEnumDeclaration()) {
1!
134
            bodyDeclaration.asEnumDeclaration().addImplementedType(superInterface);
×
135
        } else if (bodyDeclaration.isRecordDeclaration()) {
1!
136
            bodyDeclaration.asRecordDeclaration().addImplementedType(superInterface);
1✔
137
        }
138
    }
1✔
139

140
    /**
141
     * Create a string representation of an import that we can use to find matches.
142
     *
143
     * @param importDeclaration the import declaration to stringify
144
     * @return string representation of the import (not a full import statement)
145
     */
146
    private static String stringify(ImportDeclaration importDeclaration) {
147
        StringBuilder sb = new StringBuilder();
1✔
148
        if (importDeclaration.isStatic()) {
1✔
149
            sb.append("static "); //$NON-NLS-1$
1✔
150
        }
151
        if (importDeclaration.isModule()) {
1!
152
            sb.append("module "); //$NON-NLS-1$
×
153
        }
154
        sb.append(importDeclaration.getNameAsString());
1✔
155
        if (importDeclaration.isAsterisk()) {
1!
156
            sb.append(".*"); //$NON-NLS-1$
×
157
        }
158

159
        return sb.toString();
1✔
160
    }
161

162
    private static String stringify(FieldDeclaration fieldDeclaration) {
163
        return fieldDeclaration.getVariables().stream()
1✔
164
                .map(JavaMergeUtilities::stringify)
1✔
165
                .collect(Collectors.joining(",")); //$NON-NLS-1$
1✔
166
    }
167

168
    private static String stringify(VariableDeclarator variableDeclarator) {
169
        return variableDeclarator.getType().toString()
1✔
170
                + " " //$NON-NLS-1$
171
                + variableDeclarator.getName().toString();
1✔
172
    }
173

174
    public static GeneratedType checkForGeneratedAnnotation(BodyDeclaration<?> member) {
175
        return member.getAnnotations().stream()
1✔
176
                .filter(JavaMergeUtilities::isOurGeneratedAnnotation)
1✔
177
                .findFirst()
1✔
178
                .map(a -> {
1✔
179
                    if (hasDoNotDeleteComment(a)) {
1✔
180
                        return GeneratedType.GENERATED_KEEP;
1✔
181
                    } else {
182
                        return GeneratedType.GENERATED_REMOVE;
1✔
183
                    }
184
                })
185
                .orElse(GeneratedType.NOT_GENERATED);
1✔
186
    }
187

188
    private static boolean isOurGeneratedAnnotation(AnnotationExpr annotationExpr) {
189
        if (!isGeneratedAnnotation(annotationExpr)) {
1✔
190
            return false;
1✔
191
        }
192

193
        if (annotationExpr.isSingleMemberAnnotationExpr()) {
1✔
194
            Expression value = annotationExpr.asSingleMemberAnnotationExpr().getMemberValue();
1✔
195
            if (value.isStringLiteralExpr()) {
1!
196
                return annotationValueMatchesMyBatisGenerator(value.asStringLiteralExpr());
1✔
197
            }
198
        } else if (annotationExpr.isNormalAnnotationExpr()) {
1!
199
            return annotationExpr.asNormalAnnotationExpr().getPairs().stream()
1✔
200
                    .filter(JavaMergeUtilities::isValuePair)
1✔
201
                    .map(MemberValuePair::getValue)
1✔
202
                    .filter(Expression::isStringLiteralExpr)
1✔
203
                    .map(Expression::asStringLiteralExpr)
1✔
204
                    .findFirst()
1✔
205
                    .map(JavaMergeUtilities::annotationValueMatchesMyBatisGenerator)
1✔
206
                    .orElse(false);
1✔
207
        }
208

NEW
209
        return false;
×
210
    }
211

212
    private static boolean hasDoNotDeleteComment(AnnotationExpr annotationExpr) {
213
        // check the comments value for the do_not_delete marker string
214
        if (annotationExpr.isSingleMemberAnnotationExpr()) {
1✔
215
            // no comments in a single member annotation - only the single "value" member"
216
            return false;
1✔
217
        } else if (annotationExpr.isNormalAnnotationExpr()) {
1!
218
            return annotationExpr.asNormalAnnotationExpr().getPairs().stream()
1✔
219
                    .filter(JavaMergeUtilities::isCommentsPair)
1✔
220
                    .map(MemberValuePair::getValue)
1✔
221
                    .filter(Expression::isStringLiteralExpr)
1✔
222
                    .map(Expression::asStringLiteralExpr)
1✔
223
                    .findFirst()
1✔
224
                    .map(StringLiteralExpr::asString)
1✔
225
                    .map(s -> s.contains(MergeConstants.DO_NOT_DELETE_DURING_MERGE))
1✔
226
                    .orElse(false);
1✔
227
        }
228

NEW
229
        return false;
×
230
    }
231

232
    private static boolean isGeneratedAnnotation(AnnotationExpr annotationExpr) {
233
        String annotationName = annotationExpr.getNameAsString();
1✔
234
        // Check for @Generated annotation (both javax and jakarta packages)
235
        return "Generated".equals(annotationName) //$NON-NLS-1$
1✔
236
                || "javax.annotation.Generated".equals(annotationName) //$NON-NLS-1$
1!
237
                || "jakarta.annotation.Generated".equals(annotationName); //$NON-NLS-1$
1!
238
    }
239

240
    private static boolean isValuePair(MemberValuePair pair) {
241
        return pair.getName().asString().equals("value"); //$NON-NLS-1$
1✔
242
    }
243

244
    private static boolean isCommentsPair(MemberValuePair pair) {
245
        return pair.getName().asString().equals("comments"); //$NON-NLS-1$
1✔
246
    }
247

248
    private static boolean annotationValueMatchesMyBatisGenerator(StringLiteralExpr expr) {
249
        return expr.asString().equals(MyBatisGenerator.class.getName());
1✔
250
    }
251

252
    public static GeneratedType checkForGeneratedJavadocTag(BodyDeclaration<?> member) {
253
        return member.getComment()
1✔
254
                .map(Comment::getContent)
1✔
255
                .map(JavaMergeUtilities::checkJavadocTag)
1✔
256
                .orElse(GeneratedType.NOT_GENERATED);
1✔
257
    }
258

259
    // Check if the comment contains any of the javadoc tags
260
    private static GeneratedType checkJavadocTag(String comment) {
261
        for (String tag : MergeConstants.getOldElementTags()) {
1✔
262
            if (comment.contains(tag)) {
1✔
263
                if (comment.contains(MergeConstants.DO_NOT_DELETE_DURING_MERGE)) {
1✔
264
                    return GeneratedType.GENERATED_KEEP;
1✔
265
                } else {
266
                    return GeneratedType.GENERATED_REMOVE;
1✔
267
                }
268
            }
269
        }
270
        return GeneratedType.NOT_GENERATED;
1✔
271
    }
272

273
    private static TypeDeclaration<?> findMainTypeDeclaration(CompilationUnit compilationUnit,
274
                                                              MergeFileType mergeFileType) throws MergeException {
275
        // Return the first public type declaration, or the first type declaration if no public one exists
276
        TypeDeclaration<?> firstType = null;
1✔
277
        for (TypeDeclaration<?> typeDeclaration : compilationUnit.getTypes()) {
1✔
278
            if (firstType == null) {
1!
279
                firstType = typeDeclaration;
1✔
280
            }
281
            if (typeDeclaration.isPublic()) {
1!
282
                return typeDeclaration;
1✔
283
            }
NEW
284
        }
×
285
        if (firstType == null) {
1!
286
            throw new MergeException(getString("RuntimeError.29", mergeFileType.toString())); //$NON-NLS-1$
1✔
287
        }
NEW
288
        return firstType;
×
289
    }
290

291
    public static ParseResults parseAndFindMainTypeDeclaration(JavaParser javaParser, String source,
292
                                                               MergeFileType mergeFileType) throws MergeException {
293
        ParseResult<CompilationUnit> parseResult = javaParser.parse(source);
1✔
294

295
        // little hack to pull the result out of the lambda. This allows us to avoid "orElseThrow()" later on
296
        @Nullable CompilationUnit[] compilationUnits = new CompilationUnit [1];
1✔
297
        parseResult.ifSuccessful(cu -> compilationUnits[0] = cu);
1✔
298

299
        if (compilationUnits[0] == null) {
1✔
300
            List<String> details = parseResult.getProblems().stream()
1✔
301
                    .map(Problem::toString)
1✔
302
                    .toList();
1✔
303
            throw new MergeException(getString("RuntimeError.28", mergeFileType.toString()), details); //$NON-NLS-1$
1✔
304
        }
305

306
        return new ParseResults(compilationUnits[0], findMainTypeDeclaration(compilationUnits[0], mergeFileType));
1✔
307
    }
308

309
    public static void deleteDuplicateMemberIfExists(TypeDeclaration<?> newTypeDeclaration, BodyDeclaration<?> member) {
310
        newTypeDeclaration.getMembers().stream()
1✔
311
                .filter(td -> membersMatch(td, member))
1✔
312
                .findFirst()
1✔
313
                .ifPresent(newTypeDeclaration::remove);
1✔
314
    }
1✔
315
}
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