All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.maven.internal.xml.XmlNodeImpl Maven / Gradle / Ivy

The newest version!
/*
 * 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.internal.xml;

import javax.xml.stream.XMLStreamException;

import java.io.Serializable;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.maven.api.xml.XmlNode;

/**
 *  NOTE: remove all the util code in here when separated, this class should be pure data.
 */
public class XmlNodeImpl implements Serializable, XmlNode {
    private static final long serialVersionUID = 2567894443061173996L;

    protected final String prefix;

    protected final String namespaceUri;

    protected final String name;

    protected final String value;

    protected final Map attributes;

    protected final List children;

    protected final Object location;

    public XmlNodeImpl(String name) {
        this(name, null, null, null, null);
    }

    public XmlNodeImpl(String name, String value) {
        this(name, value, null, null, null);
    }

    public XmlNodeImpl(XmlNode from, String name) {
        this(name, from.getValue(), from.getAttributes(), from.getChildren(), from.getInputLocation());
    }

    public XmlNodeImpl(
            String name, String value, Map attributes, List children, Object location) {
        this("", "", name, value, attributes, children, location);
    }

    public XmlNodeImpl(
            String prefix,
            String namespaceUri,
            String name,
            String value,
            Map attributes,
            List children,
            Object location) {
        this.prefix = prefix == null ? "" : prefix;
        this.namespaceUri = namespaceUri == null ? "" : namespaceUri;
        this.name = Objects.requireNonNull(name);
        this.value = value;
        this.attributes = ImmutableCollections.copy(attributes);
        this.children = ImmutableCollections.copy(children);
        this.location = location;
    }

    @Override
    public XmlNode merge(XmlNode source, Boolean childMergeOverride) {
        return merge(this, source, childMergeOverride);
    }

    // ----------------------------------------------------------------------
    // Name handling
    // ----------------------------------------------------------------------

    @Override
    public String getPrefix() {
        return prefix;
    }

    @Override
    public String getNamespaceUri() {
        return namespaceUri;
    }

    @Override
    public String getName() {
        return name;
    }

    // ----------------------------------------------------------------------
    // Value handling
    // ----------------------------------------------------------------------

    public String getValue() {
        return value;
    }

    // ----------------------------------------------------------------------
    // Attribute handling
    // ----------------------------------------------------------------------

    @Override
    public Map getAttributes() {
        return attributes;
    }

    public String getAttribute(String name) {
        return attributes.get(name);
    }

    // ----------------------------------------------------------------------
    // Child handling
    // ----------------------------------------------------------------------

    public XmlNode getChild(String name) {
        if (name != null) {
            ListIterator it = children.listIterator(children.size());
            while (it.hasPrevious()) {
                XmlNode child = it.previous();
                if (name.equals(child.getName())) {
                    return child;
                }
            }
        }
        return null;
    }

    public List getChildren() {
        return children;
    }

    public int getChildCount() {
        return children.size();
    }

    // ----------------------------------------------------------------------
    // Input location handling
    // ----------------------------------------------------------------------

    /**
     * @since 3.2.0
     * @return input location
     */
    public Object getInputLocation() {
        return location;
    }

    // ----------------------------------------------------------------------
    // Helpers
    // ----------------------------------------------------------------------

    /**
     * Merges one DOM into another, given a specific algorithm and possible override points for that algorithm.

* The algorithm is as follows: *

    *
  1. if the recessive DOM is null, there is nothing to do... return.
  2. *
  3. Determine whether the dominant node will suppress the recessive one (flag=mergeSelf). *
      *
    1. retrieve the 'combine.self' attribute on the dominant node, and try to match against 'override'... * if it matches 'override', then set mergeSelf == false...the dominant node suppresses the recessive one * completely.
    2. *
    3. otherwise, use the default value for mergeSelf, which is true...this is the same as specifying * 'combine.self' == 'merge' as an attribute of the dominant root node.
    4. *
  4. *
  5. If mergeSelf == true *
      *
    1. Determine whether children from the recessive DOM will be merged or appended to the dominant DOM as * siblings (flag=mergeChildren). *
        *
      1. if childMergeOverride is set (non-null), use that value (true/false)
      2. *
      3. retrieve the 'combine.children' attribute on the dominant node, and try to match against * 'append'...
      4. *
      5. if it matches 'append', then set mergeChildren == false...the recessive children will be appended as * siblings of the dominant children.
      6. *
      7. otherwise, use the default value for mergeChildren, which is true...this is the same as specifying * 'combine.children' == 'merge' as an attribute on the dominant root node.
      8. *
    2. *
    3. Iterate through the recessive children, and: *
        *
      1. if mergeChildren == true and there is a corresponding dominant child (matched by element name), * merge the two.
      2. *
      3. otherwise, add the recessive child as a new child on the dominant root node.
      4. *
    4. *
  6. *
*/ @SuppressWarnings("checkstyle:MethodLength") public static XmlNode merge(XmlNode dominant, XmlNode recessive, Boolean childMergeOverride) { // TODO: share this as some sort of assembler, implement a walk interface? if (recessive == null) { return dominant; } if (dominant == null) { return recessive; } boolean mergeSelf = true; String selfMergeMode = dominant.getAttribute(SELF_COMBINATION_MODE_ATTRIBUTE); if (SELF_COMBINATION_OVERRIDE.equals(selfMergeMode)) { mergeSelf = false; } if (mergeSelf) { String value = dominant.getValue(); Object location = dominant.getInputLocation(); Map attrs = dominant.getAttributes(); List children = null; for (Map.Entry attr : recessive.getAttributes().entrySet()) { String key = attr.getKey(); if (isEmpty(attrs.get(key))) { if (attrs == dominant.getAttributes()) { attrs = new HashMap<>(attrs); } attrs.put(key, attr.getValue()); } } if (!recessive.getChildren().isEmpty()) { boolean mergeChildren = true; if (childMergeOverride != null) { mergeChildren = childMergeOverride; } else { String childMergeMode = attrs.get(CHILDREN_COMBINATION_MODE_ATTRIBUTE); if (CHILDREN_COMBINATION_APPEND.equals(childMergeMode)) { mergeChildren = false; } } Map> commonChildren = new HashMap<>(); Set names = recessive.getChildren().stream().map(XmlNode::getName).collect(Collectors.toSet()); for (String name : names) { List dominantChildren = dominant.getChildren().stream() .filter(n -> n.getName().equals(name)) .toList(); if (!dominantChildren.isEmpty()) { commonChildren.put(name, dominantChildren.iterator()); } } String keysValue = recessive.getAttribute(KEYS_COMBINATION_MODE_ATTRIBUTE); int recessiveChildIndex = 0; for (XmlNode recessiveChild : recessive.getChildren()) { String idValue = recessiveChild.getAttribute(ID_COMBINATION_MODE_ATTRIBUTE); XmlNode childDom = null; if (!isEmpty(idValue)) { for (XmlNode dominantChild : dominant.getChildren()) { if (idValue.equals(dominantChild.getAttribute(ID_COMBINATION_MODE_ATTRIBUTE))) { childDom = dominantChild; // we have a match, so don't append but merge mergeChildren = true; } } } else if (!isEmpty(keysValue)) { String[] keys = keysValue.split(","); Map> recessiveKeyValues = Stream.of(keys) .collect(Collectors.toMap( k -> k, k -> Optional.ofNullable(recessiveChild.getAttribute(k)))); for (XmlNode dominantChild : dominant.getChildren()) { Map> dominantKeyValues = Stream.of(keys) .collect(Collectors.toMap( k -> k, k -> Optional.ofNullable(dominantChild.getAttribute(k)))); if (recessiveKeyValues.equals(dominantKeyValues)) { childDom = dominantChild; // we have a match, so don't append but merge mergeChildren = true; } } } else { childDom = dominant.getChild(recessiveChild.getName()); } if (mergeChildren && childDom != null) { String name = recessiveChild.getName(); Iterator it = commonChildren.computeIfAbsent(name, n1 -> Stream.of(dominant.getChildren().stream() .filter(n2 -> n2.getName().equals(n1)) .collect(Collectors.toList())) .filter(l -> !l.isEmpty()) .map(List::iterator) .findFirst() .orElse(null)); if (it == null) { if (children == null) { children = new ArrayList<>(dominant.getChildren()); } children.add(recessiveChild); } else if (it.hasNext()) { XmlNode dominantChild = it.next(); String dominantChildCombinationMode = dominantChild.getAttribute(SELF_COMBINATION_MODE_ATTRIBUTE); if (SELF_COMBINATION_REMOVE.equals(dominantChildCombinationMode)) { if (children == null) { children = new ArrayList<>(dominant.getChildren()); } children.remove(dominantChild); } else { int idx = dominant.getChildren().indexOf(dominantChild); XmlNode merged = merge(dominantChild, recessiveChild, childMergeOverride); if (merged != dominantChild) { if (children == null) { children = new ArrayList<>(dominant.getChildren()); } children.set(idx, merged); } } } } else { if (children == null) { children = new ArrayList<>(dominant.getChildren()); } int idx = mergeChildren ? children.size() : recessiveChildIndex; children.add(idx, recessiveChild); } recessiveChildIndex++; } } if (value != null || attrs != dominant.getAttributes() || children != null) { if (children == null) { children = dominant.getChildren(); } if (!Objects.equals(value, dominant.getValue()) || !Objects.equals(attrs, dominant.getAttributes()) || !Objects.equals(children, dominant.getChildren()) || !Objects.equals(location, dominant.getInputLocation())) { return new XmlNodeImpl( dominant.getName(), value != null ? value : dominant.getValue(), attrs, children, location); } else { return dominant; } } } return dominant; } /** * Merge two DOMs, with one having dominance in the case of collision. Merge mechanisms (vs. override for nodes, or * vs. append for children) is determined by attributes of the dominant root node. * * @see #CHILDREN_COMBINATION_MODE_ATTRIBUTE * @see #SELF_COMBINATION_MODE_ATTRIBUTE * @param dominant The dominant DOM into which the recessive value/attributes/children will be merged * @param recessive The recessive DOM, which will be merged into the dominant DOM * @return merged DOM */ public static XmlNode merge(XmlNode dominant, XmlNode recessive) { return merge(dominant, recessive, null); } // ---------------------------------------------------------------------- // Standard object handling // ---------------------------------------------------------------------- @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } XmlNodeImpl that = (XmlNodeImpl) o; return Objects.equals(this.name, that.name) && Objects.equals(this.value, that.value) && Objects.equals(this.attributes, that.attributes) && Objects.equals(this.children, that.children); } @Override public int hashCode() { return Objects.hash(name, value, attributes, children); } @Override public String toString() { try { return toStringXml(); } catch (XMLStreamException e) { return toStringObject(); } } public String toStringXml() throws XMLStreamException { StringWriter writer = new StringWriter(); XmlNodeWriter.write(writer, this); return writer.toString(); } public String toStringObject() { StringBuilder sb = new StringBuilder(); sb.append("XmlNode["); boolean w = false; w = addToStringField(sb, prefix, o -> !o.isEmpty(), "prefix", w); w = addToStringField(sb, namespaceUri, o -> !o.isEmpty(), "namespaceUri", w); w = addToStringField(sb, name, o -> !o.isEmpty(), "name", w); w = addToStringField(sb, value, o -> !o.isEmpty(), "value", w); w = addToStringField(sb, attributes, o -> !o.isEmpty(), "attributes", w); w = addToStringField(sb, children, o -> !o.isEmpty(), "children", w); w = addToStringField(sb, location, Objects::nonNull, "location", w); sb.append("]"); return sb.toString(); } private static boolean addToStringField(StringBuilder sb, T o, Function p, String n, boolean w) { if (!p.apply(o)) { if (w) { sb.append(", "); } else { w = true; } sb.append(n).append("='").append(o).append('\''); } return w; } private static boolean isEmpty(String str) { return str == null || str.isEmpty(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy