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

org.httprpc.kilo.xml.ElementAdapter Maven / Gradle / Ivy

There is a newer version: 4.9
Show newest version
/*
 * Licensed 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.httprpc.kilo.xml;

import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import java.util.AbstractList;
import java.util.AbstractMap;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Provides access to the contents of an XML DOM element via the {@link Map}
 * interface. The reserved "." key refers to the element itself. Keys begining
 * with "@" represent attributes. Keys ending with "*" represent multiple
 * elements. All other keys represent individual elements.
 */
public class ElementAdapter extends AbstractMap {
    // Node list adapter
    private static class NodeListAdapter extends AbstractList {
        NodeList nodeList;

        NodeListAdapter(NodeList nodeList) {
            this.nodeList = nodeList;
        }

        @Override
        public ElementAdapter get(int i) {
            return new ElementAdapter((Element)nodeList.item(i));
        }

        @Override
        public int size() {
            return nodeList.getLength();
        }
    }

    private Element element;

    private static final String SELF_REFERENCE = ".";

    private static final String ATTRIBUTE_PREFIX = "@";
    private static final String LIST_SUFFIX = "*";

    /**
     * Constructs a new element adapter.
     *
     * @param element
     * The source element.
     */
    public ElementAdapter(Element element) {
        if (element == null) {
            throw new IllegalArgumentException();
        }

        this.element = element;
    }

    /**
     * 

Retrieves a value associated with the source element.

* *
    *
  • If the key is equal to ".", the element's text content will be * returned.
  • *
  • If the key refers to an attribute, the attribute's value (if any) * will be returned.
  • *
  • If the key refers to multiple elements, a {@link List} * implementation that recursively adapts all matching sub-elements will be * returned.
  • *
  • Otherwise, an adapter for the last matching sub-element (if any) * will be returned.
  • *
* * {@inheritDoc} */ @Override public Object get(Object key) { if (key == null) { throw new IllegalArgumentException(); } return get(key.toString()); } private Object get(String key) { if (key.equals(SELF_REFERENCE)) { return element.getTextContent(); } else if (isAttribute(key)) { key = getAttributeName(key); if (element.hasAttribute(key)) { return element.getAttribute(key); } else { return null; } } else if (isList(key)) { return new NodeListAdapter(element.getElementsByTagName(getListTagName(key))); } else { var nodeList = element.getElementsByTagName(key); var n = nodeList.getLength(); if (n > 0) { return new ElementAdapter((Element)nodeList.item(n - 1)); } else { return null; } } } /** *

Updates a value associated with the source element.

* *
    *
  • If the key is equal to ".", the element's text content will be * replaced with the string representation of the provided value.
  • *
  • If the key refers to an attribute, the attribute's value (if any) * will be replaced with the string representation of the provided * value.
  • *
  • If the key refers to multiple elements, the provided value must be a * {@link Collection}. Any matching sub-elements will be replaced with new * elements corresponding to the contents of the collection.
  • *
  • Otherwise, any matching sub-elements will be replaced by a single * new element corresponding to the provided value.
  • *
* *

For {@link Map} values, new elements will be recursively populated * based on the map's contents. For all other types, the element's text * content will be set to the string representation of the provided * value.

* * {@inheritDoc} */ @Override public Object put(String key, Object value) { if (key == null || value == null) { throw new IllegalArgumentException(); } if (key.equals(SELF_REFERENCE)) { element.setTextContent(value.toString()); } else if (isAttribute(key)) { element.setAttribute(getAttributeName(key), value.toString()); } else if (isList(key)) { if (value instanceof Collection collection) { var tagName = getListTagName(key); remove(tagName); appendElements(tagName, collection); } else { throw new IllegalArgumentException(); } } else { remove(key); appendElement(key, value); } return null; } private void appendElements(String tagName, Iterable collection) { for (var element : collection) { appendElement(tagName, element); } } private void appendElement(String tagName, Object value) { var document = element.getOwnerDocument(); var element = document.createElement(tagName); if (value instanceof Map map) { var elementAdapter = new ElementAdapter(element); for (var entry : map.entrySet()) { elementAdapter.put(entry.getKey().toString(), entry.getValue()); } } else { element.setTextContent(value.toString()); } this.element.appendChild(element); } /** *

Removes a value associated with the source element.

* *
    *
  • If the key refers to an attribute, the attribute (if any) will be * removed.
  • *
  • Otherwise, any matching sub-elements will be removed.
  • *
* *

Self reference and multi-element keys are not supported.

* * {@inheritDoc} */ @Override public Object remove(Object key) { if (key == null) { throw new IllegalArgumentException(); } remove(key.toString()); return null; } private void remove(String key) { if (key.equals(SELF_REFERENCE) || isList(key)) { throw new UnsupportedOperationException("Cannot delete self or list reference."); } if (isAttribute(key)) { element.removeAttribute(getAttributeName(key)); } else { var nodeList = element.getElementsByTagName(key); var n = nodeList.getLength(); for (var i = n - 1; i >= 0; i--) { element.removeChild(nodeList.item(i)); } } } /** * Determines if the source element contains a given key. * {@inheritDoc} */ @Override public boolean containsKey(Object key) { if (key == null) { throw new IllegalArgumentException(); } return containsKey(key.toString()); } private boolean containsKey(String key) { if (key.equals(SELF_REFERENCE) || isList(key)) { return true; } else if (isAttribute(key)) { return element.hasAttribute(getAttributeName(key)); } else { return element.getElementsByTagName(key).getLength() > 0; } } /** * Throws {@link UnsupportedOperationException}. * {@inheritDoc} */ @Override public Set> entrySet() { throw new UnsupportedOperationException(); } /** * Returns the text content of the element. * {@inheritDoc} */ @Override public String toString() { return element.getTextContent(); } private static boolean isAttribute(String key) { return key.startsWith(ATTRIBUTE_PREFIX); } private static String getAttributeName(String key) { return key.substring(ATTRIBUTE_PREFIX.length()); } private static boolean isList(String key) { return key.endsWith(LIST_SUFFIX); } private static String getListTagName(String key) { return key.substring(0, key.length() - LIST_SUFFIX.length()); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy