View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.internal.xml;
20  
21  import java.util.ArrayList;
22  import java.util.HashMap;
23  import java.util.List;
24  import java.util.Map;
25  
26  import org.apache.maven.api.xml.XmlNode;
27  import org.codehaus.plexus.configuration.PlexusConfiguration;
28  
29  /**
30   * A PlexusConfiguration implementation that wraps an XmlNode instead of copying its entire hierarchy.
31   * This provides better performance by avoiding deep copying of the XML structure.
32   *
33   * <p>This implementation supports both read and write operations. When write operations are performed,
34   * new XmlNode instances are created to maintain immutability, and internal caches are cleared.</p>
35   */
36  public class XmlPlexusConfiguration implements PlexusConfiguration {
37      private XmlNode xmlNode;
38      private PlexusConfiguration[] childrenCache;
39  
40      public static PlexusConfiguration toPlexusConfiguration(XmlNode node) {
41          return new XmlPlexusConfiguration(node);
42      }
43  
44      public XmlPlexusConfiguration(XmlNode xmlNode) {
45          this.xmlNode = xmlNode;
46      }
47  
48      /**
49       * Clears the internal cache when the XML structure is modified.
50       */
51      private synchronized void clearCache() {
52          this.childrenCache = null;
53      }
54  
55      /**
56       * Converts a PlexusConfiguration to an XmlNode.
57       */
58      private XmlNode convertToXmlNode(PlexusConfiguration config) {
59          // Convert attributes
60          Map<String, String> attributes = new HashMap<>();
61          for (String attrName : config.getAttributeNames()) {
62              String attrValue = config.getAttribute(attrName);
63              if (attrValue != null) {
64                  attributes.put(attrName, attrValue);
65              }
66          }
67  
68          // Convert children
69          List<XmlNode> children = new ArrayList<>();
70          for (PlexusConfiguration child : config.getChildren()) {
71              children.add(convertToXmlNode(child));
72          }
73  
74          return XmlNode.newInstance(config.getName(), config.getValue(), attributes, children, null);
75      }
76  
77      @Override
78      public String getName() {
79          return xmlNode.name();
80      }
81  
82      public synchronized void setName(String name) {
83          this.xmlNode = XmlNode.newBuilder()
84                  .name(name)
85                  .value(xmlNode.value())
86                  .attributes(xmlNode.attributes())
87                  .children(xmlNode.children())
88                  .namespaceUri(xmlNode.namespaceUri())
89                  .prefix(xmlNode.prefix())
90                  .inputLocation(xmlNode.inputLocation())
91                  .build();
92          clearCache();
93      }
94  
95      public String getValue() {
96          return xmlNode.value();
97      }
98  
99      public String getValue(String defaultValue) {
100         String value = xmlNode.value();
101         return value != null ? value : defaultValue;
102     }
103 
104     public synchronized void setValue(String value) {
105         this.xmlNode = XmlNode.newBuilder()
106                 .name(xmlNode.name())
107                 .value(value)
108                 .attributes(xmlNode.attributes())
109                 .children(xmlNode.children())
110                 .namespaceUri(xmlNode.namespaceUri())
111                 .prefix(xmlNode.prefix())
112                 .inputLocation(xmlNode.inputLocation())
113                 .build();
114         clearCache();
115     }
116 
117     public PlexusConfiguration setValueAndGetSelf(String value) {
118         setValue(value);
119         return this;
120     }
121 
122     public synchronized void setAttribute(String name, String value) {
123         Map<String, String> newAttributes = new HashMap<>(xmlNode.attributes());
124         if (value == null) {
125             newAttributes.remove(name);
126         } else {
127             newAttributes.put(name, value);
128         }
129         this.xmlNode = XmlNode.newBuilder()
130                 .name(xmlNode.name())
131                 .value(xmlNode.value())
132                 .attributes(newAttributes)
133                 .children(xmlNode.children())
134                 .namespaceUri(xmlNode.namespaceUri())
135                 .prefix(xmlNode.prefix())
136                 .inputLocation(xmlNode.inputLocation())
137                 .build();
138         clearCache();
139     }
140 
141     public String[] getAttributeNames() {
142         return xmlNode.attributes().keySet().toArray(new String[0]);
143     }
144 
145     public String getAttribute(String paramName) {
146         return xmlNode.attribute(paramName);
147     }
148 
149     public String getAttribute(String name, String defaultValue) {
150         String value = xmlNode.attribute(name);
151         return value != null ? value : defaultValue;
152     }
153 
154     public PlexusConfiguration getChild(String child) {
155         XmlNode childNode = xmlNode.child(child);
156         if (childNode != null) {
157             return new XmlPlexusConfiguration(childNode);
158         } else {
159             // Return an empty configuration object to match DefaultPlexusConfiguration behavior
160             XmlNode emptyNode = XmlNode.newInstance(child, null, null, null, null);
161             return new XmlPlexusConfiguration(emptyNode);
162         }
163     }
164 
165     public PlexusConfiguration getChild(int i) {
166         List<XmlNode> children = xmlNode.children();
167         if (i >= 0 && i < children.size()) {
168             return new XmlPlexusConfiguration(children.get(i));
169         }
170         return null;
171     }
172 
173     public synchronized PlexusConfiguration getChild(String child, boolean createChild) {
174         XmlNode childNode = xmlNode.child(child);
175         if (childNode == null) {
176             if (createChild) {
177                 // Create a new child node
178                 XmlNode newChild = XmlNode.newInstance(child);
179                 List<XmlNode> newChildren = new ArrayList<>(xmlNode.children());
180                 newChildren.add(newChild);
181 
182                 this.xmlNode = XmlNode.newBuilder()
183                         .name(xmlNode.name())
184                         .value(xmlNode.value())
185                         .attributes(xmlNode.attributes())
186                         .children(newChildren)
187                         .namespaceUri(xmlNode.namespaceUri())
188                         .prefix(xmlNode.prefix())
189                         .inputLocation(xmlNode.inputLocation())
190                         .build();
191                 clearCache();
192 
193                 return new XmlPlexusConfiguration(newChild);
194             } else {
195                 return null; // Return null when child doesn't exist and createChild=false
196             }
197         }
198         return new XmlPlexusConfiguration(childNode);
199     }
200 
201     public synchronized PlexusConfiguration[] getChildren() {
202         if (childrenCache == null) {
203             List<XmlNode> children = xmlNode.children();
204             childrenCache = new PlexusConfiguration[children.size()];
205             for (int i = 0; i < children.size(); i++) {
206                 childrenCache[i] = new XmlPlexusConfiguration(children.get(i));
207             }
208         }
209         return childrenCache.clone();
210     }
211 
212     public PlexusConfiguration[] getChildren(String name) {
213         List<PlexusConfiguration> result = new ArrayList<>();
214         for (XmlNode child : xmlNode.children()) {
215             if (name.equals(child.name())) {
216                 result.add(new XmlPlexusConfiguration(child));
217             }
218         }
219         return result.toArray(new PlexusConfiguration[0]);
220     }
221 
222     public synchronized void addChild(PlexusConfiguration configuration) {
223         // Convert PlexusConfiguration to XmlNode
224         XmlNode newChild = convertToXmlNode(configuration);
225         List<XmlNode> newChildren = new ArrayList<>(xmlNode.children());
226         newChildren.add(newChild);
227 
228         this.xmlNode = XmlNode.newBuilder()
229                 .name(xmlNode.name())
230                 .value(xmlNode.value())
231                 .attributes(xmlNode.attributes())
232                 .children(newChildren)
233                 .namespaceUri(xmlNode.namespaceUri())
234                 .prefix(xmlNode.prefix())
235                 .inputLocation(xmlNode.inputLocation())
236                 .build();
237         clearCache();
238     }
239 
240     public synchronized PlexusConfiguration addChild(String name) {
241         XmlNode newChild = XmlNode.newInstance(name);
242         List<XmlNode> newChildren = new ArrayList<>(xmlNode.children());
243         newChildren.add(newChild);
244 
245         this.xmlNode = XmlNode.newBuilder()
246                 .name(xmlNode.name())
247                 .value(xmlNode.value())
248                 .attributes(xmlNode.attributes())
249                 .children(newChildren)
250                 .namespaceUri(xmlNode.namespaceUri())
251                 .prefix(xmlNode.prefix())
252                 .inputLocation(xmlNode.inputLocation())
253                 .build();
254         clearCache();
255 
256         return new XmlPlexusConfiguration(newChild);
257     }
258 
259     public synchronized PlexusConfiguration addChild(String name, String value) {
260         XmlNode newChild = XmlNode.newInstance(name, value);
261         List<XmlNode> newChildren = new ArrayList<>(xmlNode.children());
262         newChildren.add(newChild);
263 
264         this.xmlNode = XmlNode.newBuilder()
265                 .name(xmlNode.name())
266                 .value(xmlNode.value())
267                 .attributes(xmlNode.attributes())
268                 .children(newChildren)
269                 .namespaceUri(xmlNode.namespaceUri())
270                 .prefix(xmlNode.prefix())
271                 .inputLocation(xmlNode.inputLocation())
272                 .build();
273         clearCache();
274 
275         return new XmlPlexusConfiguration(newChild);
276     }
277 
278     public int getChildCount() {
279         return xmlNode.children().size();
280     }
281 
282     public String toString() {
283         final StringBuilder buf = new StringBuilder().append('<').append(getName());
284         for (final String a : getAttributeNames()) {
285             buf.append(' ').append(a).append("=\"").append(getAttribute(a)).append('"');
286         }
287         if (getChildCount() > 0) {
288             buf.append('>');
289             for (int i = 0, size = getChildCount(); i < size; i++) {
290                 buf.append(getChild(i));
291             }
292             buf.append("</").append(getName()).append('>');
293         } else if (null != getValue()) {
294             buf.append('>').append(getValue()).append("</").append(getName()).append('>');
295         } else {
296             buf.append("/>");
297         }
298         return buf.append('\n').toString();
299     }
300 }