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

devonfw / IDEasy / 22345446363

24 Feb 2026 09:49AM UTC coverage: 70.247% (-0.2%) from 70.474%
22345446363

Pull #1714

github

web-flow
Merge 5655b6589 into 379acdc9d
Pull Request #1714: #404: #1713: advanced logging

4065 of 6384 branches covered (63.67%)

Branch coverage included in aggregate %.

10597 of 14488 relevant lines covered (73.14%)

3.08 hits per line

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

85.71
cli/src/main/java/com/devonfw/tools/ide/merge/xml/matcher/IdComputer.java
1
package com.devonfw.tools.ide.merge.xml.matcher;
2

3
import java.nio.file.Path;
4
import javax.xml.xpath.XPath;
5
import javax.xml.xpath.XPathConstants;
6
import javax.xml.xpath.XPathExpression;
7
import javax.xml.xpath.XPathExpressionException;
8
import javax.xml.xpath.XPathFactory;
9

10
import org.slf4j.Logger;
11
import org.slf4j.LoggerFactory;
12
import org.w3c.dom.Element;
13
import org.w3c.dom.NodeList;
14

15
import com.devonfw.tools.ide.context.IdeContext;
16
import com.devonfw.tools.ide.merge.xml.XmlMergeSupport;
17

18
/**
19
 * The IdComputer class is responsible for building XPath expressions and evaluating those expressions to match elements in a target document.
20
 */
21
public class IdComputer {
22

23
  private static final Logger LOG = LoggerFactory.getLogger(IdComputer.class);
3✔
24

25
  /** Name of the {@link com.devonfw.tools.ide.environment.EnvironmentVariables variable} to fail on ambiguous merge. */
26
  public static final String FAIL_ON_AMBIGOUS_MERGE = "FAIL_ON_AMBIGOUS_MERGE";
27

28
  /** The value of merge:id that is used to evaluate the xpath expression. */
29
  private final String id;
30

31
  private final IdeContext context;
32

33
  private static final XPathFactory xPathFactory = XPathFactory.newInstance();
3✔
34

35
  /**
36
   * The constructor.
37
   *
38
   * @param id the {@link #getId() merge ID}.
39
   */
40
  public IdComputer(String id, IdeContext context) {
41

42
    super();
2✔
43
    this.id = id;
3✔
44
    this.context = context;
3✔
45
  }
1✔
46

47
  /**
48
   * @return the value of "merge:id" attribute what is an {@link XPath} expression.
49
   * @see XmlMergeSupport#getMergeId(Element)
50
   */
51
  public String getId() {
52

53
    return this.id;
×
54
  }
55

56
  /**
57
   * Evaluates the XPath expression for the given merge element in the target element.
58
   *
59
   * @param templateElement the template {@link Element} for which to build the {@link XPath} expression.
60
   * @param workspaceElement the workspace {@link Element} in which to evaluate the {@link XPath} expression.
61
   * @param templatePath the {@link Path} to the template XML file.
62
   * @param workspacePath the {@link Path} to the workspace XML file.
63
   * @return the matched Element if found, or {@code null} if not found
64
   */
65
  public Element evaluateExpression(Element templateElement, Element workspaceElement, Path templatePath, Path workspacePath) {
66
    XPath xpath = xPathFactory.newXPath();
3✔
67
    xpath.setNamespaceContext(new NamespaceContextFromElement(templateElement));
6✔
68
    String xpathExpr = buildXPathExpression(templateElement);
4✔
69
    try {
70
      XPathExpression xpathExpression = xpath.compile(xpathExpr);
4✔
71
      NodeList nodeList = (NodeList) xpathExpression.evaluate(workspaceElement, XPathConstants.NODESET);
6✔
72
      int length = nodeList.getLength();
3✔
73
      if (length == 1) {
3✔
74
        return (Element) nodeList.item(0);
5✔
75
      } else if (length == 0) {
2✔
76
        return null;
2✔
77
      } else {
78
        String message =
6✔
79
            length + " matches found for XPath " + xpathExpr + " in workspace XML file '" + workspacePath + "' at " + XmlMergeSupport.getXPath(workspaceElement,
5✔
80
                true) + " for template file '" + templatePath + "'";
81
        if ("true".equals(this.context.getVariables().get(FAIL_ON_AMBIGOUS_MERGE))) {
8✔
82
          throw new IllegalStateException(message);
5✔
83
        } else {
84
          LOG.warn(message);
3✔
85
        }
86
        return (Element) nodeList.item(0);
5✔
87
      }
88
    } catch (XPathExpressionException e) {
×
89
      throw new IllegalStateException("Failed to compile XPath expression " + xpath, e);
×
90
    }
91
  }
92

93
  /**
94
   * Builds the XPath expression for the given merge element based on the {@link #getId()} merge:id} value.
95
   *
96
   * @param element the {@link Element} for which to build the XPath expression
97
   * @return the XPath expression as a {@link String}.
98
   */
99
  private String buildXPathExpression(Element element) {
100

101
    String namespaceURI = element.getNamespaceURI();
3✔
102
    String localName = element.getLocalName();
3✔
103
    if (localName == null) {
2!
104
      localName = element.getTagName();
×
105
    }
106
    String prefix = element.getPrefix();
3✔
107

108
    StringBuilder xpathBuilder = new StringBuilder(localName.length());
6✔
109
    if ((prefix != null) && !prefix.isEmpty()) {
5!
110
      xpathBuilder.append(prefix).append(":");
6✔
111
    }
112
    xpathBuilder.append(localName);
4✔
113
    if (this.id.startsWith("@")) {
5✔
114
      String attributeName = this.id.substring(1);
5✔
115
      String attributeValue = element.getAttribute(attributeName);
4✔
116
      xpathBuilder.append('[').append(this.id).append("='").append(XmlMergeSupport.escapeSingleQuotes(attributeValue)).append("']");
14✔
117
    } else if (this.id.equals(XmlMergeSupport.XPATH_ELEMENT_NAME)) {
6✔
118
      xpathBuilder.append("[local-name()='").append(localName).append("']");
8✔
119
      if ((namespaceURI != null) && !namespaceURI.isEmpty()) {
2!
120
        xpathBuilder.append(" and namespace-uri()='").append(namespaceURI).append('\'');
×
121
      }
122
    } else if (this.id.equals(XmlMergeSupport.XPATH_ELEMENT_TEXT)) {
5✔
123
      String textContent = element.getTextContent();
3✔
124
      xpathBuilder.append("[text()='").append(XmlMergeSupport.escapeSingleQuotes(textContent)).append("']");
9✔
125
    } else { // custom xpath like ../element[@attr='value']
1✔
126
      return this.id;
3✔
127
    }
128
    return xpathBuilder.toString();
3✔
129
  }
130

131
}
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