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.api.xml;
20  
21  import javax.xml.stream.XMLStreamException;
22  import javax.xml.stream.XMLStreamReader;
23  
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.io.Reader;
27  import java.io.Writer;
28  import java.util.ServiceLoader;
29  
30  import org.apache.maven.api.annotations.Nonnull;
31  import org.apache.maven.api.annotations.Nullable;
32  
33  /**
34   * Comprehensive service interface for XML operations including node creation,
35   * merging, reading, and writing.
36   *
37   * <p>This class provides XML merging functionality for Maven's XML handling
38   * and specifies the combination modes that control how XML elements are merged.</p>
39   *
40   * <p>The merger supports two main types of combinations:</p>
41   * <ul>
42   *   <li>Children combination: Controls how child elements are combined</li>
43   *   <li>Self combination: Controls how the element itself is combined</li>
44   * </ul>
45   *
46   * <p>Children combination modes (specified by {@code combine.children} attribute):</p>
47   * <ul>
48   *   <li>{@code merge} (default): Merges elements with matching names</li>
49   *   <li>{@code append}: Adds elements as siblings</li>
50   * </ul>
51   *
52   * <p>Self combination modes (specified by {@code combine.self} attribute):</p>
53   * <ul>
54   *   <li>{@code merge} (default): Merges attributes and values</li>
55   *   <li>{@code override}: Completely replaces the element</li>
56   *   <li>{@code remove}: Removes the element</li>
57   * </ul>
58   *
59   * <p>For complex XML structures, combining can also be done based on:</p>
60   * <ul>
61   *   <li>ID: Using the {@code combine.id} attribute</li>
62   *   <li>Keys: Using the {@code combine.keys} attribute with comma-separated key names</li>
63   * </ul>
64   *
65   * @since 4.0.0
66   */
67  public abstract class XmlService {
68  
69      /** Attribute name controlling how child elements are combined */
70      public static final String CHILDREN_COMBINATION_MODE_ATTRIBUTE = "combine.children";
71      /** Value indicating children should be merged based on element names */
72      public static final String CHILDREN_COMBINATION_MERGE = "merge";
73      /** Value indicating children should be appended as siblings */
74      public static final String CHILDREN_COMBINATION_APPEND = "append";
75      /**
76       * Default mode for combining children DOMs during merge.
77       * When element names match, the process will try to merge the element data,
78       * rather than putting the dominant and recessive elements as siblings.
79       */
80      public static final String DEFAULT_CHILDREN_COMBINATION_MODE = CHILDREN_COMBINATION_MERGE;
81  
82      /** Attribute name controlling how the element itself is combined */
83      public static final String SELF_COMBINATION_MODE_ATTRIBUTE = "combine.self";
84      /** Value indicating the element should be completely overridden */
85      public static final String SELF_COMBINATION_OVERRIDE = "override";
86      /** Value indicating the element should be merged */
87      public static final String SELF_COMBINATION_MERGE = "merge";
88      /** Value indicating the element should be removed */
89      public static final String SELF_COMBINATION_REMOVE = "remove";
90      /**
91       * Default mode for combining a DOM node during merge.
92       * When element names match, the process will try to merge element attributes
93       * and values, rather than overriding the recessive element completely.
94       */
95      public static final String DEFAULT_SELF_COMBINATION_MODE = SELF_COMBINATION_MERGE;
96  
97      /** Attribute name for ID-based combination mode */
98      public static final String ID_COMBINATION_MODE_ATTRIBUTE = "combine.id";
99      /**
100      * Attribute name for key-based combination mode.
101      * Value should be a comma-separated list of attribute names.
102      */
103     public static final String KEYS_COMBINATION_MODE_ATTRIBUTE = "combine.keys";
104 
105     /**
106      * Convenience method to merge two XML nodes using default settings.
107      */
108     @Nullable
109     public static XmlNode merge(XmlNode dominant, XmlNode recessive) {
110         return merge(dominant, recessive, null);
111     }
112 
113     /**
114      * Merges two XML nodes.
115      */
116     @Nullable
117     public static XmlNode merge(
118             @Nullable XmlNode dominant, @Nullable XmlNode recessive, @Nullable Boolean childMergeOverride) {
119         return getService().doMerge(dominant, recessive, childMergeOverride);
120     }
121 
122     /**
123      * Reads an XML node from an input stream.
124      */
125     @Nonnull
126     public static XmlNode read(InputStream input, @Nullable InputLocationBuilder locationBuilder)
127             throws XMLStreamException {
128         return getService().doRead(input, locationBuilder);
129     }
130 
131     /**
132      * Reads an XML node from a reader.
133      */
134     @Nonnull
135     public static XmlNode read(Reader reader) throws XMLStreamException {
136         return read(reader, null);
137     }
138 
139     /**
140      * Reads an XML node from a reader.
141      */
142     @Nonnull
143     public static XmlNode read(Reader reader, @Nullable InputLocationBuilder locationBuilder)
144             throws XMLStreamException {
145         return getService().doRead(reader, locationBuilder);
146     }
147 
148     /**
149      * Reads an XML node from an XMLStreamReader.
150      */
151     @Nonnull
152     public static XmlNode read(XMLStreamReader reader) throws XMLStreamException {
153         return read(reader, null);
154     }
155 
156     /**
157      * Reads an XML node from an XMLStreamReader.
158      */
159     @Nonnull
160     public static XmlNode read(XMLStreamReader reader, @Nullable InputLocationBuilder locationBuilder)
161             throws XMLStreamException {
162         return getService().doRead(reader, locationBuilder);
163     }
164 
165     /**
166      * Writes an XML node to a writer.
167      */
168     public static void write(XmlNode node, Writer writer) throws IOException {
169         getService().doWrite(node, writer);
170     }
171 
172     /**
173      * Interface for building input locations during XML parsing.
174      */
175     public interface InputLocationBuilder {
176         Object toInputLocation(XMLStreamReader parser);
177     }
178 
179     /**
180      * Implementation method for reading an XML node from an input stream.
181      *
182      * @param input the input stream to read from
183      * @param locationBuilder optional builder for creating input location objects
184      * @return the parsed XML node
185      * @throws XMLStreamException if there is an error parsing the XML
186      */
187     protected abstract XmlNode doRead(InputStream input, InputLocationBuilder locationBuilder)
188             throws XMLStreamException;
189 
190     /**
191      * Implementation method for reading an XML node from a reader.
192      *
193      * @param reader the reader to read from
194      * @param locationBuilder optional builder for creating input location objects
195      * @return the parsed XML node
196      * @throws XMLStreamException if there is an error parsing the XML
197      */
198     protected abstract XmlNode doRead(Reader reader, InputLocationBuilder locationBuilder) throws XMLStreamException;
199 
200     /**
201      * Implementation method for reading an XML node from an XMLStreamReader.
202      *
203      * @param reader the XML stream reader to read from
204      * @param locationBuilder optional builder for creating input location objects
205      * @return the parsed XML node
206      * @throws XMLStreamException if there is an error parsing the XML
207      */
208     protected abstract XmlNode doRead(XMLStreamReader reader, InputLocationBuilder locationBuilder)
209             throws XMLStreamException;
210 
211     /**
212      * Implementation method for writing an XML node to a writer.
213      *
214      * @param node the XML node to write
215      * @param writer the writer to write to
216      * @throws IOException if there is an error writing the XML
217      */
218     protected abstract void doWrite(XmlNode node, Writer writer) throws IOException;
219 
220     /**
221      * Implementation method for merging two XML nodes.
222      *
223      * @param dominant the dominant (higher priority) XML node
224      * @param recessive the recessive (lower priority) XML node
225      * @param childMergeOverride optional override for the child merge mode
226      * @return the merged XML node, or null if both inputs are null
227      */
228     protected abstract XmlNode doMerge(XmlNode dominant, XmlNode recessive, Boolean childMergeOverride);
229 
230     /**
231      * Gets the singleton instance of the XmlService.
232      *
233      * @return the XmlService instance
234      * @throws IllegalStateException if no implementation is found
235      */
236     private static XmlService getService() {
237         return Holder.INSTANCE;
238     }
239 
240     /** Holder class for lazy initialization of the default instance */
241     private static final class Holder {
242         static final XmlService INSTANCE = ServiceLoader.load(XmlService.class)
243                 .findFirst()
244                 .orElseThrow(() -> new IllegalStateException("No XmlService implementation found"));
245 
246         private Holder() {}
247     }
248 }