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

mybatis / generator / 1942

12 Jan 2026 05:01PM UTC coverage: 88.75% (+0.4%) from 88.365%
1942

push

github

web-flow
Merge pull request #1412 from jeffgbutler/jspecify

Adopt JSpecify

2331 of 3162 branches covered (73.72%)

1800 of 1949 new or added lines in 202 files covered. (92.36%)

18 existing lines in 10 files now uncovered.

11384 of 12827 relevant lines covered (88.75%)

0.89 hits per line

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

90.0
/core/mybatis-generator-core/src/main/java/org/mybatis/generator/internal/XmlFileMergerJaxp.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.io.InputStreamReader;
23
import java.io.StringReader;
24
import java.nio.charset.StandardCharsets;
25
import java.nio.file.Files;
26
import java.util.ArrayList;
27
import java.util.List;
28
import javax.xml.XMLConstants;
29
import javax.xml.parsers.DocumentBuilder;
30
import javax.xml.parsers.DocumentBuilderFactory;
31
import javax.xml.parsers.ParserConfigurationException;
32

33
import org.jspecify.annotations.Nullable;
34
import org.mybatis.generator.api.GeneratedXmlFile;
35
import org.mybatis.generator.config.MergeConstants;
36
import org.mybatis.generator.exception.ShellException;
37
import org.w3c.dom.Comment;
38
import org.w3c.dom.Document;
39
import org.w3c.dom.DocumentType;
40
import org.w3c.dom.Element;
41
import org.w3c.dom.NamedNodeMap;
42
import org.w3c.dom.Node;
43
import org.w3c.dom.NodeList;
44
import org.w3c.dom.Text;
45
import org.xml.sax.EntityResolver;
46
import org.xml.sax.InputSource;
47
import org.xml.sax.SAXException;
48

49
/**
50
 * This class handles the task of merging changes into an existing XML file.
51
 *
52
 * @author Jeff Butler
53
 */
54
public class XmlFileMergerJaxp {
55
    private XmlFileMergerJaxp() {
56
    }
57

58
    private static class NullEntityResolver implements EntityResolver {
59
        /**
60
         * returns an empty reader. This is done so that the parser doesn't
61
         * attempt to read a DTD. We don't need that support for the merge, and
62
         * it can cause problems on systems that aren't Internet connected.
63
         */
64
        @Override
65
        public InputSource resolveEntity(String publicId, String systemId) {
66

67
            StringReader sr = new StringReader(""); //$NON-NLS-1$
1✔
68

69
            return new InputSource(sr);
1✔
70
        }
71
    }
72

73
    public static String getMergedSource(GeneratedXmlFile generatedXmlFile, File existingFile) throws ShellException {
74

75
        try {
76
            return getMergedSource(new InputSource(new StringReader(generatedXmlFile.getFormattedContent())),
×
77
                new InputSource(new InputStreamReader(
NEW
78
                        Files.newInputStream(existingFile.toPath()), StandardCharsets.UTF_8)),
×
79
                existingFile.getName());
×
80
        } catch (IOException | SAXException | ParserConfigurationException e) {
×
81
            throw new ShellException(getString("Warning.13", //$NON-NLS-1$
×
82
                    existingFile.getName()), e);
×
83
        }
84
    }
85

86
    public static String getMergedSource(InputSource newFile,
87
            InputSource existingFile, String existingFileName) throws IOException, SAXException,
88
            ParserConfigurationException, ShellException {
89

90
        DocumentBuilderFactory factory = DocumentBuilderFactory
91
                .newInstance();
1✔
92
        factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
1✔
93
        factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
1✔
94
        factory.setExpandEntityReferences(false);
1✔
95
        factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
1✔
96
        DocumentBuilder builder = factory.newDocumentBuilder();
1✔
97
        builder.setEntityResolver(new NullEntityResolver());
1✔
98

99
        Document existingDocument = builder.parse(existingFile);
1✔
100
        Document newDocument = builder.parse(newFile);
1✔
101

102
        DocumentType newDocType = newDocument.getDoctype();
1✔
103
        DocumentType existingDocType = existingDocument.getDoctype();
1✔
104

105
        if (!newDocType.getName().equals(existingDocType.getName())) {
1!
106
            throw new ShellException(getString("Warning.12", //$NON-NLS-1$
×
107
                    existingFileName));
108
        }
109

110
        Element existingRootElement = existingDocument.getDocumentElement();
1✔
111
        Element newRootElement = newDocument.getDocumentElement();
1✔
112

113
        // reconcile the root element attributes -
114
        // take all attributes from the new element and add to the existing
115
        // element
116

117
        // remove all attributes from the existing root element
118
        NamedNodeMap attributes = existingRootElement.getAttributes();
1✔
119
        int attributeCount = attributes.getLength();
1✔
120
        for (int i = attributeCount - 1; i >= 0; i--) {
1✔
121
            Node node = attributes.item(i);
1✔
122
            existingRootElement.removeAttribute(node.getNodeName());
1✔
123
        }
124

125
        // add attributes from the new root node to the old root node
126
        attributes = newRootElement.getAttributes();
1✔
127
        attributeCount = attributes.getLength();
1✔
128
        for (int i = 0; i < attributeCount; i++) {
1✔
129
            Node node = attributes.item(i);
1✔
130
            existingRootElement.setAttribute(node.getNodeName(), node
1✔
131
                    .getNodeValue());
1✔
132
        }
133

134
        // remove the old generated elements and any
135
        // white space before the old nodes
136
        List<Node> nodesToDelete = new ArrayList<>();
1✔
137
        NodeList children = existingRootElement.getChildNodes();
1✔
138
        int length = children.getLength();
1✔
139
        for (int i = 0; i < length; i++) {
1✔
140
            Node node = children.item(i);
1✔
141
            if (isGeneratedNode(node)) {
1✔
142
                nodesToDelete.add(node);
1✔
143
            } else if (isWhiteSpace(node)
1✔
144
                    && isGeneratedNode(children.item(i + 1))) {
1✔
145
                nodesToDelete.add(node);
1✔
146
            }
147
        }
148

149
        for (Node node : nodesToDelete) {
1✔
150
            existingRootElement.removeChild(node);
1✔
151
        }
1✔
152

153
        // add the new generated elements
154
        children = newRootElement.getChildNodes();
1✔
155
        length = children.getLength();
1✔
156
        Node firstChild = existingRootElement.getFirstChild();
1✔
157
        for (int i = 0; i < length; i++) {
1!
158
            Node node = children.item(i);
1✔
159
            // don't add the last node if it is only white space
160
            if (i == length - 1 && isWhiteSpace(node)) {
1!
161
                break;
1✔
162
            }
163

164
            Node newNode = existingDocument.importNode(node, true);
1✔
165
            if (firstChild == null) {
1!
166
                existingRootElement.appendChild(newNode);
×
167
            } else {
168
                existingRootElement.insertBefore(newNode, firstChild);
1✔
169
            }
170
        }
171

172
        // pretty print the result
173
        return prettyPrint(existingDocument);
1✔
174
    }
175

176
    private static String prettyPrint(Document document) throws ShellException {
177
        return new DomWriter(document).getFormattedDocument();
1✔
178
    }
179

180
    private static boolean isGeneratedNode(@Nullable Node node) {
181
        return node != null
1✔
182
                && node.getNodeType() == Node.ELEMENT_NODE
1✔
183
                && (isOldFormatNode(node) || isNewFormatNode(node));
1✔
184
    }
185

186
    private static boolean isOldFormatNode(Node node) {
187
        Element element = (Element) node;
1✔
188
        String id = element.getAttribute("id"); //$NON-NLS-1$
1✔
189
        return MergeConstants.idStartsWithPrefix(id);
1✔
190

191
    }
192

193
    private static boolean isNewFormatNode(Node node) {
194
        // check for new node format - if the first non-whitespace node
195
        // is an XML comment, and the comment includes
196
        // one of the old element tags,
197
        // then it is a generated node
198
        NodeList children = node.getChildNodes();
1✔
199
        int length = children.getLength();
1✔
200
        for (int i = 0; i < length; i++) {
1✔
201
            Node childNode = children.item(i);
1✔
202
            if (childNode != null && childNode.getNodeType() == Node.COMMENT_NODE) {
1!
203
                String commentData = ((Comment) childNode).getData();
1✔
204
                return MergeConstants.commentContainsTag(commentData);
1✔
205
            }
206
        }
207

208
        return false;
1✔
209
    }
210

211
    private static boolean isWhiteSpace(Node node) {
212
        boolean rc = false;
1✔
213

214
        if (node.getNodeType() == Node.TEXT_NODE) {
1✔
215
            Text tn = (Text) node;
1✔
216
            if (tn.getData().trim().isEmpty()) {
1!
217
                rc = true;
1✔
218
            }
219
        }
220

221
        return rc;
1✔
222
    }
223
}
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