com.hfg.xml.XMLContainerImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of com_hfg Show documentation
Show all versions of com_hfg Show documentation
com.hfg xml, html, svg, and bioinformatics utility library
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 extends XMLizable> 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 extends XMLContainerImpl> 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 extends XMLContainerImpl> 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);
}
}
}
}