XmlService.java
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.api.xml;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.Writer;
import java.util.ServiceLoader;
import org.apache.maven.api.annotations.Nonnull;
import org.apache.maven.api.annotations.Nullable;
/**
* Comprehensive service interface for XML operations including node creation,
* merging, reading, and writing.
*
* <p>This class provides XML merging functionality for Maven's XML handling
* and specifies the combination modes that control how XML elements are merged.</p>
*
* <p>The merger supports two main types of combinations:</p>
* <ul>
* <li>Children combination: Controls how child elements are combined</li>
* <li>Self combination: Controls how the element itself is combined</li>
* </ul>
*
* <p>Children combination modes (specified by {@code combine.children} attribute):</p>
* <ul>
* <li>{@code merge} (default): Merges elements with matching names</li>
* <li>{@code append}: Adds elements as siblings</li>
* </ul>
*
* <p>Self combination modes (specified by {@code combine.self} attribute):</p>
* <ul>
* <li>{@code merge} (default): Merges attributes and values</li>
* <li>{@code override}: Completely replaces the element</li>
* <li>{@code remove}: Removes the element</li>
* </ul>
*
* <p>For complex XML structures, combining can also be done based on:</p>
* <ul>
* <li>ID: Using the {@code combine.id} attribute</li>
* <li>Keys: Using the {@code combine.keys} attribute with comma-separated key names</li>
* </ul>
*
* @since 4.0.0
*/
public abstract class XmlService {
/** Attribute name controlling how child elements are combined */
public static final String CHILDREN_COMBINATION_MODE_ATTRIBUTE = "combine.children";
/** Value indicating children should be merged based on element names */
public static final String CHILDREN_COMBINATION_MERGE = "merge";
/** Value indicating children should be appended as siblings */
public static final String CHILDREN_COMBINATION_APPEND = "append";
/**
* Default mode for combining children DOMs during merge.
* When element names match, the process will try to merge the element data,
* rather than putting the dominant and recessive elements as siblings.
*/
public static final String DEFAULT_CHILDREN_COMBINATION_MODE = CHILDREN_COMBINATION_MERGE;
/** Attribute name controlling how the element itself is combined */
public static final String SELF_COMBINATION_MODE_ATTRIBUTE = "combine.self";
/** Value indicating the element should be completely overridden */
public static final String SELF_COMBINATION_OVERRIDE = "override";
/** Value indicating the element should be merged */
public static final String SELF_COMBINATION_MERGE = "merge";
/** Value indicating the element should be removed */
public static final String SELF_COMBINATION_REMOVE = "remove";
/**
* Default mode for combining a DOM node during merge.
* When element names match, the process will try to merge element attributes
* and values, rather than overriding the recessive element completely.
*/
public static final String DEFAULT_SELF_COMBINATION_MODE = SELF_COMBINATION_MERGE;
/** Attribute name for ID-based combination mode */
public static final String ID_COMBINATION_MODE_ATTRIBUTE = "combine.id";
/**
* Attribute name for key-based combination mode.
* Value should be a comma-separated list of attribute names.
*/
public static final String KEYS_COMBINATION_MODE_ATTRIBUTE = "combine.keys";
/**
* Convenience method to merge two XML nodes using default settings.
*/
@Nullable
public static XmlNode merge(XmlNode dominant, XmlNode recessive) {
return merge(dominant, recessive, null);
}
/**
* Merges two XML nodes.
*/
@Nullable
public static XmlNode merge(
@Nullable XmlNode dominant, @Nullable XmlNode recessive, @Nullable Boolean childMergeOverride) {
return getService().doMerge(dominant, recessive, childMergeOverride);
}
/**
* Reads an XML node from an input stream.
*/
@Nonnull
public static XmlNode read(InputStream input, @Nullable InputLocationBuilder locationBuilder)
throws XMLStreamException {
return getService().doRead(input, locationBuilder);
}
/**
* Reads an XML node from a reader.
*/
@Nonnull
public static XmlNode read(Reader reader) throws XMLStreamException {
return read(reader, null);
}
/**
* Reads an XML node from a reader.
*/
@Nonnull
public static XmlNode read(Reader reader, @Nullable InputLocationBuilder locationBuilder)
throws XMLStreamException {
return getService().doRead(reader, locationBuilder);
}
/**
* Reads an XML node from an XMLStreamReader.
*/
@Nonnull
public static XmlNode read(XMLStreamReader reader) throws XMLStreamException {
return read(reader, null);
}
/**
* Reads an XML node from an XMLStreamReader.
*/
@Nonnull
public static XmlNode read(XMLStreamReader reader, @Nullable InputLocationBuilder locationBuilder)
throws XMLStreamException {
return getService().doRead(reader, locationBuilder);
}
/**
* Writes an XML node to a writer.
*/
public static void write(XmlNode node, Writer writer) throws IOException {
getService().doWrite(node, writer);
}
/**
* Interface for building input locations during XML parsing.
*/
public interface InputLocationBuilder {
Object toInputLocation(XMLStreamReader parser);
}
/**
* Implementation method for reading an XML node from an input stream.
*
* @param input the input stream to read from
* @param locationBuilder optional builder for creating input location objects
* @return the parsed XML node
* @throws XMLStreamException if there is an error parsing the XML
*/
protected abstract XmlNode doRead(InputStream input, InputLocationBuilder locationBuilder)
throws XMLStreamException;
/**
* Implementation method for reading an XML node from a reader.
*
* @param reader the reader to read from
* @param locationBuilder optional builder for creating input location objects
* @return the parsed XML node
* @throws XMLStreamException if there is an error parsing the XML
*/
protected abstract XmlNode doRead(Reader reader, InputLocationBuilder locationBuilder) throws XMLStreamException;
/**
* Implementation method for reading an XML node from an XMLStreamReader.
*
* @param reader the XML stream reader to read from
* @param locationBuilder optional builder for creating input location objects
* @return the parsed XML node
* @throws XMLStreamException if there is an error parsing the XML
*/
protected abstract XmlNode doRead(XMLStreamReader reader, InputLocationBuilder locationBuilder)
throws XMLStreamException;
/**
* Implementation method for writing an XML node to a writer.
*
* @param node the XML node to write
* @param writer the writer to write to
* @throws IOException if there is an error writing the XML
*/
protected abstract void doWrite(XmlNode node, Writer writer) throws IOException;
/**
* Implementation method for merging two XML nodes.
*
* @param dominant the dominant (higher priority) XML node
* @param recessive the recessive (lower priority) XML node
* @param childMergeOverride optional override for the child merge mode
* @return the merged XML node, or null if both inputs are null
*/
protected abstract XmlNode doMerge(XmlNode dominant, XmlNode recessive, Boolean childMergeOverride);
/**
* Gets the singleton instance of the XmlService.
*
* @return the XmlService instance
* @throws IllegalStateException if no implementation is found
*/
private static XmlService getService() {
return Holder.INSTANCE;
}
/** Holder class for lazy initialization of the default instance */
private static final class Holder {
static final XmlService INSTANCE = ServiceLoader.load(XmlService.class)
.findFirst()
.orElseThrow(() -> new IllegalStateException("No XmlService implementation found"));
private Holder() {}
}
}