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

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

There is a newer version: 11.1.8
Show newest version
/*
 * Copyright (C) 2014-2023 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.BiConsumer;
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.helger.commons.ValueEnforcer;
import com.helger.commons.annotation.Nonempty;
import com.helger.commons.annotation.ReturnsImmutableObject;
import com.helger.commons.annotation.ReturnsMutableCopy;
import com.helger.commons.collection.impl.CommonsArrayList;
import com.helger.commons.collection.impl.CommonsLinkedHashMap;
import com.helger.commons.collection.impl.ICommonsList;
import com.helger.commons.collection.impl.ICommonsOrderedMap;
import com.helger.commons.collection.impl.ICommonsOrderedSet;
import com.helger.commons.debug.GlobalDebug;
import com.helger.commons.equals.EqualsHelper;
import com.helger.commons.functional.ITriConsumer;
import com.helger.commons.state.EChange;
import com.helger.commons.state.EContinue;
import com.helger.commons.string.StringHelper;
import com.helger.commons.string.ToStringGenerator;
import com.helger.commons.typeconvert.TypeConverter;
import com.helger.commons.wrapper.Wrapper;
import com.helger.xml.CXML;
import com.helger.xml.CXMLRegEx;

/**
 * Default implementation of the {@link IMicroElement} interface.
 *
 * @author Philip Helger
 */
public final class MicroElement extends AbstractMicroNodeWithChildren implements IMicroElement
{
  private static final Logger LOGGER = LoggerFactory.getLogger (MicroElement.class);

  private String m_sNamespaceURI;
  private final String m_sTagName;
  private ICommonsOrderedMap  m_aAttrs;

  public MicroElement (@Nonnull @Nonempty final String sTagName)
  {
    this (null, sTagName);
  }

  public MicroElement (@Nullable final String sNamespaceURI, @Nonnull @Nonempty final String sTagName)
  {
    ValueEnforcer.notEmpty (sTagName, "TagName");
    m_sNamespaceURI = sNamespaceURI;

    // Store only the local name (cut the prefix) if a namespace is present
    final int nPrefixEnd = sNamespaceURI != null ? sTagName.indexOf (CXML.XML_PREFIX_NAMESPACE_SEP) : -1;
    if (nPrefixEnd == -1)
      m_sTagName = sTagName;
    else
    {
      // Cut the prefix
      LOGGER.warn ("Removing micro element namespace prefix '" +
                   sTagName.substring (0, nPrefixEnd) +
                   "' from tag name '" +
                   sTagName +
                   "'");
      m_sTagName = sTagName.substring (nPrefixEnd + 1);
    }
    // Only for the debug version, as this slows things down heavily
    if (GlobalDebug.isDebugMode ())
      if (!CXMLRegEx.PATTERN_NAME_QUICK.matcher (m_sTagName).matches ())
        if (!CXMLRegEx.PATTERN_NAME.matcher (m_sTagName).matches ())
          throw new IllegalArgumentException ("The micro element tag name '" +
                                              m_sTagName +
                                              "' is not a valid element name!");
  }

  @Nonnull
  public EMicroNodeType getType ()
  {
    return EMicroNodeType.ELEMENT;
  }

  @Nonnull
  @Nonempty
  public String getNodeName ()
  {
    return getTagName ();
  }

  public boolean hasAttributes ()
  {
    return m_aAttrs != null && m_aAttrs.isNotEmpty ();
  }

  public boolean hasNoAttributes ()
  {
    return m_aAttrs == null || m_aAttrs.isEmpty ();
  }

  @Nonnegative
  public int getAttributeCount ()
  {
    return m_aAttrs == null ? 0 : m_aAttrs.size ();
  }

  @Nullable
  @ReturnsImmutableObject
  public Iterable  getAttributeObjs ()
  {
    if (hasNoAttributes ())
      return null;
    return m_aAttrs.values ();
  }

  @Nullable
  @ReturnsMutableCopy
  public ICommonsList  getAllAttributeObjs ()
  {
    if (hasNoAttributes ())
      return null;
    return m_aAttrs.copyOfValues ();
  }

  @Nullable
  @ReturnsMutableCopy
  public ICommonsOrderedMap  getAllQAttributes ()
  {
    if (hasNoAttributes ())
      return null;
    return new CommonsLinkedHashMap <> (m_aAttrs.values (),
                                        IMicroAttribute::getAttributeQName,
                                        IMicroAttribute::getAttributeValue);
  }

  @Nullable
  @ReturnsMutableCopy
  public ICommonsOrderedSet  getAllAttributeQNames ()
  {
    if (hasNoAttributes ())
      return null;
    return m_aAttrs.copyOfKeySet ();
  }

  public void forAllAttributes (@Nonnull final Consumer  aConsumer)
  {
    if (m_aAttrs != null)
      m_aAttrs.forEachValue (aConsumer);
  }

  public void forAllAttributes (@Nonnull final BiConsumer  aConsumer)
  {
    if (m_aAttrs != null)
      m_aAttrs.forEachValue (a -> aConsumer.accept (a.getAttributeQName (), a.getAttributeValue ()));
  }

  public void forAllAttributes (@Nonnull final ITriConsumer  aConsumer)
  {
    if (m_aAttrs != null)
      m_aAttrs.forEachValue (x -> aConsumer.accept (x.getNamespaceURI (),
                                                    x.getAttributeName (),
                                                    x.getAttributeValue ()));
  }

  @Nullable
  public MicroAttribute getAttributeObj (@Nullable final IMicroQName aQName)
  {
    return aQName == null || m_aAttrs == null ? null : m_aAttrs.get (aQName);
  }

  @Nullable
  private static  DSTTYPE _getConvertedToType (@Nullable final String sAttrValue,
                                                        @Nonnull final Class  aDstClass)
  {
    // Avoid having a conversion issue with empty strings!
    if (StringHelper.hasNoText (sAttrValue))
      return null;
    // throws TypeConverterException if nothing can be converted
    return TypeConverter.convert (sAttrValue, aDstClass);
  }

  @Nullable
  public  DSTTYPE getAttributeValueWithConversion (@Nullable final IMicroQName aAttrName,
                                                            @Nonnull final Class  aDstClass)
  {
    final String sAttrValue = getAttributeValue (aAttrName);
    return _getConvertedToType (sAttrValue, aDstClass);
  }

  public boolean hasAttribute (@Nullable final IMicroQName aAttrName)
  {
    return m_aAttrs != null && aAttrName != null && m_aAttrs.containsKey (aAttrName);
  }

  @Nonnull
  public EChange removeAttribute (@Nullable final IMicroQName aAttrName)
  {
    if (m_aAttrs == null || aAttrName == null)
      return EChange.UNCHANGED;
    return m_aAttrs.removeObject (aAttrName);
  }

  @Nonnull
  public MicroElement setAttribute (@Nonnull final IMicroQName aAttrName, @Nullable final String sAttrValue)
  {
    ValueEnforcer.notNull (aAttrName, "AttrName");
    if (sAttrValue != null)
    {
      if (m_aAttrs == null)
        m_aAttrs = new CommonsLinkedHashMap <> ();
      m_aAttrs.put (aAttrName, new MicroAttribute (aAttrName, sAttrValue));
    }
    else
      removeAttribute (aAttrName);
    return this;
  }

  @Nonnull
  public EChange removeAllAttributes ()
  {
    if (m_aAttrs == null)
      return EChange.UNCHANGED;
    return m_aAttrs.removeAll ();
  }

  @Nullable
  public String getNamespaceURI ()
  {
    return m_sNamespaceURI;
  }

  @Nonnull
  public EChange setNamespaceURI (@Nullable final String sNamespaceURI)
  {
    if (EqualsHelper.equals (m_sNamespaceURI, sNamespaceURI))
      return EChange.UNCHANGED;
    m_sNamespaceURI = sNamespaceURI;
    return EChange.CHANGED;
  }

  @Nullable
  public String getLocalName ()
  {
    return m_sNamespaceURI == null ? null : m_sTagName;
  }

  @Nonnull
  public String getTagName ()
  {
    return m_sTagName;
  }

  @Nonnull
  @ReturnsMutableCopy
  public ICommonsList  getAllChildElementsRecursive ()
  {
    final ICommonsList  ret = new CommonsArrayList <> ();
    _forAllChildElements (this, null, aChildElement -> {
      ret.add (aChildElement);
      ret.addAll (aChildElement.getAllChildElementsRecursive ());
    });
    return ret;
  }

  private static boolean _containsChildElementRecursive (@Nonnull final IMicroNode aStartNode,
                                                         @Nullable final Predicate  aFilter)
  {
    return aStartNode.containsAnyChild (x -> {
      if (x.isElement ())
      {
        final IMicroElement aChildElement = (IMicroElement) x;
        if (aFilter == null || aFilter.test (aChildElement))
          return true;
      }
      else
        if (x.isContainer ())
        {
          if (_containsChildElementRecursive (x, aFilter))
            return true;
        }
      return false;
    });
  }

  public boolean containsAnyChildElement (@Nullable final Predicate  aFilter)
  {
    return _containsChildElementRecursive (this, aFilter);
  }

  @Nonnull
  private static EContinue _forAllChildElementsBreakable (@Nonnull final IMicroNode aStartNode,
                                                          @Nullable final Predicate  aFilter,
                                                          @Nonnull final Function  aConsumer)
  {
    return aStartNode.forAllChildrenBreakable (x -> {
      if (x.isElement ())
      {
        final IMicroElement aChildElement = (IMicroElement) x;
        if (aFilter == null || aFilter.test (aChildElement))
          if (aConsumer.apply (aChildElement).isBreak ())
            return EContinue.BREAK;
      }
      else
        if (x.isContainer ())
          if (_forAllChildElementsBreakable (x, aFilter, aConsumer).isBreak ())
            return EContinue.BREAK;
      return EContinue.CONTINUE;
    });
  }

  private static IMicroElement _findFirstChildElement (@Nonnull final IMicroNode aStartNode,
                                                       @Nullable final Predicate  aFilter)
  {
    final Wrapper  ret = new Wrapper <> ();
    _forAllChildElementsBreakable (aStartNode, aFilter, x -> {
      assert ret.isNotSet ();
      ret.set (x);
      return EContinue.BREAK;
    });
    return ret.get ();
  }

  @Nullable
  public IMicroElement getFirstChildElement (@Nullable final Predicate  aFilter)
  {
    return _findFirstChildElement (this, aFilter);
  }

  private static void _forAllChildElements (@Nonnull final IMicroNode aStartNode,
                                            @Nullable final Predicate  aFilter,
                                            @Nonnull final Consumer  aConsumer)
  {
    aStartNode.forAllChildren (aChildNode -> {
      if (aChildNode.isElement ())
      {
        final IMicroElement aChildElement = (IMicroElement) aChildNode;
        if (aFilter == null || aFilter.test (aChildElement))
          aConsumer.accept (aChildElement);
      }
      else
        if (aChildNode.isContainer ())
          _forAllChildElements (aChildNode, aFilter, aConsumer);
    });
  }

  public void forAllChildElements (@Nullable final Predicate  aFilter,
                                   @Nonnull final Consumer  aConsumer)
  {
    _forAllChildElements (this, aFilter, aConsumer);
  }

  @Nonnull
  public EContinue forAllChildElementsBreakable (@Nullable final Predicate  aFilter,
                                                 @Nonnull final Function  aConsumer)
  {
    return _forAllChildElementsBreakable (this, aFilter, aConsumer);
  }

  @Nonnull
  public IMicroElement getClone ()
  {
    final MicroElement ret = new MicroElement (m_sNamespaceURI, m_sTagName);

    // Copy attributes
    if (m_aAttrs != null)
      ret.m_aAttrs = new CommonsLinkedHashMap <> (m_aAttrs);

    // Deep clone all child nodes
    forAllChildren (aChildNode -> ret.appendChild (aChildNode.getClone ()));
    return ret;
  }

  @Override
  public boolean isEqualContent (@Nullable final IMicroNode o)
  {
    if (o == this)
      return true;
    if (!super.isEqualContent (o))
      return false;
    final MicroElement rhs = (MicroElement) o;
    return EqualsHelper.equals (m_sNamespaceURI, rhs.m_sNamespaceURI) &&
           m_sTagName.equals (rhs.m_sTagName) &&
           EqualsHelper.equals (m_aAttrs, rhs.m_aAttrs);
  }

  @Override
  public String toString ()
  {
    return ToStringGenerator.getDerived (super.toString ())
                            .appendIfNotNull ("namespace", m_sNamespaceURI)
                            .append ("tagname", m_sTagName)
                            .appendIfNotNull ("attrs", m_aAttrs)
                            .getToString ();
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy