XMLListReference.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.configuration2;

  18. import java.util.List;
  19. import java.util.Map;
  20. import java.util.stream.Collectors;

  21. import org.apache.commons.configuration2.convert.ListDelimiterHandler;
  22. import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;
  23. import org.apache.commons.configuration2.tree.ImmutableNode;
  24. import org.apache.commons.configuration2.tree.ReferenceNodeHandler;
  25. import org.apache.commons.lang3.StringUtils;
  26. import org.w3c.dom.Element;

  27. /**
  28.  * <p>
  29.  * An internal class implementing list handling functionality for {@link XMLConfiguration}.
  30.  * </p>
  31.  * <p>
  32.  * When an XML document is loaded list properties defined as a string with multiple values separated by the list
  33.  * delimiter are split into multiple configuration nodes. When the configuration is saved the original format should be
  34.  * kept if possible. This class implements functionality to achieve this. Instances are used as references associated
  35.  * with configuration nodes so that the original format can be restored when the configuration is saved.
  36.  * </p>
  37.  */
  38. final class XMLListReference {
  39.     /**
  40.      * Assigns an instance of this class as reference to the specified configuration node. This reference acts as a marker
  41.      * indicating that this node is subject to extended list handling.
  42.      *
  43.      * @param refs the mapping for node references
  44.      * @param node the affected configuration node
  45.      * @param elem the current XML element
  46.      */
  47.     public static void assignListReference(final Map<ImmutableNode, Object> refs, final ImmutableNode node, final Element elem) {
  48.         if (refs != null) {
  49.             refs.put(node, new XMLListReference(elem));
  50.         }
  51.     }

  52.     /**
  53.      * Checks whether the specified node has an associated list reference. This marks the node as part of a list.
  54.      *
  55.      * @param node the node to be checked
  56.      * @param handler the reference handler
  57.      * @return a flag whether this node has a list reference
  58.      */
  59.     private static boolean hasListReference(final ImmutableNode node, final ReferenceNodeHandler handler) {
  60.         return handler.getReference(node) instanceof XMLListReference;
  61.     }

  62.     /**
  63.      * Checks whether the specified node is the first node of a list. This is needed because all items of the list are
  64.      * collected and stored as value of the first list node. Note: This method requires that the passed in node is a list
  65.      * node, so {@link #isListNode(ImmutableNode, ReferenceNodeHandler)} must have returned <strong>true</strong> for it.
  66.      *
  67.      * @param node the configuration node
  68.      * @param handler the reference node handler
  69.      * @return a flag whether this is the first node of a list
  70.      */
  71.     public static boolean isFirstListItem(final ImmutableNode node, final ReferenceNodeHandler handler) {
  72.         final ImmutableNode parent = handler.getParent(node);
  73.         ImmutableNode firstItem = null;
  74.         int idx = 0;
  75.         while (firstItem == null) {
  76.             final ImmutableNode child = handler.getChild(parent, idx);
  77.             if (nameEquals(node, child)) {
  78.                 firstItem = child;
  79.             }
  80.             idx++;
  81.         }
  82.         return firstItem == node;
  83.     }

  84.     /**
  85.      * Checks whether the specified configuration node has to be taken into account for list handling. This is the case if
  86.      * the node's parent has at least one child node with the same name which has a special list reference assigned. (Note
  87.      * that the passed in node does not necessarily have such a reference; if it has been added at a later point in time, it
  88.      * also has to become an item of the list.)
  89.      *
  90.      * @param node the configuration node
  91.      * @param handler the reference node handler
  92.      * @return a flag whether this node is relevant for list handling
  93.      */
  94.     public static boolean isListNode(final ImmutableNode node, final ReferenceNodeHandler handler) {
  95.         if (hasListReference(node, handler)) {
  96.             return true;
  97.         }

  98.         final ImmutableNode parent = handler.getParent(node);
  99.         if (parent != null) {
  100.             for (int i = 0; i < handler.getChildrenCount(parent, null); i++) {
  101.                 final ImmutableNode child = handler.getChild(parent, i);
  102.                 if (hasListReference(child, handler) && nameEquals(node, child)) {
  103.                     return true;
  104.                 }
  105.             }
  106.         }
  107.         return false;
  108.     }

  109.     /**
  110.      * Constructs the concatenated string value of all items comprising the list the specified node belongs to. This method
  111.      * is called when saving an {@link XMLConfiguration}. Then configuration nodes created for list items have to be
  112.      * collected again and transformed into a string defining all list elements.
  113.      *
  114.      * @param node the configuration node
  115.      * @param nodeHandler the reference node handler
  116.      * @param delimiterHandler the list delimiter handler of the configuration
  117.      * @return a string with all values of the current list
  118.      * @throws ConfigurationRuntimeException if the list delimiter handler does not support the transformation of list items
  119.      *         to a string
  120.      */
  121.     public static String listValue(final ImmutableNode node, final ReferenceNodeHandler nodeHandler, final ListDelimiterHandler delimiterHandler) {
  122.         // cannot be null if the current node is a list node
  123.         final ImmutableNode parent = nodeHandler.getParent(node);
  124.         final List<ImmutableNode> items = nodeHandler.getChildren(parent, node.getNodeName());
  125.         final List<Object> values = items.stream().map(ImmutableNode::getValue).collect(Collectors.toList());
  126.         try {
  127.             return String.valueOf(delimiterHandler.escapeList(values, ListDelimiterHandler.NOOP_TRANSFORMER));
  128.         } catch (final UnsupportedOperationException e) {
  129.             throw new ConfigurationRuntimeException("List handling not supported by " + "the current ListDelimiterHandler! Make sure that the same delimiter "
  130.                     + "handler is used for loading and saving the configuration.", e);
  131.         }
  132.     }

  133.     /**
  134.      * Helper method for comparing the names of two nodes.
  135.      *
  136.      * @param n1 node 1
  137.      * @param n2 node 2
  138.      * @return a flag whether these nodes have equal names
  139.      */
  140.     private static boolean nameEquals(final ImmutableNode n1, final ImmutableNode n2) {
  141.         return StringUtils.equals(n2.getNodeName(), n1.getNodeName());
  142.     }

  143.     /** The wrapped XML element. */
  144.     private final Element element;

  145.     /**
  146.      * Private constructor. No instances can be created from other classes.
  147.      *
  148.      * @param e the associated element
  149.      */
  150.     private XMLListReference(final Element e) {
  151.         element = e;
  152.     }

  153.     /**
  154.      * Gets the associated element.
  155.      *
  156.      * @return the associated XML element
  157.      */
  158.     public Element getElement() {
  159.         return element;
  160.     }
  161. }