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

com.helger.xml.microdom.AbstractMicroNode Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2014-2024 Philip Helger (www.helger.com)
 * philip[at]helger[dot]com
 *
 * 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 com.helger.xml.microdom;

import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;

import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import com.helger.commons.ValueEnforcer;
import com.helger.commons.annotation.OverrideOnDemand;
import com.helger.commons.annotation.ReturnsMutableCopy;
import com.helger.commons.callback.CallbackList;
import com.helger.commons.collection.impl.CommonsEnumMap;
import com.helger.commons.collection.impl.ICommonsIterable;
import com.helger.commons.collection.impl.ICommonsList;
import com.helger.commons.collection.impl.ICommonsMap;
import com.helger.commons.state.EChange;
import com.helger.commons.state.EContinue;
import com.helger.commons.string.ToStringGenerator;

/**
 * This is an abstract base class for the micro document object model. It
 * implements a set of common methods required for all object types. Especially
 * for the parent/child handling, the sub class AbstractMicroNodeWithChildren
 * provides some additional features.
 *
 * @author Philip Helger
 */
public abstract class AbstractMicroNode implements IMicroNode
{
  /** The parent node of this node. */
  private AbstractMicroNodeWithChildren m_aParentNode;
  private CommonsEnumMap > m_aEventTargets;

  /**
   * Callback that is invoked once a child is to be appended.
   *
   * @param aChildNode
   *        The appended child node.
   */
  @OverrideOnDemand
  protected void onAppendChild (@Nonnull final AbstractMicroNode aChildNode)
  {
    throw new MicroException ("Cannot append children in class " + getClass ().getName ());
  }

  /**
   * Callback that is invoked once a child is to be inserted before another
   * child.
   *
   * @param aChildNode
   *        The new child node to be inserted.
   * @param aSuccessor
   *        The node before which the new node will be inserted.
   */
  @OverrideOnDemand
  protected void onInsertBefore (@Nonnull final AbstractMicroNode aChildNode, @Nonnull final IMicroNode aSuccessor)
  {
    throw new MicroException ("Cannot insert children in class " + getClass ().getName ());
  }

  /**
   * Callback that is invoked once a child is to be inserted after another
   * child.
   *
   * @param aChildNode
   *        The new child node to be inserted.
   * @param aPredecessor
   *        The node after which the new node will be inserted.
   */
  @OverrideOnDemand
  protected void onInsertAfter (@Nonnull final AbstractMicroNode aChildNode, @Nonnull final IMicroNode aPredecessor)
  {
    throw new MicroException ("Cannot insert children in class " + getClass ().getName ());
  }

  /**
   * Callback that is invoked once a child is to be inserted at the specified
   * index.
   *
   * @param nIndex
   *        The index where the node should be inserted.
   * @param aChildNode
   *        The new child node to be inserted.
   */
  @OverrideOnDemand
  protected void onInsertAtIndex (@Nonnegative final int nIndex, @Nonnull final AbstractMicroNode aChildNode)
  {
    throw new MicroException ("Cannot insert children in class " + getClass ().getName ());
  }

  @Nullable
  public final  NODETYPE appendChild (@Nullable final NODETYPE aChildNode)
  {
    if (aChildNode != null)
      onAppendChild ((AbstractMicroNode) aChildNode);
    return aChildNode;
  }

  @Nullable
  public final  NODETYPE insertBefore (@Nullable final NODETYPE aChildNode,
                                                                    @Nonnull final IMicroNode aSuccessor)
  {
    if (aChildNode != null)
      onInsertBefore ((AbstractMicroNode) aChildNode, aSuccessor);
    return aChildNode;
  }

  @Nullable
  public final  NODETYPE insertAfter (@Nullable final NODETYPE aChildNode,
                                                                   @Nonnull final IMicroNode aPredecessor)
  {
    if (aChildNode != null)
      onInsertAfter ((AbstractMicroNode) aChildNode, aPredecessor);
    return aChildNode;
  }

  @Nullable
  public final  NODETYPE insertAtIndex (@Nonnegative final int nIndex, @Nullable final NODETYPE aChildNode)
  {
    if (aChildNode != null)
      onInsertAtIndex (nIndex, (AbstractMicroNode) aChildNode);
    return aChildNode;
  }

  /**
   * Callback when a child is removed.
   *
   * @param aChild
   *        The child that is removed.
   * @return {@link EChange#CHANGED} if something changed
   */
  @OverrideOnDemand
  @Nonnull
  protected EChange onRemoveChild (final IMicroNode aChild)
  {
    throw new MicroException ("Cannot remove child from this node: " + getClass ().getName ());
  }

  @Nonnull
  public final EChange removeChild (@Nonnull final IMicroNode aChild)
  {
    ValueEnforcer.notNull (aChild, "Child");
    return onRemoveChild (aChild);
  }

  /**
   * Remove the child not at the specified index.
   *
   * @param nIndex
   *        The 0-based index of the item to be removed.
   * @return {@link EChange#CHANGED} if the node was successfully removed,
   *         {@link EChange#UNCHANGED} otherwise.
   */
  @OverrideOnDemand
  @Nonnull
  protected EChange onRemoveChildAtIndex (final int nIndex)
  {
    throw new MicroException ("Cannot remove child from this node: " + getClass ().getName ());
  }

  @Nonnull
  public final EChange removeChildAtIndex (@Nonnegative final int nIndex)
  {
    return onRemoveChildAtIndex (nIndex);
  }

  /**
   * Remove all children from this node.
   *
   * @return {@link EChange#CHANGED} if at least one child was present, and was
   *         successfully removed, {@link EChange#UNCHANGED} otherwise.
   */
  @OverrideOnDemand
  @Nonnull
  protected EChange onRemoveAllChildren ()
  {
    throw new MicroException ("Cannot remove all children from this node: " + getClass ().getName ());
  }

  @Nonnull
  public final EChange removeAllChildren ()
  {
    return onRemoveAllChildren ();
  }

  @Override
  @OverrideOnDemand
  public boolean hasChildren ()
  {
    return false;
  }

  @OverrideOnDemand
  @Nullable
  public ICommonsList  getAllChildren ()
  {
    return null;
  }

  @OverrideOnDemand
  @Nullable
  public ICommonsIterable  getChildren ()
  {
    return null;
  }

  @Override
  public void forAllChildren (@Nonnull final Consumer  aConsumer)
  {
    // empty
  }

  @Override
  @Nonnull
  public EContinue forAllChildrenBreakable (@Nonnull final Function  aConsumer)
  {
    return EContinue.CONTINUE;
  }

  @Override
  public void forAllChildren (@Nonnull final Predicate  aFilter, @Nonnull final Consumer  aConsumer)
  {
    // empty
  }

  @Override
  public  void forAllChildrenMapped (@Nonnull final Predicate  aFilter,
                                              @Nonnull final Function  aMapper,
                                              @Nonnull final Consumer  aConsumer)
  {
    // empty
  }

  @OverrideOnDemand
  @Nullable
  public IMicroNode getChildAtIndex (@Nonnegative final int nIndex)
  {
    return null;
  }

  @OverrideOnDemand
  @Nonnegative
  public int getChildCount ()
  {
    return 0;
  }

  @OverrideOnDemand
  @Nullable
  public IMicroNode getFirstChild ()
  {
    return null;
  }

  @Override
  @Nullable
  public IMicroNode findFirstChild (@Nonnull final Predicate  aFilter)
  {
    return null;
  }

  @Override
  @Nullable
  public  DSTTYPE findFirstChildMapped (@Nonnull final Predicate  aFilter,
                                                 @Nonnull final Function  aMapper)
  {
    return null;
  }

  @OverrideOnDemand
  @Nullable
  public IMicroNode getLastChild ()
  {
    return null;
  }

  @Nullable
  public final IMicroNode getPreviousSibling ()
  {
    if (m_aParentNode == null)
      return null;
    final ICommonsList  aParentChildren = m_aParentNode.directGetAllChildren ();
    final int nIndex = aParentChildren.indexOf (this);
    if (nIndex == -1)
      throw new IllegalStateException ("this is no part of it's parents children");

    // getAtIndex handles index underflow
    return aParentChildren.getAtIndex (nIndex - 1);
  }

  @Nullable
  public final IMicroNode getNextSibling ()
  {
    if (m_aParentNode == null)
      return null;
    final ICommonsList  aParentChildren = m_aParentNode.directGetAllChildren ();
    final int nIndex = aParentChildren.indexOf (this);
    if (nIndex == -1)
      throw new IllegalStateException ("this is no part of it's parents children");

    // getAtIndex handles index overflow
    return aParentChildren.getAtIndex (nIndex + 1);
  }

  public final boolean hasParent ()
  {
    return m_aParentNode != null;
  }

  @Nullable
  public final IMicroNode getParent ()
  {
    return m_aParentNode;
  }

  protected final void internalResetParentNode ()
  {
    m_aParentNode = null;
  }

  protected final void internalSetParentNode (@Nonnull final AbstractMicroNodeWithChildren aParentNode)
  {
    if (aParentNode == null)
      throw new MicroException ("No parent node passed!");
    if (aParentNode == this)
      throw new MicroException ("Node cannot have itself as parent: " + toString ());
    if (m_aParentNode != null)
      throw new MicroException ("Node already has a parent: " + toString ());
    m_aParentNode = aParentNode;
  }

  @Nonnull
  public final IMicroNode detachFromParent ()
  {
    if (m_aParentNode != null)
    {
      if (m_aParentNode.removeChild (this).isUnchanged ())
        throw new IllegalStateException ("Failed to remove this from parents child list");
      internalResetParentNode ();
    }
    return this;
  }

  @Nullable
  public IMicroElement findParentElement (@Nonnull final Predicate  aFilter)
  {
    IMicroNode aParent = m_aParentNode;
    while (aParent != null && aParent.isElement ())
    {
      final IMicroElement aParentElement = (IMicroElement) aParent;
      if (aFilter.test (aParentElement))
        return aParentElement;
      aParent = aParent.getParent ();
    }
    return null;
  }

  /*
   * Note: the implementations with "this instanceof IMicroXXX" is faster than
   * doing either "getType ().equals (EMicroNodeType....)" and faster than
   * having "return false;" in here and "return true;" in the respective
   * implementation classes.
   */

  public final boolean isDocument ()
  {
    return this instanceof IMicroDocument;
  }

  public final boolean isDocumentType ()
  {
    return this instanceof IMicroDocumentType;
  }

  public final boolean isText ()
  {
    return this instanceof IMicroText;
  }

  public final boolean isCDATA ()
  {
    return this instanceof IMicroCDATA;
  }

  public final boolean isComment ()
  {
    return this instanceof IMicroComment;
  }

  public final boolean isEntityReference ()
  {
    return this instanceof IMicroEntityReference;
  }

  public final boolean isElement ()
  {
    return this instanceof IMicroElement;
  }

  public final boolean isProcessingInstruction ()
  {
    return this instanceof IMicroProcessingInstruction;
  }

  public final boolean isContainer ()
  {
    return this instanceof IMicroContainer;
  }

  protected final void internalTriggerEvent (@Nonnull final EMicroEvent eEventType, @Nonnull final IMicroEvent aEvent)
  {
    // Any event targets present?
    if (m_aEventTargets != null && m_aEventTargets.isNotEmpty ())
    {
      // Get all event handler
      final CallbackList  aTargets = m_aEventTargets.get (eEventType);
      if (aTargets != null)
        aTargets.forEach (x -> x.handleEvent (aEvent));
    }

    // Bubble to parent
    if (m_aParentNode != null)
      m_aParentNode.internalTriggerEvent (eEventType, aEvent);
  }

  protected final void onEvent (@Nonnull final EMicroEvent eEventType,
                                @Nonnull final IMicroNode aSourceNode,
                                @Nonnull final IMicroNode aTargetNode)
  {
    // Create the event only once
    internalTriggerEvent (eEventType, new MicroEvent (eEventType, aSourceNode, aTargetNode));
  }

  @Nonnull
  public EChange registerEventTarget (@Nonnull final EMicroEvent eEventType, @Nonnull final IMicroEventTarget aTarget)
  {
    ValueEnforcer.notNull (eEventType, "EventType");
    ValueEnforcer.notNull (aTarget, "EventTarget");

    if (m_aEventTargets == null)
      m_aEventTargets = new CommonsEnumMap <> (EMicroEvent.class);
    final CallbackList  aSet = m_aEventTargets.computeIfAbsent (eEventType, k -> new CallbackList <> ());
    return EChange.valueOf (aSet.add (aTarget));
  }

  @Nonnull
  public EChange unregisterEventTarget (@Nonnull final EMicroEvent eEventType, @Nonnull final IMicroEventTarget aTarget)
  {
    ValueEnforcer.notNull (eEventType, "EventType");
    ValueEnforcer.notNull (aTarget, "EventTarget");

    if (m_aEventTargets != null && m_aEventTargets.isNotEmpty ())
    {
      final CallbackList  aSet = m_aEventTargets.get (eEventType);
      if (aSet != null)
        return aSet.removeObject (aTarget);
    }
    return EChange.UNCHANGED;
  }

  @Nonnull
  @ReturnsMutableCopy
  public ICommonsMap > getAllEventTargets ()
  {
    return new CommonsEnumMap <> (m_aEventTargets);
  }

  @Nonnull
  @ReturnsMutableCopy
  public CallbackList  getAllEventTargets (@Nullable final EMicroEvent eEvent)
  {
    return new CallbackList <> (m_aEventTargets == null ? null : m_aEventTargets.get (eEvent));
  }

  @Override
  public String toString ()
  {
    return new ToStringGenerator (this).appendIfNotNull ("ParentNodeName", m_aParentNode == null ? null : m_aParentNode.getNodeName ())
                                       .appendIfNotNull ("EventTargets", m_aEventTargets)
                                       .getToString ();
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy