001package com.hfg.xml;
002
003
004import java.util.ArrayList;
005import java.util.Collection;
006import java.util.List;
007import java.util.regex.Pattern;
008
009import com.hfg.exception.ProgrammingException;
010import com.hfg.util.Case;
011import com.hfg.util.CompareUtil;
012import com.hfg.util.Recursion;
013import com.hfg.util.StringBuilderPlus;
014import com.hfg.util.collection.CollectionUtil;
015import com.hfg.util.io.GZIP;
016
017//------------------------------------------------------------------------------
018/**
019 XML object that can contain sub-objects but not a name or attributes.
020
021 @author J. Alex Taylor, hairyfatguy.com
022 */
023//------------------------------------------------------------------------------
024// com.hfg XML/HTML Coding Library
025//
026// This library is free software; you can redistribute it and/or
027// modify it under the terms of the GNU Lesser General Public
028// License as published by the Free Software Foundation; either
029// version 2.1 of the License, or (at your option) any later version.
030//
031// This library is distributed in the hope that it will be useful,
032// but WITHOUT ANY WARRANTY; without even the implied warranty of
033// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
034// Lesser General Public License for more details.
035//
036// You should have received a copy of the GNU Lesser General Public
037// License along with this library; if not, write to the Free Software
038// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
039//
040// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com
041// jataylor@hairyfatguy.com
042//------------------------------------------------------------------------------
043
044public abstract class XMLContainerImpl implements XMLContainer
045{
046   protected List         mContentAndSubtagList;
047   private   XMLContainer mParent;
048
049   private static final boolean ESCAPE = true;
050   private static final boolean NO_ESCAPE = false;
051   private static final boolean WITH_EMBEDDED_SUBTAGS = true;
052   private static final boolean WITHOUT_EMBEDDED_SUBTAGS = false;
053
054   protected static enum  sMode { FIND, REMOVE };
055   private static int   sCompressionThreshold = 1024;
056
057   //---------------------------------------------------------------------------
058   @Override
059   public XMLTag clone()
060   {
061      XMLTag cloneObj;
062      try
063      {
064         cloneObj = (XMLTag) super.clone();
065      }
066      catch (CloneNotSupportedException e)
067      {
068         throw new ProgrammingException(e);
069      }
070
071      if (mContentAndSubtagList != null)
072      {
073         cloneObj.mContentAndSubtagList = new ArrayList(mContentAndSubtagList.size());
074         for (Object content : mContentAndSubtagList)
075         {
076            if (content instanceof XMLizable)
077            {
078               XMLizable subtagClone = ((XMLizable)content).clone();
079               cloneObj.mContentAndSubtagList.add(subtagClone);
080               if (subtagClone instanceof XMLContainer)
081               {
082                  ((XMLContainer)subtagClone).setParentNode(cloneObj);
083               }
084            }
085            else
086            {
087               cloneObj.mContentAndSubtagList.add(content);
088            }
089         }
090      }
091
092      return cloneObj;
093   }
094
095   //---------------------------------------------------------------------------
096   public boolean hasContent()
097   {
098      boolean returnValue = false;
099
100      if (mContentAndSubtagList != null)
101      {
102         for (Object content : mContentAndSubtagList)
103         {
104            if (content instanceof String
105               || content instanceof byte[]) // Compressed content is stored as a byte[]
106            {
107               returnValue = true;
108               break;
109            }
110         }
111      }
112
113      return returnValue;
114   }
115
116   //---------------------------------------------------------------------------
117   public boolean hasContentOrSubtags()
118   {
119      return (CollectionUtil.hasValues(mContentAndSubtagList));
120   }
121
122   //---------------------------------------------------------------------------
123   public List getContentPlusSubtagList()
124   {
125      return mContentAndSubtagList;
126   }
127
128
129   //---------------------------------------------------------------------------
130   public synchronized XMLContainer setContent(CharSequence inContent)
131   {
132      clearContent();
133
134      addContent(inContent);
135
136      return this;
137   }
138
139   //---------------------------------------------------------------------------
140   public synchronized XMLContainer setContent(int inContent)
141   {
142      return setContent(inContent + "");
143   }
144
145
146   //---------------------------------------------------------------------------
147   // Clears content (not subtags).
148   public synchronized void clearContent()
149   {
150      if (mContentAndSubtagList != null)
151      {
152         for (int i = 0; i < mContentAndSubtagList.size(); i++)
153         {
154            Object content = mContentAndSubtagList.get(i);
155
156            // Ignore subtags and remove compressed or uncompressed content.
157            if (content instanceof String
158               || content instanceof byte[])
159            {
160               mContentAndSubtagList.remove(i--);
161            }
162         }
163      }
164   }
165
166   //---------------------------------------------------------------------------
167   public XMLContainer addContent(CharSequence inContent)
168   {
169      addContent(inContent, ESCAPE);
170
171      return this;
172   }
173
174   //---------------------------------------------------------------------------
175   public XMLContainer addContentWithoutEscaping(CharSequence inContent)
176   {
177      addContent(inContent, NO_ESCAPE);
178
179      return this;
180   }
181
182   //---------------------------------------------------------------------------
183   public String getContent()
184   {
185      return getContent(WITHOUT_EMBEDDED_SUBTAGS);
186   }
187
188   //---------------------------------------------------------------------------
189   /**
190    The returned content also contains any embedded subtags.
191    */
192   private String getContentWithEmbeddedSubtags()
193   {
194      return getContent(WITH_EMBEDDED_SUBTAGS);
195   }
196
197   //----------------------------------------------------------------------------
198   public String getUnescapedContent()
199   {
200      return XMLUtil.unescapeContent(getContent());
201   }
202
203   //---------------------------------------------------------------------------
204   public synchronized void addSubtag(XMLizable inSubtag)
205   {
206      if (null == inSubtag)
207      {
208         throw new RuntimeException("The added xml subTag cannot be null");
209      }
210
211      if (null == mContentAndSubtagList) mContentAndSubtagList = new ArrayList(2);
212
213      mContentAndSubtagList.add(inSubtag);
214
215      if (inSubtag instanceof XMLContainer)
216      {
217         ((XMLContainer)inSubtag).setParentNode(this);
218      }
219   }
220
221   //---------------------------------------------------------------------------
222   /**
223    Added the specified subtag at the specified position in the list of this tag's
224    children.
225    @param inIndex the index at which the specified subtag should be added
226    @param inSubtag the subtag which should be added to this tag
227    */
228   public synchronized void addSubtag(int inIndex, XMLizable inSubtag)
229   {
230      if (null == inSubtag)
231      {
232         throw new RuntimeException("The added xml subTag cannot be null");
233      }
234
235      if (null == mContentAndSubtagList) mContentAndSubtagList = new ArrayList(2);
236
237      if (inIndex > mContentAndSubtagList.size())  // Prevent trying to insert past the end
238      {
239         inIndex = mContentAndSubtagList.size();
240      }
241
242      mContentAndSubtagList.add(inIndex, inSubtag);
243
244      if (inSubtag instanceof XMLContainer)
245      {
246         ((XMLContainer)inSubtag).setParentNode(this);
247      }
248   }
249
250   //---------------------------------------------------------------------------
251   public synchronized void addSubtags(Collection<? extends XMLizable> inSubtags)
252   {
253      if (null == inSubtags)
254      {
255         throw new RuntimeException("The added Collection of xml subTags cannot be null");
256      }
257
258      if (null == mContentAndSubtagList) mContentAndSubtagList = new ArrayList(inSubtags.size());
259
260      for (XMLizable subtag : inSubtags)
261      {
262         addSubtag(subtag);
263      }
264   }
265
266   //---------------------------------------------------------------------------
267   public synchronized <T extends XMLizable> void setSubtags(List<T> inSubtags)
268   {
269      clearSubtags();
270      if (CollectionUtil.hasValues(inSubtags))
271      {
272         addSubtags(inSubtags);
273      }
274   }
275
276   //---------------------------------------------------------------------------
277   public <T extends XMLizable> List<T> getSubtags()
278   {
279      List<T> subtagList = new ArrayList<T>(5);
280
281      if (mContentAndSubtagList != null)
282      {
283         for (Object content : mContentAndSubtagList)
284         {
285            if (content instanceof XMLizable)
286            {
287               subtagList.add((T)content);
288            }
289         }
290      }
291
292      return subtagList;
293   }
294
295   //---------------------------------------------------------------------------
296   public <T extends XMLNode> List<T> getXMLNodeSubtags()
297   {
298      List<T> subtagList = new ArrayList<>(5);
299
300      if (mContentAndSubtagList != null)
301      {
302         for (Object content : mContentAndSubtagList)
303         {
304            if (content instanceof XMLNode)
305            {
306               subtagList.add((T)content);
307            }
308         }
309      }
310
311      return subtagList;
312   }
313
314   //---------------------------------------------------------------------------
315   /**
316    Returns the subtag with the specified attribute name and value.
317    @param inAttributeName the attribute name to match on. Allows null.
318    @param inAttributeValue the attribute value to match on
319    @return the subtag which matched the attribute criteria
320    @throws RuntimeException if multiple subtags match the specified attribute criteria
321    */
322   public <T extends XMLNode> T getSubtagByAttribute(XMLName inAttributeName, String inAttributeValue)
323   {
324      return getSubtagByAttribute(inAttributeName != null ? inAttributeName.getLocalName() : null, inAttributeValue);
325   }
326
327   //---------------------------------------------------------------------------
328   /**
329    Returns the subtag with the specified attribute name and value.
330    @param inAttributeName the attribute name to match on. Allows null.
331    @param inAttributeValue the attribute value to match on
332    @return the subtag which matched the attribute criteria
333    @throws RuntimeException if multiple subtags match the specified attribute criteria
334    */
335   public <T extends XMLNode> T getSubtagByAttribute(String inAttributeName, String inAttributeValue)
336   {
337      T requestedSubtag = null;
338
339      List<T> subtags = getSubtagsByAttribute(inAttributeName, inAttributeValue);
340      if (CollectionUtil.hasValues(subtags))
341      {
342         if (subtags.size() > 1)
343         {
344            throw new RuntimeException(subtags.size() + " subtags matching the requested attribute ["
345                                       + inAttributeName + "=" + inAttributeValue + "] instead of the required 1!");
346         }
347         else
348         {
349            requestedSubtag = subtags.get(0);
350         }
351      }
352
353      return requestedSubtag;
354   }
355
356   //---------------------------------------------------------------------------
357   /**
358    Returns a List of all subtags with the specified attribute name and value.
359    @param inAttributeName the attribute name to match on. Allows null.
360    @param inAttributeValue the attribute value to match on
361    @return the list of all subtags which matched the attribute criteria
362    */
363   public <T extends XMLNode> List<T> getSubtagsByAttribute(XMLName inAttributeName, String inAttributeValue)
364   {
365      return getSubtagsByAttribute(inAttributeName != null ? inAttributeName.getLocalName() : null, inAttributeValue);
366   }
367
368   //---------------------------------------------------------------------------
369   /**
370    Returns a List of all subtags with the specified attribute name and value.
371    @param inAttributeName the attribute name to match on. Allows null.
372    @param inAttributeValue the attribute value to match on
373    @return the list of all subtags which matched the attribute criteria
374    */
375   public <T extends XMLNode> List<T> getSubtagsByAttribute(String inAttributeName, String inAttributeValue)
376   {
377      List<T> subtagList = null;
378
379      int index = inAttributeName.indexOf(":");
380      if (index > 0)
381      {
382         subtagList = getSubtagsByAttribute(new XMLName(inAttributeName.substring(index + 1), XMLNamespace.getNamespaceViaPrefix(inAttributeName.substring(0, index))), inAttributeValue);   
383      }
384      else
385      {
386         List<T> xmlNodes = getXMLNodeSubtags();
387         if (CollectionUtil.hasValues(xmlNodes))
388         {
389            subtagList = new ArrayList<>(5);
390            for (T node : xmlNodes)
391            {
392               if (inAttributeName != null)
393               {
394
395                  String attributeValue = node.getAttributeValue(inAttributeName);
396                  if (0 == CompareUtil.compare(attributeValue, inAttributeValue))
397                  {
398                     subtagList.add(node);
399                  }
400               }
401               else // Allow the attribute name to be null
402               {
403                  for (XMLAttribute attr : node.getAttributes())
404                  {
405                     if (0 == CompareUtil.compare(attr.getValue(), inAttributeValue))
406                     {
407                        subtagList.add(node);
408                        break;
409                     }
410                  }
411               }
412            }
413         }
414      }
415
416      return subtagList;
417   }
418
419   //---------------------------------------------------------------------------
420   /**
421    Returns a List of all subtags with the specified attribute name and value.
422    @param inAttributeName the attribute name to match on. Allows null.
423    @param inAttributeValuePattern the Pattern to compare to attribute values via find(). Allows null.
424    @return the list of all subtags which matched the attribute criteria
425    */
426   public <T extends XMLNode> List<T> getSubtagsByAttribute(String inAttributeName, Pattern inAttributeValuePattern)
427   {
428      List<T> subtagList = null;
429
430      List<T> xmlNodes = getXMLNodeSubtags();
431      if (CollectionUtil.hasValues(xmlNodes))
432      {
433         subtagList = new ArrayList<>(5);
434         for (T node : xmlNodes)
435         {
436            if (inAttributeName != null)
437            {
438               if (node.hasAttribute(inAttributeName))
439               {
440                  String attributeValue = node.getAttributeValue(inAttributeName);
441                  if (null == inAttributeValuePattern
442                      || (attributeValue != null
443                          && inAttributeValuePattern.matcher(attributeValue).find()))
444                  {
445                     subtagList.add(node);
446                  }
447               }
448            }
449            else // Allow the attribute name to be null
450            {
451               for (XMLAttribute attr : node.getAttributes())
452               {
453                  if (null == inAttributeValuePattern
454                        || inAttributeValuePattern.matcher(attr.getValue()).find())
455                  {
456                     subtagList.add(node);
457                     break;
458                  }
459               }
460            }
461         }
462      }
463
464      return subtagList;
465   }
466
467   //---------------------------------------------------------------------------
468   /**
469    Returns a List of all subtags with the specified attribute name and value
470    (if recursion is off) or at any level below this object (if recursion is on).
471    @param inAttributeName the attribute name to match on. Allows null.
472    @param inAttributeValue the attribute value to match on
473    @param inRecursion flag to indicate whether or not recursion should be used
474    @return the list of all subtags which matched the attribute criteria
475    */
476   public <T extends XMLNode> List<T> getSubtagsByAttribute(String inAttributeName, String inAttributeValue, Recursion inRecursion)
477   {
478      List<T> outList = new ArrayList<T>();
479
480      if (inRecursion == Recursion.ON)
481      {
482         Pattern attributeValuePattern = null;
483         if (inAttributeValue != null)
484         {
485            attributeValuePattern = Pattern.compile("^" + Pattern.quote(inAttributeValue) + "$");
486         }
487
488         recursivelyGetSubtagsByAttribute(inAttributeName, attributeValuePattern, this, outList, sMode.FIND); // Treat the attribute value as a literal pattern
489      }
490      else
491      {
492         outList = getSubtagsByAttribute(inAttributeName, inAttributeValue);
493         for (XMLNode subtag : outList)
494         {
495            removeSubtag(subtag);
496         }
497      }
498
499      return outList;
500   }
501
502   //---------------------------------------------------------------------------
503   /**
504    Returns a List of all subtags with the specified attribute name and value.
505    @param inAttributeName the attribute name to match on. Allows null.
506    @param inAttributeValuePattern the Pattern to compare to attribute values via find(). Allows null.
507    @param inRecursion flag to indicate whether or not recursion should be used
508    @return the list of all subtags which matched the attribute criteria
509    */
510   public <T extends XMLNode> List<T> getSubtagsByAttribute(String inAttributeName, Pattern inAttributeValuePattern, Recursion inRecursion)
511   {
512      List<T> outList = new ArrayList<T>();
513
514      if (inRecursion == Recursion.ON)
515      {
516         recursivelyGetSubtagsByAttribute(inAttributeName, inAttributeValuePattern, this, outList, sMode.FIND);
517      }
518      else
519      {
520         outList = getSubtagsByAttribute(inAttributeName, inAttributeValuePattern);
521      }
522
523      return outList;
524   }
525
526
527   //---------------------------------------------------------------------------
528   /**
529    Returns a List of all subtags of the specified Java Class on this XMLTag object
530    The same as getSubtagsByClass(inClassName, Recursion.OFF)
531    */
532   public <T> List<T> getSubtagsByClass(Class<T> inClassType)
533   {
534      return getSubtagsByClass(inClassType, Recursion.OFF);
535   }
536
537   //---------------------------------------------------------------------------
538   /**
539    Returns a List of all subtags of the specified Java Class on this XMLTag object
540    (if recursion is off) or at any level below this object (if recursion is on).
541    */
542   public <T> List<T> getSubtagsByClass(Class<T> inClassType, Recursion inRecursion)
543   {
544      List<T> outList = new ArrayList<T>();
545
546      if (inClassType != null)
547      {
548         if (inRecursion == Recursion.ON)
549         {
550            recursivelyGetSubtagsByClass(inClassType, this, outList, sMode.FIND);
551         }
552         else
553         {
554            if (mContentAndSubtagList != null)
555            {
556               for (Object content : mContentAndSubtagList)
557               {
558                  if (inClassType.isInstance(content))
559                  {
560                     outList.add((T)content);
561                  }
562               }
563            }
564         }
565      }
566
567      return outList;
568   }
569
570
571   //---------------------------------------------------------------------------
572   public synchronized void removeSubtag(XMLizable inSubtag)
573   {
574      if (mContentAndSubtagList != null)
575      {
576         // Remove all instances of the subtag in the list.
577         while (mContentAndSubtagList.remove(inSubtag))
578         {
579         }
580      }
581   }
582
583   //---------------------------------------------------------------------------
584   /**
585    Removes subtags of the specified Java Class on this object
586    The same as removeSubtagsByClass(inClassName, Recursion.OFF)
587    @return the List of removed objects
588    */
589   public <T extends XMLContainer> List<T> removeSubtagsByClass(Class<T> inClassType)
590   {
591      return removeSubtagsByClass(inClassType, Recursion.OFF);
592   }
593
594   //---------------------------------------------------------------------------
595   /**
596    Removes subtags of the specified Java Class on this XMLTag object
597    (if recursion is off) or at any level below this object (if recursion is on).
598    @return the List of removed objects
599    */
600   public <T extends XMLContainer> List<T> removeSubtagsByClass(Class<T> inClassType, Recursion inRecursion)
601   {
602      List<T> outList = new ArrayList<T>();
603
604      if (inClassType != null)
605      {
606         if (inRecursion == Recursion.ON)
607         {
608            recursivelyGetSubtagsByClass(inClassType, this, outList, sMode.REMOVE);
609         }
610         else
611         {
612            outList = getSubtagsByClass(inClassType);
613            for (T subtag : outList)
614            {
615               removeSubtag(subtag);
616            }
617         }
618      }
619
620      return outList;
621   }
622
623   //---------------------------------------------------------------------------
624   public synchronized void clearSubtags()
625   {
626      if (mContentAndSubtagList != null)
627      {
628         for (int i = 0; i < mContentAndSubtagList.size(); i++)
629         {
630            Object content = mContentAndSubtagList.get(i);
631
632            try
633            {
634               XMLizable subtag = (XMLizable) content;
635               mContentAndSubtagList.remove(i--);
636            }
637            catch (ClassCastException e)
638            {
639               // Ignore. Content or Comment.
640            }
641         }
642      }
643   }
644
645   //---------------------------------------------------------------------------
646   public synchronized int indexOf(XMLizable inSubtag)
647   {
648      int index = -1;
649      if (mContentAndSubtagList != null)
650      {
651         for (int i = 0; i < mContentAndSubtagList.size(); i++)
652         {
653            Object content = mContentAndSubtagList.get(i);
654            if (content.equals(inSubtag))
655            {
656               index = i;
657               break;
658            }
659         }
660      }
661
662      return index;
663   }
664
665
666   //---------------------------------------------------------------------------
667   public int getTotalTagCount()
668   {
669      int count = 1;
670
671      if (mContentAndSubtagList != null)
672      {
673         for (Object content : mContentAndSubtagList)
674         {
675            try
676            {
677               XMLNode subtag = (XMLNode) content;
678               count += subtag.getTotalTagCount();
679            }
680            catch (ClassCastException e)
681            {
682               // Ignore. Content or Comment.
683            }
684         }
685      }
686
687      return count;
688   }
689
690   //---------------------------------------------------------------------------
691   public <T extends XMLNode> T getRequiredSubtagByName(XMLName inTagName)
692   {
693      return getRequiredSubtagByName(inTagName.getLocalName());
694   }
695
696   //---------------------------------------------------------------------------
697   public <T extends XMLNode> T getRequiredSubtagByName(String inTagName)
698   {
699      List<T> subtags = getSubtagsByName(inTagName);
700      if (!CollectionUtil.hasValues(subtags))
701      {
702         throw new XMLException("The required " + inTagName + " subtag was not found!");
703      }
704      else if (subtags.size() > 1)
705      {
706         throw new XMLException(subtags.size() + " " + inTagName + " subtags found!");
707      }
708
709      return subtags.get(0);
710   }
711
712   //---------------------------------------------------------------------------
713   public <T extends XMLNode> T getRequiredSubtagById(String inId)
714   {
715      List<T> subtags = getSubtagsByAttribute("id", inId);
716      if (! CollectionUtil.hasValues(subtags))
717      {
718         throw new XMLException("Subtag with the required id '" + inId + "' subtag was not found!");
719      }
720      else if (subtags.size() > 1)
721      {
722         throw new XMLException(subtags.size() + " " + "subtags found with id '" + inId + "'!");
723      }
724
725      return subtags.get(0);
726   }
727
728   //---------------------------------------------------------------------------
729   public <T extends XMLNode> T getRequiredSubtagById(String inId, Recursion inRecursion)
730   {
731      List<T> subtags = getSubtagsByAttribute("id", inId, inRecursion);
732      if (! CollectionUtil.hasValues(subtags))
733      {
734         throw new XMLException("Subtag with the required id '" + inId + "' subtag was not found!");
735      }
736      else if (subtags.size() > 1)
737      {
738         throw new XMLException(subtags.size() + " " + "subtags found with id '" + inId + "'!");
739      }
740
741      return subtags.get(0);
742   }
743
744   //---------------------------------------------------------------------------
745   public <T extends XMLNode> T getOptionalSubtagByName(XMLName inTagName)
746   {
747      return getOptionalSubtagByName(inTagName.getLocalName(), Case.SENSITIVE);
748   }
749
750   //---------------------------------------------------------------------------
751   public <T extends XMLNode> T getOptionalSubtagByName(XMLName inTagName, Case inCaseSensitivity)
752   {
753      return getOptionalSubtagByName(inTagName.getLocalName(), inCaseSensitivity);
754   }
755
756   //---------------------------------------------------------------------------
757   public <T extends XMLNode> T getOptionalSubtagByName(String inTagName)
758   {
759      return getOptionalSubtagByName(inTagName, Case.SENSITIVE);
760   }
761
762   //---------------------------------------------------------------------------
763   public <T extends XMLNode> T getOptionalSubtagByName(String inTagName, Case inCaseSensitivity)
764   {
765      List<T> subtags = getSubtagsByName(inTagName, inCaseSensitivity, Recursion.OFF);
766      if (subtags.size() > 1)
767      {
768         throw new XMLException(subtags.size() + " " + inTagName + " subtags found!");
769      }
770
771      return subtags.size() == 1 ? subtags.get(0) : null;
772   }
773
774   //---------------------------------------------------------------------------
775   public <T extends XMLNode> List<T> getSubtagsByName(XMLName inTagName)
776   {
777      return getSubtagsByName(inTagName.getLocalName(), Recursion.OFF);
778   }
779
780   //---------------------------------------------------------------------------
781   public <T extends XMLNode> List<T> getSubtagsByName(String inTagName)
782   {
783      return getSubtagsByName(inTagName, Recursion.OFF);
784   }
785
786   //---------------------------------------------------------------------------
787   public <T extends XMLNode> List<T> getSubtagsByName(XMLName inTagName, Recursion inRecursion)
788   {
789      return getSubtagsByName(inTagName.getLocalName(), inRecursion);
790   }
791
792   //---------------------------------------------------------------------------
793   public <T extends XMLNode> List<T> getSubtagsByName(String inTagName, Recursion inRecursion)
794   {
795      return getSubtagsByName(inTagName, Case.SENSITIVE, inRecursion);
796   }
797
798   //---------------------------------------------------------------------------
799   public <T extends XMLNode> List<T> getSubtagsByName(String inTagName, Case inCaseSensitivity, Recursion inRecursion)
800   {
801      List<T> outList = new ArrayList<T>();
802
803      if (inTagName != null)
804      {
805         if (inRecursion == Recursion.ON)
806         {
807            recursivelyGetSubtagsByName(inTagName, this, outList, sMode.FIND);
808         }
809         else
810         {
811            if (mContentAndSubtagList != null)
812            {
813               for (Object content : mContentAndSubtagList)
814               {
815                  if (content instanceof XMLNode)
816                  {
817                     String tagName = ((XMLNode) content).getTagName();
818                     if (tagName != null
819                         && (inCaseSensitivity.equals(Case.SENSITIVE) ? tagName.equals(inTagName) :
820                                                                        tagName.equalsIgnoreCase(inTagName)))
821                     {
822                        outList.add((T) content);
823                     }
824                  }
825               }
826            }
827         }
828      }
829
830      return outList;
831   }
832
833   //---------------------------------------------------------------------------
834   public synchronized <T extends XMLNode> List<T> removeSubtagsByName(XMLName inTagName)
835   {
836      return removeSubtagsByName(inTagName.getLocalName());
837   }
838
839   //---------------------------------------------------------------------------
840   public synchronized <T extends XMLNode> List<T> removeSubtagsByName(String inTagName)
841   {
842      return removeSubtagsByName(inTagName, Recursion.OFF);
843   }
844
845   //---------------------------------------------------------------------------
846   /**
847    Removes all subtags with the specified tag name from this XMLTag object
848    (if recursion is off) or at any level below this object (if recursion is on).
849    @param inTagName the name of the subtags to remove
850    @param inRecursion flag to indicate whether or not recursion should be used
851    @return the list of removed subtags
852    */
853   public synchronized <T extends XMLNode> List<T> removeSubtagsByName(String inTagName, Recursion inRecursion)
854   {
855      List<T> outList = new ArrayList<T>();
856      if (inTagName != null)
857      {
858         if (inRecursion == Recursion.ON)
859         {
860            recursivelyGetSubtagsByName(inTagName, this, outList, sMode.REMOVE);
861         }
862         else
863         {
864            outList = getSubtagsByName(inTagName);
865            for (XMLNode subtag : outList)
866            {
867               removeSubtag(subtag);
868            }
869         }
870      }
871
872      return outList;
873   }
874
875   //---------------------------------------------------------------------------
876   /**
877    Removes all subtags with the specified tag name from this XMLTag object
878    (if recursion is off) or at any level below this object (if recursion is on).
879    @param inAttributeName the attribute name to match on. Allows null.
880    @param inAttributeValue the attribute value to match on
881    @param inRecursion flag to indicate whether or not recursion should be used
882    @return the list of removed subtags
883    */
884   public synchronized <T extends XMLNode> List<T> removeSubtagsByAttribute(XMLName inAttributeName, String inAttributeValue, Recursion inRecursion)
885   {
886      return removeSubtagsByAttribute(inAttributeName != null ? inAttributeName.getLocalName() : null, inAttributeValue, inRecursion);
887   }
888
889   //---------------------------------------------------------------------------
890   /**
891    Removes all subtags with the specified tag name from this XMLTag object
892    (if recursion is off) or at any level below this object (if recursion is on).
893    @param inAttributeName the attribute name to match on. Allows null.
894    @param inAttributeValue the attribute value to match on
895    @param inRecursion flag to indicate whether or not recursion should be used
896    @return the list of removed subtags
897    */
898   public synchronized <T extends XMLNode> List<T> removeSubtagsByAttribute(String inAttributeName, String inAttributeValue, Recursion inRecursion)
899   {
900      List<T> outList = new ArrayList<T>();
901
902      if (inRecursion == Recursion.ON)
903      {
904         Pattern attributeValuePattern = null;
905         if (inAttributeValue != null)
906         {
907            attributeValuePattern = Pattern.compile(Pattern.quote(inAttributeValue)); // Treat the attribute value as a literal pattern
908         }
909
910         recursivelyGetSubtagsByAttribute(inAttributeName, attributeValuePattern, this, outList, sMode.REMOVE);
911      }
912      else
913      {
914         outList = getSubtagsByAttribute(inAttributeName, inAttributeValue);
915         for (XMLNode subtag : outList)
916         {
917            removeSubtag(subtag);
918         }
919      }
920
921      return outList;
922   }
923
924   //---------------------------------------------------------------------------
925   /**
926    Removes all subtags with the specified tag name from this XMLTag object
927    (if recursion is off) or at any level below this object (if recursion is on).
928    @param inAttributeName the attribute name to match on. Allows null.
929    @param inAttributeValuePattern the attribute value pattern to match with
930    @param inRecursion flag to indicate whether or not recursion should be used
931    @return the list of removed subtags
932    */
933   public synchronized <T extends XMLNode> List<T> removeSubtagsByAttribute(String inAttributeName, Pattern inAttributeValuePattern, Recursion inRecursion)
934   {
935      List<T> outList = new ArrayList<T>();
936
937      if (inRecursion == Recursion.ON)
938      {
939         recursivelyGetSubtagsByAttribute(inAttributeName, inAttributeValuePattern, this, outList, sMode.REMOVE);
940      }
941      else
942      {
943         outList = getSubtagsByAttribute(inAttributeName, inAttributeValuePattern);
944         for (XMLNode subtag : outList)
945         {
946            removeSubtag(subtag);
947         }
948      }
949
950      return outList;
951   }
952
953
954   //---------------------------------------------------------------------------
955   public void setParentNode(XMLContainer inParent)
956   {
957      mParent = inParent;
958   }
959
960   //---------------------------------------------------------------------------
961   public XMLContainer getParentNode()
962   {
963      return mParent;
964   }
965
966   //---------------------------------------------------------------------------
967   public XMLContainer getPreviousSibling()
968   {
969      XMLContainer requestedSibling = null;
970      if (getParentNode() != null)
971      {
972         List<XMLizable> siblings = getParentNode().getSubtags();
973         int index = siblings.indexOf(this) - 1;
974         while (index >= 0)
975         {
976            XMLizable sibling = siblings.get(index);
977            if (sibling instanceof XMLContainer)
978            {
979               requestedSibling = (XMLContainer) sibling;
980               break;
981            }
982            index--;
983         }
984      }
985
986      return requestedSibling;
987   }
988
989   //---------------------------------------------------------------------------
990   public XMLContainer getNextSibling()
991   {
992      XMLContainer requestedSibling = null;
993      if (getParentNode() != null)
994      {
995         List<XMLizable> siblings = getParentNode().getSubtags();
996         int index = siblings.indexOf(this) + 1;
997         while (index < siblings.size())
998         {
999            XMLizable sibling = siblings.get(index);
1000            if (sibling instanceof XMLContainer)
1001            {
1002               requestedSibling = (XMLContainer) sibling;
1003               break;
1004            }
1005
1006            index++;
1007         }
1008      }
1009
1010      return requestedSibling;
1011   }
1012
1013   //###########################################################################
1014   // PROTECTED METHODS
1015   //###########################################################################
1016
1017   //---------------------------------------------------------------------------
1018   protected String innerHTML()
1019   {
1020      StringBuilderPlus html = new StringBuilderPlus();
1021
1022      if (mContentAndSubtagList != null)
1023      {
1024         for (Object content : mContentAndSubtagList)
1025         {
1026            if (content instanceof String)
1027            {
1028               html.append(content.toString());
1029            }
1030            else if (content instanceof byte[])
1031            {
1032               html.append(GZIP.uncompressToString((byte[]) content));
1033            }
1034            else
1035            {
1036               XMLizable subtag = (XMLizable) content;
1037               html.append(subtag.toXML());
1038            }
1039         }
1040      }
1041
1042      return html.toString();
1043   }
1044
1045   //###########################################################################
1046   // PRIVATE METHODS
1047   //###########################################################################
1048
1049   //--------------------------------------------------------------------------
1050   /**
1051    Adds content with or without escaping. Large content is compressed to save space.
1052    */
1053   private synchronized void addContent(CharSequence inContent, boolean escape)
1054   {
1055//        if (null == inContent || inContent.length() == 0) return;
1056      if (null == inContent) return;
1057
1058      if (null == mContentAndSubtagList) mContentAndSubtagList = new ArrayList(2);
1059
1060
1061      String content = inContent.toString();
1062      if (escape)
1063      {
1064         content = XMLUtil.escapeContentIfNecessary(inContent.toString());
1065//         content = XMLUtil.escapeContent(inContent);
1066      }
1067
1068      mContentAndSubtagList.add(content.length() > sCompressionThreshold ? (Object) GZIP.compress(content) : (Object) content);
1069   }
1070
1071
1072   //---------------------------------------------------------------------------
1073   private String getContent(boolean includeEmbeddedSubtags)
1074   {
1075      if (null == mContentAndSubtagList) return "";
1076
1077      StringBuilder outContent = new StringBuilder();
1078      for (Object content : mContentAndSubtagList)
1079      {
1080         if (content instanceof String)
1081         {
1082            outContent.append(content);
1083         }
1084         else if (content instanceof byte[])
1085         {
1086            outContent.append(GZIP.uncompressToString((byte[]) content));
1087         }
1088         else if (content instanceof XMLizable)
1089         {
1090            if (includeEmbeddedSubtags)
1091            {
1092               XMLizable subTag = (XMLizable) content;
1093               outContent.append(subTag.toXML());
1094            }
1095         }
1096         else
1097         {
1098            throw new XMLException("Unexpected content type: '" + content + "'.");
1099         }
1100      }
1101
1102      return outContent.toString();
1103   }
1104
1105   //---------------------------------------------------------------------------
1106   private static <T extends XMLNode> void recursivelyGetSubtagsByName(String inTagName, XMLContainer inRootTag, List<T> inList, sMode inMode)
1107   {
1108      List<? extends XMLContainerImpl> subtags = inRootTag.getSubtags();
1109      if (CollectionUtil.hasValues(subtags))
1110      {
1111         for (int i = 0; i < subtags.size(); i++)
1112         {
1113            XMLizable subtag = subtags.get(i);
1114            if (subtag instanceof XMLContainer)
1115            {
1116               if (subtag instanceof XMLNode)
1117               {
1118                  if (((XMLNode) subtag).getTagName().equals(inTagName))
1119                  {
1120                     inList.add((T) subtag);
1121                     if (inMode == sMode.REMOVE)
1122                     {
1123                        inRootTag.removeSubtag(subtag);
1124                        subtags.remove(i);
1125                        i--;
1126                     }
1127                  }
1128               }
1129
1130               recursivelyGetSubtagsByName(inTagName, (XMLContainer) subtag, inList, inMode);
1131            }
1132         }
1133      }
1134   }
1135
1136   //---------------------------------------------------------------------------
1137      private static <T extends XMLNode> void recursivelyGetSubtagsByAttribute(String inAttributeName, Pattern inAttributeValuePattern, XMLContainerImpl inRootTag, List<T> inList, sMode inMode)
1138   {
1139      List<? extends XMLContainerImpl> subtags = inRootTag.getSubtags();
1140      if (CollectionUtil.hasValues(subtags))
1141      {
1142         for (int i = 0; i < subtags.size(); i++)
1143         {
1144            XMLizable subtag = subtags.get(i);
1145            if (! (subtag instanceof XMLContainerImpl))
1146            {
1147               // Skip comments and CDATA
1148               continue;
1149            }
1150
1151            if (subtag instanceof XMLNode)
1152            {
1153               if (inAttributeName != null)
1154               {
1155                  String attributeValue = ((XMLNode) subtag).getAttributeValue(inAttributeName);
1156                  if (null == inAttributeValuePattern
1157                      || (attributeValue != null
1158                        && inAttributeValuePattern.matcher(attributeValue).find()))
1159                  {
1160                     inList.add((T)subtag);
1161                     if (inMode == sMode.REMOVE)
1162                     {
1163                        inRootTag.removeSubtag(subtag);
1164                        subtags.remove(i);
1165                        i--;
1166                     }
1167                  }
1168               }
1169               else // Allow the attribute name to be null
1170               {
1171                  for (XMLAttribute attr : ((XMLTag)subtag).getAttributes())
1172                  {
1173                     if (null == inAttributeValuePattern
1174                           || (attr.getValue() != null
1175                           && inAttributeValuePattern.matcher(attr.getValue()).find()))
1176                     {
1177                        inList.add((T)subtag);
1178                        if (inMode == sMode.REMOVE)
1179                        {
1180                           inRootTag.removeSubtag(subtag);
1181                           subtags.remove(i);
1182                           i--;
1183                        }
1184                     }
1185                  }
1186               }
1187            }
1188
1189            recursivelyGetSubtagsByAttribute(inAttributeName, inAttributeValuePattern, (XMLContainerImpl) subtag, inList, inMode);
1190         }
1191      }
1192   }
1193
1194   //---------------------------------------------------------------------------
1195   private static <T> void recursivelyGetSubtagsByClass(Class<T> inClassType, XMLContainerImpl inRootTag, List<T> inList, sMode inMode)
1196   {
1197      List<? extends XMLContainerImpl> subtags = inRootTag.getSubtags();
1198      if (CollectionUtil.hasValues(subtags))
1199      {
1200         for (int i = 0; i < subtags.size(); i++)
1201         {
1202            XMLContainerImpl subtag = subtags.get(i);
1203
1204            if (inClassType.isInstance(subtag))
1205            {
1206               inList.add((T)subtag);
1207
1208               if (inMode == sMode.REMOVE)
1209               {
1210                  inRootTag.removeSubtag(subtag);
1211                  subtags.remove(i);
1212                  i--;
1213               }
1214            }
1215
1216            recursivelyGetSubtagsByClass(inClassType, subtag, inList, inMode);
1217         }
1218      }
1219   }
1220
1221}