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

com.hfg.xml.XMLContainerImpl Maven / Gradle / Ivy

There is a newer version: 20240423
Show newest version
package com.hfg.xml;


import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.regex.Pattern;

import com.hfg.exception.ProgrammingException;
import com.hfg.util.CompareUtil;
import com.hfg.util.collection.CollectionUtil;
import com.hfg.util.Recursion;
import com.hfg.util.StringBuilderPlus;
import com.hfg.util.io.GZIP;

//------------------------------------------------------------------------------
/**
 XML object that can contain sub-objects but not a name or attributes.

 @author J. Alex Taylor, hairyfatguy.com
 */
//------------------------------------------------------------------------------
// com.hfg XML/HTML Coding Library
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com
// [email protected]
//------------------------------------------------------------------------------

public abstract class XMLContainerImpl implements XMLContainer
{
   protected List         mContentAndSubtagList;
   private   XMLContainer mParent;

   private static final boolean ESCAPE = true;
   private static final boolean NO_ESCAPE = false;
   private static final boolean WITH_EMBEDDED_SUBTAGS = true;
   private static final boolean WITHOUT_EMBEDDED_SUBTAGS = false;

   protected static enum  sMode { FIND, REMOVE };
   private static int   sCompressionThreshold = 1024;

   //---------------------------------------------------------------------------
   @Override
   public XMLTag clone()
   {
      XMLTag cloneObj;
      try
      {
         cloneObj = (XMLTag) super.clone();
      }
      catch (CloneNotSupportedException e)
      {
         throw new ProgrammingException(e);
      }

      if (mContentAndSubtagList != null)
      {
         cloneObj.mContentAndSubtagList = new ArrayList(mContentAndSubtagList.size());
         for (Object content : mContentAndSubtagList)
         {
            if (content instanceof XMLizable)
            {
               XMLizable subtagClone = ((XMLizable)content).clone();
               cloneObj.mContentAndSubtagList.add(subtagClone);
               if (subtagClone instanceof XMLContainer)
               {
                  ((XMLContainer)subtagClone).setParentNode(cloneObj);
               }
            }
            else
            {
               cloneObj.mContentAndSubtagList.add(content);
            }
         }
      }

      return cloneObj;
   }

   //---------------------------------------------------------------------------
   public boolean hasContent()
   {
      boolean returnValue = false;

      if (mContentAndSubtagList != null)
      {
         for (Object content : mContentAndSubtagList)
         {
            if (content instanceof String
               || content instanceof byte[]) // Compressed content is stored as a byte[]
            {
               returnValue = true;
               break;
            }
         }
      }

      return returnValue;
   }

   //---------------------------------------------------------------------------
   public boolean hasContentOrSubtags()
   {
      return (CollectionUtil.hasValues(mContentAndSubtagList));
   }

   //---------------------------------------------------------------------------
   public List getContentPlusSubtagList()
   {
      return mContentAndSubtagList;
   }


   //---------------------------------------------------------------------------
   public synchronized XMLContainer setContent(CharSequence inContent)
   {
      clearContent();

      addContent(inContent);

      return this;
   }

   //---------------------------------------------------------------------------
   public synchronized XMLContainer setContent(int inContent)
   {
      return setContent(inContent + "");
   }


   //---------------------------------------------------------------------------
   // Clears content (not subtags).
   public synchronized void clearContent()
   {
      if (mContentAndSubtagList != null)
      {
         for (int i = 0; i < mContentAndSubtagList.size(); i++)
         {
            Object content = mContentAndSubtagList.get(i);

            // Ignore subtags and remove compressed or uncompressed content.
            if (content instanceof String
               || content instanceof byte[])
            {
               mContentAndSubtagList.remove(i--);
            }
         }
      }
   }

   //---------------------------------------------------------------------------
   public XMLContainer addContent(CharSequence inContent)
   {
      addContent(inContent, ESCAPE);

      return this;
   }

   //---------------------------------------------------------------------------
   public XMLContainer addContentWithoutEscaping(CharSequence inContent)
   {
      addContent(inContent, NO_ESCAPE);

      return this;
   }

   //---------------------------------------------------------------------------
   public String getContent()
   {
      return getContent(WITHOUT_EMBEDDED_SUBTAGS);
   }

   //---------------------------------------------------------------------------
   /**
    The returned content also contains any embedded subtags.
    */
   private String getContentWithEmbeddedSubtags()
   {
      return getContent(WITH_EMBEDDED_SUBTAGS);
   }

   //----------------------------------------------------------------------------
   public String getUnescapedContent()
   {
      return XMLUtil.unescapeContent(getContent());
   }

   //---------------------------------------------------------------------------
   public synchronized void addSubtag(XMLizable inSubtag)
   {
      if (null == inSubtag)
      {
         throw new RuntimeException("The added xml subTag cannot be null");
      }

      if (null == mContentAndSubtagList) mContentAndSubtagList = new ArrayList(2);

      mContentAndSubtagList.add(inSubtag);

      if (inSubtag instanceof XMLContainer)
      {
         ((XMLContainer)inSubtag).setParentNode(this);
      }
   }

   //---------------------------------------------------------------------------
   /**
    Added the specified subtag at the specified position in the list of this tag's
    children.
    @param inIndex the index at which the specified subtag should be added
    @param inSubtag the subtag which should be added to this tag
    */
   public synchronized void addSubtag(int inIndex, XMLizable inSubtag)
   {
      if (null == inSubtag)
      {
         throw new RuntimeException("The added xml subTag cannot be null");
      }

      if (null == mContentAndSubtagList) mContentAndSubtagList = new ArrayList(2);

      if (inIndex > mContentAndSubtagList.size())  // Prevent trying to insert past the end
      {
         inIndex = mContentAndSubtagList.size();
      }

      mContentAndSubtagList.add(inIndex, inSubtag);

      if (inSubtag instanceof XMLContainer)
      {
         ((XMLContainer)inSubtag).setParentNode(this);
      }
   }

   //---------------------------------------------------------------------------
   public synchronized void addSubtags(Collection inSubtags)
   {
      if (null == inSubtags)
      {
         throw new RuntimeException("The added Collection of xml subTags cannot be null");
      }

      if (null == mContentAndSubtagList) mContentAndSubtagList = new ArrayList(inSubtags.size());

      for (XMLizable subtag : inSubtags)
      {
         addSubtag(subtag);
      }
   }

   //---------------------------------------------------------------------------
   public synchronized  void setSubtags(List inSubtags)
   {
      clearSubtags();
      if (CollectionUtil.hasValues(inSubtags))
      {
         addSubtags(inSubtags);
      }
   }

   //---------------------------------------------------------------------------
   public  List getSubtags()
   {
      List subtagList = new ArrayList(5);

      if (mContentAndSubtagList != null)
      {
         for (Object content : mContentAndSubtagList)
         {
            if (content instanceof XMLizable)
            {
               subtagList.add((T)content);
            }
         }
      }

      return subtagList;
   }

   //---------------------------------------------------------------------------
   public  List getXMLNodeSubtags()
   {
      List subtagList = new ArrayList<>(5);

      if (mContentAndSubtagList != null)
      {
         for (Object content : mContentAndSubtagList)
         {
            if (content instanceof XMLNode)
            {
               subtagList.add((T)content);
            }
         }
      }

      return subtagList;
   }

   //---------------------------------------------------------------------------
   /**
    Returns the subtag with the specified attribute name and value.
    @param inAttributeName the attribute name to match on. Allows null.
    @param inAttributeValue the attribute value to match on
    @return the subtag which matched the attribute criteria
    @throws RuntimeException if multiple subtags match the specified attribute criteria
    */
   public  T getSubtagByAttribute(XMLName inAttributeName, String inAttributeValue)
   {
      return getSubtagByAttribute(inAttributeName != null ? inAttributeName.getLocalName() : null, inAttributeValue);
   }

   //---------------------------------------------------------------------------
   /**
    Returns the subtag with the specified attribute name and value.
    @param inAttributeName the attribute name to match on. Allows null.
    @param inAttributeValue the attribute value to match on
    @return the subtag which matched the attribute criteria
    @throws RuntimeException if multiple subtags match the specified attribute criteria
    */
   public  T getSubtagByAttribute(String inAttributeName, String inAttributeValue)
   {
      T requestedSubtag = null;

      List subtags = getSubtagsByAttribute(inAttributeName, inAttributeValue);
      if (CollectionUtil.hasValues(subtags))
      {
         if (subtags.size() > 1)
         {
            throw new RuntimeException(subtags.size() + " subtags matching the requested attribute ["
                                       + inAttributeName + "=" + inAttributeValue + "] instead of the required 1!");
         }
         else
         {
            requestedSubtag = subtags.get(0);
         }
      }

      return requestedSubtag;
   }

   //---------------------------------------------------------------------------
   /**
    Returns a List of all subtags with the specified attribute name and value.
    @param inAttributeName the attribute name to match on. Allows null.
    @param inAttributeValue the attribute value to match on
    @return the list of all subtags which matched the attribute criteria
    */
   public  List getSubtagsByAttribute(XMLName inAttributeName, String inAttributeValue)
   {
      return getSubtagsByAttribute(inAttributeName != null ? inAttributeName.getLocalName() : null, inAttributeValue);
   }

   //---------------------------------------------------------------------------
   /**
    Returns a List of all subtags with the specified attribute name and value.
    @param inAttributeName the attribute name to match on. Allows null.
    @param inAttributeValue the attribute value to match on
    @return the list of all subtags which matched the attribute criteria
    */
   public  List getSubtagsByAttribute(String inAttributeName, String inAttributeValue)
   {
      List subtagList = null;

      List xmlNodes = getXMLNodeSubtags();
      if (CollectionUtil.hasValues(xmlNodes))
      {
         subtagList = new ArrayList<>(5);
         for (T node : xmlNodes)
         {
            if (inAttributeName != null)
            {
               String attributeValue = node.getAttributeValue(inAttributeName);
               if (0 == CompareUtil.compare(attributeValue, inAttributeValue))
               {
                  subtagList.add(node);
               }
            }
            else // Allow the attribute name to be null
            {
               for (XMLAttribute attr : node.getAttributes())
               {
                  if (0 == CompareUtil.compare(attr.getValue(), inAttributeValue))
                  {
                     subtagList.add(node);
                     break;
                  }
               }
            }
         }
      }

      return subtagList;
   }

   //---------------------------------------------------------------------------
   /**
    Returns a List of all subtags with the specified attribute name and value.
    @param inAttributeName the attribute name to match on. Allows null.
    @param inAttributeValuePattern the Pattern to compare to attribute values via find(). Allows null.
    @return the list of all subtags which matched the attribute criteria
    */
   public  List getSubtagsByAttribute(String inAttributeName, Pattern inAttributeValuePattern)
   {
      List subtagList = null;

      List xmlNodes = getXMLNodeSubtags();
      if (CollectionUtil.hasValues(xmlNodes))
      {
         subtagList = new ArrayList<>(5);
         for (T node : xmlNodes)
         {
            if (inAttributeName != null)
            {
               if (node.hasAttribute(inAttributeName))
               {
                  String attributeValue = node.getAttributeValue(inAttributeName);
                  if (null == inAttributeValuePattern
                      || (attributeValue != null
                          && inAttributeValuePattern.matcher(attributeValue).find()))
                  {
                     subtagList.add(node);
                  }
               }
            }
            else // Allow the attribute name to be null
            {
               for (XMLAttribute attr : node.getAttributes())
               {
                  if (null == inAttributeValuePattern
                        || inAttributeValuePattern.matcher(attr.getValue()).find())
                  {
                     subtagList.add(node);
                     break;
                  }
               }
            }
         }
      }

      return subtagList;
   }


   //---------------------------------------------------------------------------
   /**
    Returns a List of all subtags of the specified Java Class on this XMLTag object
    The same as getSubtagsByClass(inClassName, Recursion.OFF)
    */
   public  List getSubtagsByClass(Class inClassType)
   {
      return getSubtagsByClass(inClassType, Recursion.OFF);
   }

   //---------------------------------------------------------------------------
   /**
    Returns a List of all subtags of the specified Java Class on this XMLTag object
    (if recursion is off) or at any level below this object (if recursion is on).
    */
   public  List getSubtagsByClass(Class inClassType, Recursion inRecursion)
   {
      List outList = new ArrayList();

      if (inClassType != null)
      {
         if (inRecursion == Recursion.ON)
         {
            recursivelyGetSubtagsByClass(inClassType, this, outList, sMode.FIND);
         }
         else
         {
            if (mContentAndSubtagList != null)
            {
               for (Object content : mContentAndSubtagList)
               {
                  if (inClassType.isInstance(content))
                  {
                     outList.add((T)content);
                  }
               }
            }
         }
      }

      return outList;
   }


   //---------------------------------------------------------------------------
   public synchronized void removeSubtag(XMLizable inSubtag)
   {
      if (mContentAndSubtagList != null)
      {
         // Remove all instances of the subtag in the list.
         while (mContentAndSubtagList.remove(inSubtag))
         {
         }
      }
   }

   //---------------------------------------------------------------------------
   /**
    Removes subtags of the specified Java Class on this object
    The same as removeSubtagsByClass(inClassName, Recursion.OFF)
    @return the List of removed objects
    */
   public  List removeSubtagsByClass(Class inClassType)
   {
      return removeSubtagsByClass(inClassType, Recursion.OFF);
   }

   //---------------------------------------------------------------------------
   /**
    Removes subtags of the specified Java Class on this XMLTag object
    (if recursion is off) or at any level below this object (if recursion is on).
    @return the List of removed objects
    */
   public  List removeSubtagsByClass(Class inClassType, Recursion inRecursion)
   {
      List outList = new ArrayList();

      if (inClassType != null)
      {
         if (inRecursion == Recursion.ON)
         {
            recursivelyGetSubtagsByClass(inClassType, this, outList, sMode.REMOVE);
         }
         else
         {
            outList = getSubtagsByClass(inClassType);
            for (T subtag : outList)
            {
               removeSubtag(subtag);
            }
         }
      }

      return outList;
   }

   //---------------------------------------------------------------------------
   public synchronized void clearSubtags()
   {
      if (mContentAndSubtagList != null)
      {
         for (int i = 0; i < mContentAndSubtagList.size(); i++)
         {
            Object content = mContentAndSubtagList.get(i);

            try
            {
               XMLizable subtag = (XMLizable) content;
               mContentAndSubtagList.remove(i--);
            }
            catch (ClassCastException e)
            {
               // Ignore. Content or Comment.
            }
         }
      }
   }

   //---------------------------------------------------------------------------
   public synchronized int indexOf(XMLizable inSubtag)
   {
      int index = -1;
      if (mContentAndSubtagList != null)
      {
         for (int i = 0; i < mContentAndSubtagList.size(); i++)
         {
            Object content = mContentAndSubtagList.get(i);
            if (content.equals(inSubtag))
            {
               index = i;
               break;
            }
         }
      }

      return index;
   }


   //---------------------------------------------------------------------------
   public int getTotalTagCount()
   {
      int count = 1;

      if (mContentAndSubtagList != null)
      {
         for (Object content : mContentAndSubtagList)
         {
            try
            {
               XMLNode subtag = (XMLNode) content;
               count += subtag.getTotalTagCount();
            }
            catch (ClassCastException e)
            {
               // Ignore. Content or Comment.
            }
         }
      }

      return count;
   }

   //---------------------------------------------------------------------------
   public  T getRequiredSubtagByName(XMLName inTagName)
   {
      return getRequiredSubtagByName(inTagName.getLocalName());
   }

   //---------------------------------------------------------------------------
   public  T getRequiredSubtagByName(String inTagName)
   {
      List subtags = getSubtagsByName(inTagName);
      if (!CollectionUtil.hasValues(subtags))
      {
         throw new XMLException("The required " + inTagName + " subtag was not found!");
      }
      else if (subtags.size() > 1)
      {
         throw new XMLException(subtags.size() + " " + inTagName + " subtags found!");
      }

      return subtags.get(0);
   }

   //---------------------------------------------------------------------------
   public  T getOptionalSubtagByName(XMLName inTagName)
   {
      return getOptionalSubtagByName(inTagName.getLocalName());
   }

   //---------------------------------------------------------------------------
   public  T getOptionalSubtagByName(String inTagName)
   {
      List subtags = getSubtagsByName(inTagName);
      if (subtags.size() > 1)
      {
         throw new XMLException(subtags.size() + " " + inTagName + " subtags found!");
      }

      return subtags.size() == 1 ? subtags.get(0) : null;
   }

   //---------------------------------------------------------------------------
   public  List getSubtagsByName(XMLName inTagName)
   {
      return getSubtagsByName(inTagName.getLocalName(), Recursion.OFF);
   }

   //---------------------------------------------------------------------------
   public  List getSubtagsByName(String inTagName)
   {
      return getSubtagsByName(inTagName, Recursion.OFF);
   }

   //---------------------------------------------------------------------------
   public  List getSubtagsByName(XMLName inTagName, Recursion inRecursion)
   {
      return getSubtagsByName(inTagName.getLocalName(), inRecursion);
   }

   //---------------------------------------------------------------------------
   public  List getSubtagsByName(String inTagName, Recursion inRecursion)
   {
      List outList = new ArrayList();

      if (inTagName != null)
      {
         if (inRecursion == Recursion.ON)
         {
            recursivelyGetSubtagsByName(inTagName, this, outList, sMode.FIND);
         }
         else
         {
            if (mContentAndSubtagList != null)
            {
               for (Object content : mContentAndSubtagList)
               {
                  if (content instanceof XMLNode
                     && ((XMLNode)content).getTagName().equals(inTagName))
                  {
                     outList.add((T)content);
                  }
               }
            }
         }
      }

      return outList;
   }

   //---------------------------------------------------------------------------
   public synchronized  List removeSubtagsByName(XMLName inTagName)
   {
      return removeSubtagsByName(inTagName.getLocalName());
   }

   //---------------------------------------------------------------------------
   public synchronized  List removeSubtagsByName(String inTagName)
   {
      return removeSubtagsByName(inTagName, Recursion.OFF);
   }

   //---------------------------------------------------------------------------
   /**
    Removes all subtags with the specified tag name from this XMLTag object
    (if recursion is off) or at any level below this object (if recursion is on).
    @param inTagName the name of the subtags to remove
    @param inRecursion flag to indicate whether or not recursion should be used
    @return the list of removed subtags
    */
   public synchronized  List removeSubtagsByName(String inTagName, Recursion inRecursion)
   {
      List outList = new ArrayList();
      if (inTagName != null)
      {
         if (inRecursion == Recursion.ON)
         {
            recursivelyGetSubtagsByName(inTagName, this, outList, sMode.REMOVE);
         }
         else
         {
            outList = getSubtagsByName(inTagName);
            for (XMLNode subtag : outList)
            {
               removeSubtag(subtag);
            }
         }
      }

      return outList;
   }


   //---------------------------------------------------------------------------
   public void setParentNode(XMLContainer inParent)
   {
      mParent = inParent;
   }

   //---------------------------------------------------------------------------
   public XMLContainer getParentNode()
   {
      return mParent;
   }

   //---------------------------------------------------------------------------
   public XMLContainer getPreviousSibling()
   {
      XMLContainer requestedSibling = null;
      if (getParentNode() != null)
      {
         List siblings = getParentNode().getSubtags();
         int index = siblings.indexOf(this) - 1;
         while (index >= 0)
         {
            XMLizable sibling = siblings.get(index);
            if (sibling instanceof XMLContainer)
            {
               requestedSibling = (XMLContainer) sibling;
               break;
            }
            index--;
         }
      }

      return requestedSibling;
   }

   //---------------------------------------------------------------------------
   public XMLContainer getNextSibling()
   {
      XMLContainer requestedSibling = null;
      if (getParentNode() != null)
      {
         List siblings = getParentNode().getSubtags();
         int index = siblings.indexOf(this) + 1;
         while (index < siblings.size())
         {
            XMLizable sibling = siblings.get(index);
            if (sibling instanceof XMLContainer)
            {
               requestedSibling = (XMLContainer) sibling;
               break;
            }

            index++;
         }
      }

      return requestedSibling;
   }

   //###########################################################################
   // PROTECTED METHODS
   //###########################################################################

   //---------------------------------------------------------------------------
   protected String innerHTML()
   {
      StringBuilderPlus html = new StringBuilderPlus();

      if (mContentAndSubtagList != null)
      {
         for (Object content : mContentAndSubtagList)
         {
            if (content instanceof String)
            {
               html.append(content.toString());
            }
            else if (content instanceof byte[])
            {
               html.append(GZIP.uncompressToString((byte[]) content));
            }
            else
            {
               XMLizable subtag = (XMLizable) content;
               html.append(subtag.toXML());
            }
         }
      }

      return html.toString();
   }

   //###########################################################################
   // PRIVATE METHODS
   //###########################################################################

   //--------------------------------------------------------------------------
   /**
    Adds content with or without escaping. Large content is compressed to save space.
    */
   private synchronized void addContent(CharSequence inContent, boolean escape)
   {
//        if (null == inContent || inContent.length() == 0) return;
      if (null == inContent) return;

      if (null == mContentAndSubtagList) mContentAndSubtagList = new ArrayList(2);


      String content = inContent.toString();
      if (escape)
      {
         content = XMLUtil.escapeContentIfNecessary(inContent.toString());
//         content = XMLUtil.escapeContent(inContent);
      }

      mContentAndSubtagList.add(content.length() > sCompressionThreshold ? (Object) GZIP.compress(content) : (Object) content);
   }


   //---------------------------------------------------------------------------
   private String getContent(boolean includeEmbeddedSubtags)
   {
      if (null == mContentAndSubtagList) return "";

      StringBuilder outContent = new StringBuilder();
      for (Object content : mContentAndSubtagList)
      {
         if (content instanceof String)
         {
            outContent.append(content);
         }
         else if (content instanceof byte[])
         {
            outContent.append(GZIP.uncompressToString((byte[]) content));
         }
         else if (content instanceof XMLizable)
         {
            if (includeEmbeddedSubtags)
            {
               XMLizable subTag = (XMLizable) content;
               outContent.append(subTag.toXML());
            }
         }
         else
         {
            throw new XMLException("Unexpected content type: '" + content + "'.");
         }
      }

      return outContent.toString();
   }

   //---------------------------------------------------------------------------
   private static  void recursivelyGetSubtagsByName(String inTagName, XMLContainerImpl inRootTag, List inList, sMode inMode)
   {
      List subtags = inRootTag.getSubtags();
      if (CollectionUtil.hasValues(subtags))
      {
         for (int i = 0; i < subtags.size(); i++)
         {
            XMLContainerImpl subtag = subtags.get(i);
            if (subtag instanceof XMLNode)
            {
               if (((XMLNode) subtag).getTagName().equals(inTagName))
               {
                  inList.add((T)subtag);
                  if (inMode == sMode.REMOVE)
                  {
                     inRootTag.removeSubtag(subtag);
                     subtags.remove(i);
                     i--;
                  }
               }
            }

            recursivelyGetSubtagsByName(inTagName, subtag, inList, inMode);
         }
      }
   }

   //---------------------------------------------------------------------------
   private static  void recursivelyGetSubtagsByClass(Class inClassType, XMLContainerImpl inRootTag, List inList, sMode inMode)
   {
      List subtags = inRootTag.getSubtags();
      if (CollectionUtil.hasValues(subtags))
      {
         for (int i = 0; i < subtags.size(); i++)
         {
            XMLContainerImpl subtag = subtags.get(i);

            if (inClassType.isInstance(subtag))
            {
               inList.add((T)subtag);

               if (inMode == sMode.REMOVE)
               {
                  inRootTag.removeSubtag(subtag);
                  subtags.remove(i);
                  i--;
               }
            }

            recursivelyGetSubtagsByClass(inClassType, subtag, inList, inMode);
         }
      }
   }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy