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

cdc.io.data.Parent Maven / Gradle / Ivy

The newest version!
package cdc.io.data;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;

import cdc.io.data.paths.Part;
import cdc.io.data.paths.Path;
import cdc.util.function.IterableUtils;
import cdc.util.function.Predicates;
import cdc.util.lang.Checks;
import cdc.util.lang.Operators;

/**
 * Interface implemented by nodes that are parents (they have children nodes): Document and Element.
 *
 * @author Damien Carbonne
 *
 */
public interface Parent extends Node {

    @Override
    public Parent clone(boolean recurse);

    /**
     * Returns {@code true} when a child can be added.
     *
     * @param child The child
     * @return {@code true} when {@code child} can be added.
     */
    public boolean canAddChild(Child child);

    public List getModifiableChildren();

    /**
     * @return A list of children.
     */
    public List getChildren();

    /**
     * Returns an Iterable of children that are instance of a given class.
     *
     * @param  Type of searched children.
     * @param childClass The class of searched children.
     * @return An Iterable of children that are instance of {@code childClass}.
     */
    public default  Iterable getChildren(Class childClass) {
        return IterableUtils.filterAndConvert(childClass,
                                              getChildren(),
                                              Predicates.isInstanceOf(childClass));
    }

    /**
     * Returns an Iterable of children that are instance of a given class and match a predicate.
     *
     * @param  Type of searched children.
     * @param childClass The class of searched children.
     * @param predicate The predicate.
     * @return An Iterable of children that are instance of {@code childClass} and match {@code predicate}.
     */
    public default  Iterable getChildren(Class childClass,
                                                            Predicate predicate) {
        return IterableUtils.filterAndConvert(childClass,
                                              getChildren(),
                                              Predicates.isInstanceOf(childClass).and(predicate));
    }

    public default  T getChild(Class childClass,
                                               Predicate predicate) {
        return IterableUtils.getFirstOrNull(getChildren(childClass, predicate));
    }

    public default  T getChild(Class childClass) {
        return getChild(childClass, Predicates.alwaysTrue());
    }

    /**
     * Returns the child that is an instance of a given class, matches a Predicate and is at a given position.
     *
     * @param  Type of searched children.
     * @param childClass The class of searched children.
     * @param predicate The predicate.
     * @param index The index of the searched child.
     *            This index takes into account elements that match {@code childClass} and {@code predicate}.
     * @return The element matching {@code childClass} and {@code predicate} and at {@code index}, or {@code null}.
     */
    public default  T getChildAt(Class childClass,
                                                 Predicate predicate,
                                                 int index) {
        return IterableUtils.getAtOrNull(getChildren(childClass, predicate), index);
    }

    /**
     * Returns the child that is an instance of a given class and is at a given position.
     *
     * @param  Type of searched children.
     * @param childClass The class of searched children.
     * @param index The index of the searched child.
     *            This index takes into account elements that match {@code childClass}.
     * @return The element matching {@code childClass} and at {@code index}, or {@code null}.
     */
    public default  T getChildAt(Class childClass,
                                                 int index) {
        return IterableUtils.getAtOrNull(getChildren(childClass), index);
    }

    /**
     * @return An Iterable of children elements.
     */
    public default Iterable getElements() {
        return getChildren(Element.class);
    }

    /**
     * @return {@code true} if this parent has children elements.
     */
    public default boolean hasElements() {
        return !IterableUtils.isEmpty(getElements());
    }

    /**
     * Returns an Iterable of children elements that have a given name.
     *
     * @param name The name.
     * @return An Iterable containing elements named {@code name}.
     */
    public default Iterable getElementsNamed(String name) {
        return getChildren(Element.class, Element.named(name));
    }

    /**
     * Returns the child element that has a given name and index.
     *
     * @param name The name.
     * @param index The index of the searched child element.
     *            This index takes into account children that are elements and are named {@code name}.
     * @return The corresponding element or {@code null}.
     */
    public default Element getElementNamedAt(String name,
                                             int index) {
        return IterableUtils.getAtOrNull(getElementsNamed(name), index);
    }

    /**
     * Returns the first child element that has a given name or {@code null}.
     *
     * @param name The name.
     * @return The first child element named {@code name} or {@code null}.
     */
    public default Element getElementNamed(String name) {
        return getElementNamedAt(name, 0);
    }

    /**
     * Returns the sub-child at a given path.
     *
     * @param names The names of elements.
     * @return The element that has a path.
     */
    public default Element getElement(String... names) {
        Checks.isNotNullOrEmpty(names, "names");
        Parent parent = this;
        for (final String name : names) {
            if (parent != null) {
                parent = parent.getElementNamed(name);
            }
        }
        return (Element) parent;
    }

    /**
     * Returns the relative element with a given path.
     *
     * @param path The relative path of the searched element.
     * @return The element at {code path} or {@code null}.
     */
    public default Element getElement(Path path) {
        Parent parent = this;
        for (final Part part : path.getParts()) {
            switch (part.getType()) {
            case DOT:
                // Ignore
                break;
            case DOT_DOT:
                if (parent instanceof Child) {
                    parent = ((Child) parent).getParent();
                } else {
                    parent = null;
                }
                break;
            case ELEMENT:
                if (parent != null) {
                    parent = parent.getElementNamed(part.getElementName());
                }
                break;
            case ATTRIBUTE:
                parent = null;
                break;
            case SELECTOR:
                if (parent != null) {
                    parent = parent.getChild(Element.class,
                                             Element.namedWithAttribute(part.getElementName(),
                                                                        part.getSelectorName(),
                                                                        part.getSelectorValue()));
                }
                break;
            }
        }
        if (parent instanceof Element) {
            return (Element) parent;
        } else {
            return null;
        }
    }

    /**
     * Returns an Iterable of children elements that have a given name, ignoring case.
     *
     * @param name The name.
     * @return An Iterable containing elements named {@code name} ignoring case.
     */
    public default Iterable getElementsNamedIgnoreCase(String name) {
        return getChildren(Element.class, Element.namedIgnoreCase(name));
    }

    /**
     * Returns the child element that has a given name, ignoring case, and index.
     *
     * @param name The name.
     * @param index The index of the searched child element.
     *            This index takes into account children that are elements and are named {@code name} ignoring case.
     * @return The corresponding element or {@code null}.
     */
    public default Element getElementNamedIgnoreCaseAt(String name,
                                                       int index) {
        return IterableUtils.getAtOrNull(getElementsNamedIgnoreCase(name), index);
    }

    /**
     * Returns the first child element that has a given name, ignoring case, or {@code null}.
     *
     * @param name The name.
     * @return The first child element named {@code name}, ignoring case, or {@code null}.
     */
    public default Element getElementNamedIgnoreCase(String name) {
        return getElementNamedIgnoreCaseAt(name, 0);
    }

    public default Element getElementNamedWithAttribute(String name,
                                                        String attributeName,
                                                        String attributeValue) {
        return getChild(Element.class, Element.namedWithAttribute(name, attributeName, attributeValue));
    }

    /**
     * @return An Iterable if text children.
     */
    public default Iterable getTexts() {
        return getChildren(Text.class);
    }

    /**
     * @return {@code true} if this parent has texts children.
     */
    public default boolean hasTexts() {
        return !IterableUtils.isEmpty(getTexts());
    }

    /**
     * @return An Iterable if comment children.
     */
    public default Iterable getComments() {
        return getChildren(Comment.class);
    }

    /**
     * @return {@code true} if this parent has comments children.
     */
    public default boolean hasComments() {
        return !IterableUtils.isEmpty(getComments());
    }

    /**
     * Returns the number of children that match a class and predicate.
     *
     * @param  Type of counted children.
     * @param childClass The class of counted children.
     * @param predicate The predicate.
     * @return The number of children that match {@code childClass} and {@code predicate}.
     */
    public default  int getChildrenCount(Class childClass,
                                                         Predicate predicate) {
        return IterableUtils.size(getChildren(childClass, predicate));
    }

    /**
     * Returns the number of children that match a class.
     *
     * @param childClass The class of counted children.
     * @return The number of children that match {@code childClass}.
     */
    public default int getChildrenCount(Class childClass) {
        return IterableUtils.size(getChildren(childClass));
    }

    /**
     * @return The number of children.
     */
    public default int getChildrenCount() {
        return getChildren().size();
    }

    /**
     * Returns {@code true} if this parent has children of a given class and matching a predicate.
     *
     * @param  Type of searched children.
     *
     * @param childClass The child class.
     * @param predicate The predicate.
     * @return {@code true} if this parent has children of {@code childClass} class matching {@code predicate}.
     */
    public default  boolean hasChildren(Class childClass,
                                                        Predicate predicate) {
        return !IterableUtils.isEmpty(getChildren(childClass, predicate));
    }

    /**
     * Returns {@code true} if this parent has children of a given class.
     *
     * @param childClass The child class.
     * @return {@code true} if this parent has children of {@code childClass} class.
     */
    public default boolean hasChildren(Class childClass) {
        return !IterableUtils.isEmpty(getChildren(childClass));
    }

    /**
     * @return {@code true} if this parent has children.
     */
    public default boolean hasChildren() {
        return !IterableUtils.isEmpty(getChildren());
    }

    /**
     * Returns the child at a given, index or null.
     * 

* {@code null} is returned when index is invalid. * * @param index The index. * @return The child at {@code index}. * @throws IndexOutOfBoundsException When index is out of range ({@code index < 0 || index >= getChildrenCount()}). */ public default Child getChildAt(int index) { return getChildren().get(index); } /** * @return The last child, if any, or {@code null}. */ public default Child getLastChild() { final List children = getChildren(); if (children.isEmpty()) { return null; } else { return children.get(children.size() - 1); } } /** * Adds a child to this parent. *

* If possible, child current parent is changed to this node. * * @param Type of the child. * @param child The child. MUST NOT be null. * @return The input {@code child}. * @throws IllegalArgumentException If {@code child} is {@code null}. * @throws IllegalStateException If {@code child}'s parent can not be set. */ public default C addChild(C child) { Checks.isNotNull(child, "child"); // setParent calls canAddChild() child.setParent(this); return child; } /** * Creates and adds a child element as last child, if possible. * * @param name The child name. * @return the created element. * @throws IllegalStateException If {@code child}'s parent can not be set. */ public default Element addElement(String name) { final Element child = new Element(name); return addChild(child); } /** * Adds a comment as last child. * * @param content The comment content. * @param merge If {@code true}, then if last child exists and is a comment, * {@code content} is added to this last child.
* Otherwise, a comment child is created and added to last position. * @return The modified or created comment. */ public default Comment addComment(String content, boolean merge) { if (merge) { final Child last = getLastChild(); if (last != null && last.getType() == NodeType.COMMENT) { ((Comment) last).appendContent(content); return (Comment) last; } } final Comment result = new Comment(content); addChild(result); return result; } /** * Adds a comment as last child. *

* If last child exists and is a comment, {@code content} is added to this last child.
* Otherwise, a comment child is created and added to last position. * * @param content The comment content. * @return The modified or created comment. */ public default Comment addComment(String content) { return addComment(content, true); } /** * Adds all children of an Iterable to this parent. * * @param children The children. */ public default void addChildren(Iterable children) { for (final Child child : children) { addChild(child); } } /** * Adds all children of an array to this parent. * * @param children The children. */ public default void addChildren(Child... children) { for (final Child child : children) { addChild(child); } } /** * Remove a child. *

* If child is not a child of this parent, then false is returned. * * @param child The child. * @return {@code true} if child was removed, {@code false} otherwise. */ public default boolean removeChild(Child child) { if (child != null && child.getParent() == this) { getModifiableChildren().remove(child); child.setParent(null); return true; } else { return false; } } /** * Removes the child located at a given index. *

* If index is invalid, returns {@code false}. * * @param index The index (0-based). * @return The removed child. * @throws IndexOutOfBoundsException When index is out of range ({@code index < 0 || index >= getChildrenCount()}). */ public default Child removeChildAt(int index) { final AbstractChild child = (AbstractChild) getModifiableChildren().remove(index); child.resetParent(); return child; } /** * Removes all children locally. */ public default void removeChildren() { while (hasChildren()) { removeChildAt(getChildrenCount() - 1); } } /** * Removes all children that match a predicate. *

* This does only a shallow removal.
* For deep removal, use {@link cdc.io.data.util.DataUtils}. * * @param predicate The predicate. */ public default void removeChildren(Predicate predicate) { List indices = null; // Add candidates to removal in reverse order // Better performances for removal for (int index = getChildrenCount() - 1; index >= 0; index--) { final Child child = getChildAt(index); if (predicate.test(child)) { if (indices == null) { indices = new ArrayList<>(); } indices.add(index); } } if (indices != null) { for (final int index : indices) { final Child removed = removeChildAt(index); assert removed != null; } } } /** * Removes all children that match a predicate, recursively. * * @param predicate The predicate. * @param pre If {@code true}, removal is applied before recursion, after otherwise. * @param recurse If {@code true}, then this is applied recursively. */ public default void removeChildren(Predicate predicate, boolean pre, boolean recurse) { if (pre) { removeChildren(predicate); } if (recurse) { for (final Element child : getElements()) { child.removeChildren(predicate, pre, true); } } if (!pre) { removeChildren(predicate); } } /** * Removes all children elements that have a given name. * * @param name The name of elements to remove. */ public default void removeElementsNamed(String name) { removeChildren(Element.named(name)); } /** * Removes all children elements that have a given name, recursively. * * @param name The name of elements to remove. * @param recurse If {@code true}, then this is applied recursively. */ public default void removeElementsNamed(String name, boolean recurse) { removeChildren(Element.named(name), true, recurse); } /** * Removes all comments locally. */ public default void removeComments() { removeChildren(IS_COMMENT); } /** * Removes all comments recursively. * * @param recurse If {@code true}, then this is applied recursively. */ public default void removeComments(boolean recurse) { removeChildren(IS_COMMENT, true, recurse); } /** * Removes all texts locally. */ public default void removeTexts() { removeChildren(IS_TEXT); } /** * Removes all texts recursively. * * @param recurse If {@code true}, then this is applied recursively. */ public default void removeTexts(boolean recurse) { removeChildren(IS_TEXT, true, recurse); } /** * Removes all ignorable texts locally. */ public default void removeIgnorableTexts() { removeChildren(IS_IGNORABLE_TEXT); } /** * Removes all ignorable texts recursively. * * @param recurse If {@code true}, then this is applied recursively. */ public default void removeIgnorableTexts(boolean recurse) { removeChildren(IS_IGNORABLE_TEXT, true, recurse); } /** * Sorts children locally. * * @param comparator The comparator. */ public default void sortChildren(Comparator comparator) { if (hasChildren()) { Collections.sort(getChildren(), comparator); } } /** * Sorts children recursively. * * @param comparator The comparator. * @param recurse If {@code true}, then this is applied recursively. */ public default void sortChildren(Comparator comparator, boolean recurse) { sortChildren(comparator); if (recurse) { for (final Element child : getElements()) { child.sortChildren(comparator, true); } } } /** * Merges all consecutive texts locally. */ public default void mergeTexts() { if (getChildrenCount() > 1) { Child ref = getChildAt(0); int index = 1; while (index < getChildrenCount()) { final Child next = getChildAt(index); if (ref.getType() == NodeType.TEXT && next.getType() == NodeType.TEXT && ((Text) ref).getKind() == ((Text) next).getKind()) { // ref and next are both texts: merge them // They have the same cdata attribute ((Text) ref).appendContent(((Text) next).getContent()); // Remove next next.detach(); // Do not change index and ref } else { ref = next; index++; } } } } /** * Merges all consecutive texts recursively. * * @param recurse If {@code true}, then this is applied recursively. */ public default void mergeTexts(boolean recurse) { mergeTexts(); if (recurse) { for (final Element child : getElements()) { child.mergeTexts(true); } } } /** * Locally sets TextKind of all texts. * * @param kind The text kind. */ public default void setTextsKind(TextKind kind) { for (final Text child : getTexts()) { child.setKind(kind); } } /** * Recursively sets TextKind of all texts. * * @param kind The text kind. * @param recurse If {@code true}, then this is applied recursively. */ public default void setTextsKind(TextKind kind, boolean recurse) { setTextsKind(kind); if (recurse) { for (final Element child : getElements()) { child.setTextsKind(kind, true); } } } /** * Merges all consecutive comments locally. */ public default void mergeComments() { if (getChildrenCount() > 1) { Child ref = getChildAt(0); int index = 1; while (index < getChildrenCount()) { final Child next = getChildAt(index); if (ref.getType() == NodeType.COMMENT && next.getType() == NodeType.COMMENT) { // ref and next are both texts: merge them ((Comment) ref).appendContent(((Comment) next).getContent()); // Remove next next.detach(); // Do not change index and ref } else { ref = next; index++; } } } } /** * Merges all consecutive comments recursively. * * @param recurse If {@code true}, then this is applied recursively. */ public default void mergeComments(boolean recurse) { mergeComments(); if (recurse) { for (final Element child : getElements()) { child.mergeComments(true); } } } /** * Changes all texts locally. * * @param modifier A function that take content and returns new content. */ public default void changeTexts(UnaryOperator modifier) { for (final Text child : getTexts()) { final String content = child.getContent(); final String newContent = modifier.apply(content); if (Operators.notEquals(content, newContent)) { child.setContent(newContent); } } } /** * Changes all texts recursively. * * @param modifier A function that take content and returns new content. * @param recurse If {@code true}, then this is applied recursively. */ public default void changeTexts(UnaryOperator modifier, boolean recurse) { changeTexts(modifier); if (recurse) { for (final Element child : getElements()) { child.changeTexts(modifier, true); } } } /** * Changes or set text under element that have a given name, locally. *

* This will work on elements that are empty or have text and comment children.
* It will ignore elements that have children elements. *

* Children nodes are preserved if possible.
* Otherwise, they are all removed and replace if necessary.
* WARNING: This may remove comments. * * @param name The element name. * @param modifier A function that take content and returns new content. */ public default void changeNamedTexts(String name, UnaryOperator modifier) { for (final Element child : getElementsNamed(name)) { switch (child.getContentType()) { case EMPTY: case TEXT: final String content = child.getText(); final String newContent = modifier.apply(content); if (Operators.notEquals(content, newContent)) { if (child.getChildrenCount(Text.class) == 1 && newContent != null && !newContent.isEmpty()) { final Text text = child.getChild(Text.class); text.clearContent(); text.appendContent(newContent); } else { child.removeChildren(); if (newContent != null && !newContent.isEmpty()) { child.addText(modifier.apply(content)); } } } break; default: break; } } } /** * Changes or set text under element that have a given name, recursively. * * @param name The element name. * @param modifier A function that take content and returns new content. * @param recurse If {@code true}, then this is applied recursively. */ public default void changeNamedTexts(String name, UnaryOperator modifier, boolean recurse) { changeNamedTexts(name, modifier); if (recurse) { for (final Element child : getElements()) { child.changeNamedTexts(name, modifier, true); } } } /** * Changes all comments locally. * * @param modifier A function that take content and returns new content. */ public default void changeComments(UnaryOperator modifier) { for (final Comment child : getComments()) { final String content = child.getContent(); final String newContent = modifier.apply(content); if (Operators.notEquals(content, newContent)) { child.setContent(newContent); } } } /** * Changes all comments recursively. * * @param modifier A function that take content and returns new content. * @param recurse If {@code true}, then this is applied recursively. */ public default void changeComments(UnaryOperator modifier, boolean recurse) { changeComments(modifier); if (recurse) { for (final Element child : getElements()) { child.changeComments(modifier, true); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy