Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/test/tools/pack200/pack200-verifier/src/xmlkit/XMLKit.java
38867 views
/*1* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/24package xmlkit; // -*- mode: java; indent-tabs-mode: nil -*-2526// XML Implementation packages:27import java.util.*;2829import java.io.Reader;30import java.io.Writer;31import java.io.OutputStream;32import java.io.InputStreamReader;33import java.io.OutputStreamWriter;34import java.io.BufferedReader;35import java.io.PrintWriter;36import java.io.StringWriter;37import java.io.StringReader;3839import java.io.IOException;4041import org.xml.sax.XMLReader;42import org.xml.sax.InputSource;43import org.xml.sax.ContentHandler;44import org.xml.sax.SAXException;45import org.xml.sax.SAXParseException;46import org.xml.sax.Attributes;47import org.xml.sax.ext.LexicalHandler;48import org.xml.sax.helpers.AttributesImpl;4950/**51* A kit of methods and classes useful for manipulating XML trees in52* memory. They are very compact and easy to use. An XML element53* occupies six pointers of overhead (like two arrays) plus a pointer54* for its name, each attribute name and value, and each sub-element.55* Many useful XML operations (or Lisp-like calls) can be accomplished56* with a single method call on an element itself.57* <p>58* There is strong integration with the Java collection classes.59* There are viewing and conversion operators to and from various60* collection types. Elements directly support list iterators.61* Most <tt>List</tt> methods work analogously on elements.62* <p>63* Because of implementation compromises, these XML trees are less64* functional than many standard XML classes.65* <ul>66* <li>There are no parent or sibling pointers in the tree.</li>67* <li>Attribute names are simple strings, with no namespaces.</li>68* <li>There is no internal support for schemas or validation.</li>69* </ul>70* <p>71* Here is a summary of functionality in <tt>XMLKit</tt>.72* (Overloaded groups of methods are summarized by marking some73* arguments optional with their default values. Some overloaded74* arguments are marked with their alternative types separated by75* a bar "|". Arguments or return values for which a null is76* specially significant are marked by an alternative "|null".77* Accessors which have corresponding setters are marked78* by "/set". Removers which have corresponding retainers are marked79* by "/retain".)80* <pre>81* --- element construction82* new Element(int elemCapacity=4), String name=""83* new Element(String name, String[] attrs={}, Element[] elems={}, int elemCapacity=4)84* new Element(String name, String[] attrs, Object[] elems, int elemCapacity=4)85* new Element(Element original) // shallow copy86* new Element(String name="", Collection elems) // coercion87*88* Element shallowCopy()89* Element shallowFreeze() // side-effecting90* Element deepCopy()91* Element deepFreeze() // not side-effecting92*93* EMPTY // frozen empty anonymous element94* void ensureExtraCapacity(int)95* void trimToSize()96* void sortAttrs() // sort by key97*98* --- field accessors99* String getName()/set100* int size()101* boolean isEmpty()102* boolean isFrozen()103* boolean isAnonymous()104* int getExtraCapacity()/set105* int attrSize()106*107* --- attribute accessors108* String getAttr(int i)/set109* String getAttrName(int i)110*111* String getAttr(String key)/set112* List getAttrList(String key)/set113* Number getAttrNumber(String key)/set114* long getAttrLong(String key)/set115* double getAttrDouble(String key)/set116*117* String getAttr(String key, String dflt=null)118* long getAttrLong(String key, long dflt=0)119* double getAttrDouble(String key, double dflt=0)120*121* Element copyAttrsOnly()122* Element getAttrs()/set => <em><><key>value</key>...</></em>123* void addAttrs(Element attrs)124*125* void removeAttr(int i)126* void clearAttrs()127*128* --- element accessors129* Object get(int i)/set130* Object getLast() | null131* Object[] toArray()132* Element copyContentOnly()133*134* void add(int i=0, Object subElem)135* int addAll(int i=0, Collection | Element elems)136* int addContent(int i=0, TokenList|Element|Object|null)137* void XMLKit.addContent(TokenList|Element|Object|null, Collection sink|null)138*139* void clear(int beg=0, int end=size)140* void sort(Comparator=contentOrder())141* void reverse()142* void shuffle(Random rnd=(anonymous))143* void rotate(int distance)144* Object min/max(Comparator=contentOrder())145*146* --- text accessors147* CharSequence getText()/set148* CharSequence getUnmarkedText()149* int addText(int i=size, CharSequence)150* void trimText();151*152* --- views153* List asList() // element view154* ListIterator iterator()155* PrintWriter asWriter()156* Map asAttrMap()157* Iterable<CharSequence> texts()158* Iterable<Element> elements()159* Iterable<T> partsOnly(Class<T>)160* String[] toStrings()161*162* --- queries163* boolean equals(Element | Object)164* int compareTo(Element | Object)165* boolean equalAttrs(Element)166* int hashCode()167* boolean isText() // every sub-elem is CharSequence168* boolean hasText() // some sub-elem is CharSequence169*170* boolean contains(Object)171* boolean containsAttr(String)172*173* int indexOf(Object)174* int indexOf(Filter, int fromIndex=0)175* int lastIndexOf(Object)176* int lastIndexOf(Filter, int fromIndex=size-1)177*178* int indexOfAttr(String)179*180* // finders, removers, and replacers do addContent of each filtered value181* // (i.e., TokenLists and anonymous Elements are broken out into their parts)182* boolean matches(Filter)183*184* Object find(Filter, int fromIndex=0)185* Object findLast(Filter, int fromIndex=size-1)186* Element findAll(Filter, int fromIndex=0 & int toIndex=size)187* int findAll(Filter, Collection sink | null, int fromIndex=0 & int toIndex=size)188*189* Element removeAllInTree(Filter)/retain190* int findAllInTree(Filter, Collection sink | null)191* int countAllInTree(Filter)192* Element removeAllInTree(Filter)/retain193* int removeAllInTree(Filter, Collection sink | null)/retain194* void replaceAllInTree(Filter)195*196* Element findElement(String name=any)197* Element findAllElements(String name=any)198*199* Element findWithAttr(String key, String value=any)200* Element findAllWithAttr(String key, String value=any)201*202* Element removeElement(String name=any)203* Element removeAllElements(String name=any)/retain204*205* Element removeWithAttr(String key, String value=any)206* Element removeAllWithAttr(String key, String value=any)/retain207*208* //countAll is the same as findAll but with null sink209* int countAll(Filter)210* int countAllElements(String name=any)211* int countAllWithAttr(String key, String value=any)212*213* void replaceAll(Filter, int fromIndex=0 & int toIndex=size)214* void replaceAllInTree(Filter)215* void XMLKit.replaceAll(Filter, List target) //if(fx){remove x;addContent fx}216*217* --- element mutators218* boolean remove(Object)219* Object remove(int)220* Object removeLast() | null221*222* Object remove(Filter, int fromIndex=0)223* Object removeLast(Filter, int fromIndex=size-1)224* Element sink = removeAll(Filter, int fromIndex=0 & int toIndex=size)/retain225* int count = removeAll(Filter, int fromIndex=0 & int toIndex=size, Collection sink | null)/retain226*227* Element removeAllElements(String name=any)228*229* --- attribute mutators230* ??int addAllAttrsFrom(Element attrSource)231*232* --- parsing and printing233* void tokenize(String delims=whitespace, returnDelims=false)234* void writeTo(Writer)235* void writePrettyTo(Writer)236* String prettyString()237* String toString()238*239* ContentHandler XMLKit.makeBuilder(Collection sink, tokenizing=false, makeFrozen=false) // for standard XML parser240* Element XMLKit.readFrom(Reader, tokenizing=false, makeFrozen=false)241* void XMLKit.prettyPrintTo(Writer | OutputStream, Element)242* class XMLKit.Printer(Writer) { void print/Recursive(Element) }243* void XMLKit.output(Object elem, ContentHandler, LexicalHandler=null)244* void XMLKit.writeToken(String, char quote, Writer)245* void XMLKit.writeCData(String, Writer)246* Number XMLKit.convertToNumber(String, Number dflt=null)247* long XMLKit.convertToLong(String, long dflt=0)248* double XMLKit.convertToDouble(String, double dflt=0)249*250* --- filters251* XMLKit.ElementFilter { Element filter(Element) }252* XMLKit.elementFilter(String name=any | Collection nameSet)253* XMLKit.AttrFilter(String key) { boolean test(String value) }254* XMLKit.attrFilter(String key, String value=any)255* XMLKit.attrFilter(Element matchThis, String key)256* XMLKit.classFilter(Class)257* XMLKit.textFilter() // matches any CharSequence258* XMLKit.specialFilter() // matches any Special element259* XMLKit.methodFilter(Method m, Object[] args=null, falseResult=null)260* XMLKit.testMethodFilter(Method m, Object[] args=null)261* XMLKit.not(Filter) // inverts sense of Filter262* XMLKit.and(Filter&Filter | Filter[])263* XMLKit.or(Filter&Filter | Filter[])264* XMLKit.stack(Filter&Filter | Filter[]) // result is (fx && g(fx))265* XMLKit.content(Filter, Collection sink) // copies content to sink266* XMLKit.replaceInTree(Filter pre, Filter post=null) // pre-replace else recur267* XMLKit.findInTree(Filter pre, Collection sink=null) // pre-find else recur268* XMLKit.nullFilter() // ignores input, always returns null (i.e., false)269* XMLKit.selfFilter( ) // always returns input (i.e., true)270* XMLKit.emptyFilter() // ignores input, always returns EMPTY271* XMLKit.constantFilter(Object) // ignores input, always returns constant272*273* --- misc274* Comparator XMLKit.contentOrder() // for comparing/sorting mixed content275* Method XMLKit.Element.method(String name) // returns Element method276* </pre>277*278* @author jrose279*/280public abstract class XMLKit {281282private XMLKit() {283}284// We need at least this much slop if the element is to stay unfrozen.285static final int NEED_SLOP = 1;286static final Object[] noPartsFrozen = {};287static final Object[] noPartsNotFrozen = new Object[NEED_SLOP];288static final String WHITESPACE_CHARS = " \t\n\r\f";289static final String ANON_NAME = new String("*"); // unique copy of "*"290291public static final class Element implements Comparable<Element>, Iterable<Object> {292// Note: Does not implement List, because it has more293// significant parts besides its sub-elements. Therefore,294// hashCode and equals must be more distinctive than Lists.295296// <name> of element297String name;298// number of child elements, in parts[0..size-1]299int size;300// The parts start with child elements:: {e0, e1, e2, ...}.301// Following that are optional filler elements, all null.302// Following that are attributes as key/value pairs.303// They are in reverse: {...key2, val2, key1, val1, key0, val0}.304// Child elements and attr keys and values are never null.305Object[] parts;306307// Build a partially-constructed node.308// Caller is responsible for initializing promised attributes.309Element(String name, int size, int capacity) {310this.name = name.toString();311this.size = size;312assert (size <= capacity);313this.parts = capacity > 0 ? new Object[capacity] : noPartsFrozen;314}315316/** An anonymous, empty element.317* Optional elemCapacity argument is expected number of sub-elements.318*/319public Element() {320this(ANON_NAME, 0, NEED_SLOP + 4);321}322323public Element(int extraCapacity) {324this(ANON_NAME, 0, NEED_SLOP + Math.max(0, extraCapacity));325}326327/** An empty element with the given name.328* Optional extraCapacity argument is expected number of sub-elements.329*/330public Element(String name) {331this(name, 0, NEED_SLOP + 4);332}333334public Element(String name, int extraCapacity) {335this(name, 0, NEED_SLOP + Math.max(0, extraCapacity));336}337338/** An empty element with the given name and attributes.339* Optional extraCapacity argument is expected number of sub-elements.340*/341public Element(String name, String... attrs) {342this(name, attrs, (Element[]) null, 0);343}344345public Element(String name, String[] attrs, int extraCapacity) {346this(name, attrs, (Element[]) null, extraCapacity);347}348349/** An empty element with the given name and sub-elements.350* Optional extraCapacity argument is expected extra sub-elements.351*/352public Element(String name, Element... elems) {353this(name, (String[]) null, elems, 0);354}355356public Element(String name, Element[] elems, int extraCapacity) {357this(name, (String[]) null, elems, extraCapacity);358}359360/** An empty element with the given name, attributes, and sub-elements.361* Optional extraCapacity argument is expected extra sub-elements.362*/363public Element(String name, String[] attrs, Object... elems) {364this(name, attrs, elems, 0);365}366367public Element(String name, String[] attrs, Object[] elems, int extraCapacity) {368this(name, 0,369((elems == null) ? 0 : elems.length)370+ Math.max(0, extraCapacity)371+ NEED_SLOP372+ ((attrs == null) ? 0 : attrs.length));373int ne = ((elems == null) ? 0 : elems.length);374int na = ((attrs == null) ? 0 : attrs.length);375int fillp = 0;376for (int i = 0; i < ne; i++) {377if (elems[i] != null) {378parts[fillp++] = elems[i];379}380}381size = fillp;382for (int i = 0; i < na; i += 2) {383setAttr(attrs[i + 0], attrs[i + 1]);384}385}386387public Element(Collection c) {388this(c.size());389addAll(c);390}391392public Element(String name, Collection c) {393this(name, c.size());394addAll(c);395}396397/** Shallow copy. Same as old.shallowCopy().398* Optional extraCapacity argument is expected extra sub-elements.399*/400public Element(Element old) {401this(old, 0);402}403404public Element(Element old, int extraCapacity) {405this(old.name, old.size,406old.size407+ Math.max(0, extraCapacity) + NEED_SLOP408+ old.attrLength());409// copy sub-elements410System.arraycopy(old.parts, 0, parts, 0, size);411int alen = parts.length412- (size + Math.max(0, extraCapacity) + NEED_SLOP);413// copy attributes414System.arraycopy(old.parts, old.parts.length - alen,415parts, parts.length - alen,416alen);417assert (!isFrozen());418}419420/** Shallow copy. Same as new Element(this). */421public Element shallowCopy() {422return new Element(this);423}424static public final Element EMPTY = new Element(ANON_NAME, 0, 0);425426Element deepFreezeOrCopy(boolean makeFrozen) {427if (makeFrozen && isFrozen()) {428return this; // no need to copy it429}430int alen = attrLength();431int plen = size + (makeFrozen ? 0 : NEED_SLOP) + alen;432Element copy = new Element(name, size, plen);433// copy attributes434System.arraycopy(parts, parts.length - alen, copy.parts, plen - alen, alen);435// copy sub-elements436for (int i = 0; i < size; i++) {437Object e = parts[i];438String str;439if (e instanceof Element) { // recursion is common case440e = ((Element) e).deepFreezeOrCopy(makeFrozen);441} else if (makeFrozen) {442// Freeze StringBuffers, etc.443e = fixupString(e);444}445copy.setRaw(i, e);446}447return copy;448}449450/** Returns new Element(this), and also recursively copies sub-elements. */451public Element deepCopy() {452return deepFreezeOrCopy(false);453}454455/** Returns frozen version of deepCopy. */456public Element deepFreeze() {457return deepFreezeOrCopy(true);458}459460/** Freeze this element.461* Throw an IllegalArgumentException if any sub-element is not already frozen.462* (Use deepFreeze() to make a frozen copy of an entire element tree.)463*/464public void shallowFreeze() {465if (isFrozen()) {466return;467}468int alen = attrLength();469Object[] nparts = new Object[size + alen];470// copy attributes471System.arraycopy(parts, parts.length - alen, nparts, size, alen);472// copy sub-elements473for (int i = 0; i < size; i++) {474Object e = parts[i];475String str;476if (e instanceof Element) { // recursion is common case477if (!((Element) e).isFrozen()) {478throw new IllegalArgumentException("Sub-element must be frozen.");479}480} else {481// Freeze StringBuffers, etc.482e = fixupString(e);483}484nparts[i] = e;485}486parts = nparts;487assert (isFrozen());488}489490/** Return the name of this element. */491public String getName() {492return name;493}494495/** Change the name of this element. */496public void setName(String name) {497checkNotFrozen();498this.name = name.toString();499}500501/** Reports if the element's name is a particular string (spelled "*").502* Such elements are created by the nullary Element constructor,503* and by query functions which return multiple values,504* such as <tt>findAll</tt>.505*/506public boolean isAnonymous() {507return name == ANON_NAME;508}509510/** Return number of elements. (Does not include attributes.) */511public int size() {512return size;513}514515/** True if no elements. (Does not consider attributes.) */516public boolean isEmpty() {517return size == 0;518}519520/** True if this element does not allow modification. */521public boolean isFrozen() {522// It is frozen iff there is no slop space.523return !hasNulls(NEED_SLOP);524}525526void checkNotFrozen() {527if (isFrozen()) {528throw new UnsupportedOperationException("cannot modify frozen element");529}530}531532/** Remove specified elements. (Does not affect attributes.) */533public void clear() {534clear(0, size);535}536537public void clear(int beg) {538clear(beg, size);539}540541public void clear(int beg, int end) {542if (end > size) {543badIndex(end);544}545if (beg < 0 || beg > end) {546badIndex(beg);547}548if (beg == end) {549return;550}551checkNotFrozen();552if (end == size) {553if (beg == 0554&& parts.length > 0 && parts[parts.length - 1] == null) {555// If no attributes, free the parts array.556parts = noPartsNotFrozen;557size = 0;558} else {559clearParts(beg, size);560size = beg;561}562} else {563close(beg, end - beg);564}565}566567void clearParts(int beg, int end) {568for (int i = beg; i < end; i++) {569parts[i] = null;570}571}572573/** True if name, attributes, and elements are the same. */574public boolean equals(Element that) {575if (!this.name.equals(that.name)) {576return false;577}578if (this.size != that.size) {579return false;580}581// elements must be equal and ordered582Object[] thisParts = this.parts;583Object[] thatParts = that.parts;584for (int i = 0; i < size; i++) {585Object thisPart = thisParts[i];586Object thatPart = thatParts[i];587588if (thisPart instanceof Element) { // recursion is common case589if (!thisPart.equals(thatPart)) {590return false;591}592} else {593// If either is a non-string char sequence, normalize it.594thisPart = fixupString(thisPart);595thatPart = fixupString(thatPart);596if (!thisPart.equals(thatPart)) {597return false;598}599}600}601// finally, attributes must be equal (unordered)602return this.equalAttrs(that);603}604// bridge method605606public boolean equals(Object o) {607if (!(o instanceof Element)) {608return false;609}610return equals((Element) o);611}612613public int hashCode() {614int hc = 0;615int alen = attrLength();616for (int i = parts.length - alen; i < parts.length; i += 2) {617hc += (parts[i + 0].hashCode() ^ parts[i + 1].hashCode());618}619hc ^= hc << 11;620hc += name.hashCode();621for (int i = 0; i < size; i++) {622hc ^= hc << 7;623Object p = parts[i];624if (p instanceof Element) {625hc += p.hashCode(); // recursion is common case626} else {627hc += fixupString(p).hashCode();628}629}630hc ^= hc >>> 19;631return hc;632}633634/** Compare lexicographically. Earlier-spelled attrs are more sigificant. */635public int compareTo(Element that) {636int r;637// Primary key is element name.638r = this.name.compareTo(that.name);639if (r != 0) {640return r;641}642643// Secondary key is attributes, as if in normal key order.644// The key/value pairs are sorted as a token sequence.645int thisAlen = this.attrLength();646int thatAlen = that.attrLength();647if (thisAlen != 0 || thatAlen != 0) {648r = compareAttrs(thisAlen, that, thatAlen, true);649assert (assertAttrCompareOK(r, that));650if (r != 0) {651return r;652}653}654655// Finally, elements should be equal and ordered,656// and the first difference rules.657Object[] thisParts = this.parts;658Object[] thatParts = that.parts;659int minSize = this.size;660if (minSize > that.size) {661minSize = that.size;662}663Comparator<Object> cc = contentOrder();664for (int i = 0; i < minSize; i++) {665r = cc.compare(thisParts[i], thatParts[i]);666if (r != 0) {667return r;668}669}670//if (this.size < that.size) return -1;671return this.size - that.size;672}673674private boolean assertAttrCompareOK(int r, Element that) {675Element e0 = this.copyAttrsOnly();676Element e1 = that.copyAttrsOnly();677e0.sortAttrs();678e1.sortAttrs();679int r2;680for (int k = 0;; k++) {681boolean con0 = e0.containsAttr(k);682boolean con1 = e1.containsAttr(k);683if (con0 != con1) {684if (!con0) {685r2 = 0 - 1;686break;687}688if (!con1) {689r2 = 1 - 0;690break;691}692}693if (!con0) {694r2 = 0;695break;696}697String k0 = e0.getAttrName(k);698String k1 = e1.getAttrName(k);699r2 = k0.compareTo(k1);700if (r2 != 0) {701break;702}703String v0 = e0.getAttr(k);704String v1 = e1.getAttr(k);705r2 = v0.compareTo(v1);706if (r2 != 0) {707break;708}709}710if (r != 0) {711r = (r > 0) ? 1 : -1;712}713if (r2 != 0) {714r2 = (r2 > 0) ? 1 : -1;715}716if (r != r2) {717System.out.println("*** wrong attr compare, " + r + " != " + r2);718System.out.println(" this = " + this);719System.out.println(" attr->" + e0);720System.out.println(" that = " + that);721System.out.println(" attr->" + e1);722}723return r == r2;724}725726private void badIndex(int i) {727Object badRef = (new Object[0])[i];728}729730public Object get(int i) {731if (i >= size) {732badIndex(i);733}734return parts[i];735}736737public Object set(int i, Object e) {738if (i >= size) {739badIndex(i);740}741e.getClass(); // null check742checkNotFrozen();743Object old = parts[i];744setRaw(i, e);745return old;746}747748void setRaw(int i, Object e) {749parts[i] = e;750}751752public boolean remove(Object e) {753int i = indexOf(e);754if (i < 0) {755return false;756}757close(i, 1);758return true;759}760761public Object remove(int i) {762if (i >= size) {763badIndex(i);764}765Object e = parts[i];766close(i, 1);767return e;768}769770public Object removeLast() {771if (size == 0) {772return null;773}774return remove(size - 1);775}776777/** Remove the first element matching the given filter.778* Return the filtered value.779*/780public Object remove(Filter f) {781return findOrRemove(f, 0, true);782}783784public Object remove(Filter f, int fromIndex) {785if (fromIndex < 0) {786fromIndex = 0;787}788return findOrRemove(f, fromIndex, true);789}790791/** Remove the last element matching the given filter.792* Return the filtered value.793*/794public Object removeLast(Filter f) {795return findOrRemoveLast(f, size - 1, true);796}797798public Object removeLast(Filter f, int fromIndex) {799if (fromIndex >= size) {800fromIndex = size - 1;801}802return findOrRemoveLast(f, fromIndex, true);803}804805/** Remove all elements matching the given filter.806* If there is a non-null collection given as a sink,807* transfer removed elements to the given collection.808* The int result is the number of removed elements.809* If there is a null sink given, the removed elements810* are discarded. If there is no sink given, the removed811* elements are returned in an anonymous container element.812*/813public Element removeAll(Filter f) {814Element result = new Element();815findOrRemoveAll(f, false, 0, size, result.asList(), true);816return result;817}818819public Element removeAll(Filter f, int fromIndex, int toIndex) {820Element result = new Element();821findOrRemoveAll(f, true, fromIndex, toIndex, result.asList(), true);822return result;823}824825public int removeAll(Filter f, Collection<Object> sink) {826return findOrRemoveAll(f, false, 0, size, sink, true);827}828829public int removeAll(Filter f, int fromIndex, int toIndex, Collection<Object> sink) {830return findOrRemoveAll(f, false, fromIndex, toIndex, sink, true);831}832833/** Remove all elements not matching the given filter.834* If there is a non-null collection given as a sink,835* transfer removed elements to the given collection.836* The int result is the number of removed elements.837* If there is a null sink given, the removed elements838* are discarded. If there is no sink given, the removed839* elements are returned in an anonymous container element.840*/841public Element retainAll(Filter f) {842Element result = new Element();843findOrRemoveAll(f, true, 0, size, result.asList(), true);844return result;845}846847public Element retainAll(Filter f, int fromIndex, int toIndex) {848Element result = new Element();849findOrRemoveAll(f, true, fromIndex, toIndex, result.asList(), true);850return result;851}852853public int retainAll(Filter f, Collection<Object> sink) {854return findOrRemoveAll(f, true, 0, size, sink, true);855}856857public int retainAll(Filter f, int fromIndex, int toIndex, Collection<Object> sink) {858return findOrRemoveAll(f, true, fromIndex, toIndex, sink, true);859}860861public void add(int i, Object e) {862// (The shape of this method is tweaked for common cases.)863e.getClass(); // force a null check on e864if (hasNulls(1 + NEED_SLOP)) {865// Common case: Have some slop space.866if (i == size) {867// Most common case: Append.868setRaw(i, e);869size++;870return;871}872if (i > size) {873badIndex(i);874}875// Second most common case: Shift right by one.876open(i, 1);877setRaw(i, e);878return;879}880// Ran out of space. Do something complicated.881size = expand(i, 1);882setRaw(i, e);883}884885public boolean add(Object e) {886add(size, e);887return true;888}889890public Object getLast() {891return size == 0 ? null : parts[size - 1];892}893894/** Returns the text of this Element.895* All sub-elements of this Element must be of type CharSequence.896* A ClassCastException is raised if there are non-character sub-elements.897* If there is one sub-element, return it.898* Otherwise, returns a TokenList of all sub-elements.899* This results in a space being placed between each adjacent pair of sub-elements.900*/901public CharSequence getText() {902checkTextOnly();903if (size == 1) {904return parts[0].toString();905} else {906return new TokenList(parts, 0, size);907}908}909910/** Provides an iterable view of this object as a series of texts.911* All sub-elements of this Element must be of type CharSequence.912* A ClassCastException is raised if there are non-character sub-elements.913*/914public Iterable<CharSequence> texts() {915checkTextOnly();916return (Iterable<CharSequence>) (Iterable) this;917}918919/** Returns an array of strings derived from the sub-elements of this object.920* All sub-elements of this Element must be of type CharSequence.921* A ClassCastException is raised if there are non-character sub-elements.922*/923public String[] toStrings() {924//checkTextOnly();925String[] result = new String[size];926for (int i = 0; i < size; i++) {927result[i] = ((CharSequence) parts[i]).toString();928}929return result;930}931932/** Like getText, except that it disregards non-text elements.933* Non-text elements are replaced by their textual contents, if any.934* Text elements which were separated only by non-text element935* boundaries are merged into single tokens.936* <p>937* There is no corresponding setter, since this accessor does938* not report the full state of the element.939*/940public CharSequence getFlatText() {941if (size == 1) {942// Simple cases.943if (parts[0] instanceof CharSequence) {944return parts[0].toString();945} else {946return new TokenList();947}948}949if (isText()) {950return getText();951}952// Filter and merge.953Element result = new Element(size);954boolean merge = false;955for (int i = 0; i < size; i++) {956Object text = parts[i];957if (!(text instanceof CharSequence)) {958// Skip, but erase this boundary.959if (text instanceof Element) {960Element te = (Element) text;961if (!te.isEmpty()) {962result.addText(te.getFlatText());963}964}965merge = true;966continue;967}968if (merge) {969// Merge w/ previous token.970result.addText((CharSequence) text);971merge = false;972} else {973result.add(text);974}975}976if (result.size() == 1) {977return (CharSequence) result.parts[0];978} else {979return result.getText();980}981}982983/** Return true if all sub-elements are of type CharSequence. */984public boolean isText() {985for (int i = 0; i < size; i++) {986if (!(parts[i] instanceof CharSequence)) {987return false;988}989}990return true;991}992993/** Return true if at least one sub-element is of type CharSequence. */994public boolean hasText() {995for (int i = 0; i < size; i++) {996if (parts[i] instanceof CharSequence) {997return true;998}999}1000return false;1001}10021003/** Raise a ClassCastException if !isText. */1004public void checkTextOnly() {1005for (int i = 0; i < size; i++) {1006((CharSequence) parts[i]).getClass();1007}1008}10091010/** Clears out all sub-elements, and replaces them by the given text.1011* A ClassCastException is raised if there are non-character sub-elements,1012* either before or after the change.1013*/1014public void setText(CharSequence text) {1015checkTextOnly();1016clear();1017if (text instanceof TokenList) {1018// TL's contain only strings1019addAll(0, (TokenList) text);1020} else {1021add(text);1022}1023}10241025/** Add text at the given position, merging with any previous1026* text element, but preserving token boundaries where possible.1027* <p>1028* In all cases, the new value of getText() is the string1029* concatenation of the old value of getText() plus the new text.1030* <p>1031* The total effect is to concatenate the given text to any1032* pre-existing text, and to do so efficiently even if there1033* are many such concatenations. Also, getText calls which1034* return multiple tokens (in a TokenList) are respected.1035* For example, if x is empty, x.addText(y.getText()) puts1036* an exact structural copy of y's text into x.1037* <p>1038* Internal token boundaries in the original text, and in the new1039* text (i.e., if it is a TokenList), are preserved. However,1040* at the point where new text joins old text, a StringBuffer1041* or new String may be created to join the last old and first1042* new token.1043* <p>1044* If the given text is a TokenList, add the tokens as1045* separate sub-elements, possibly merging the first token to1046* a previous text item (to avoid making a new token boundary).1047* <p>1048* If the element preceding position i is a StringBuffer,1049* append the first new token to it.1050* <p>1051* If the preceding element is a CharSequence, replace it by a1052* StringBuffer containing both its and the first new token.1053* <p>1054* If tokens are added after a StringBuffer, freeze it into a String.1055* <p>1056* Every token not merged into a previous CharSequence is added1057* as a new sub-element, starting at position i.1058* <p>1059* Returns the number of elements added, which is useful1060* for further calls to addText. This number is zero1061* if the input string was null, or was successfully1062* merged into a StringBuffer at position i-1.1063* <p>1064* By contrast, calling add(text) always adds a new sub-element.1065* In that case, if there is a previous string, a separating1066* space is virtually present also, and will be observed if1067* getText() is used to return all the text together.1068*/1069public int addText(int i, CharSequence text) {1070if (text instanceof String) {1071return addText(i, (String) text);1072} else if (text instanceof TokenList) {1073// Text is a list of tokens.1074TokenList tl = (TokenList) text;1075int tlsize = tl.size();1076if (tlsize == 0) {1077return 0;1078}1079String token0 = tl.get(0).toString();1080if (tlsize == 1) {1081return addText(i, token0);1082}1083if (mergeWithPrev(i, token0, false)) {1084// Add the n-1 remaining tokens.1085addAll(i, tl.subList(1, tlsize));1086return tlsize - 1;1087} else {1088addAll(i, (Collection) tl);1089return tlsize;1090}1091} else {1092return addText(i, text.toString());1093}1094}10951096public int addText(CharSequence text) {1097return addText(size, text);1098}10991100private // no reason to make this helper public1101int addText(int i, String text) {1102if (text.length() == 0) {1103return 0; // Trivial success.1104}1105if (mergeWithPrev(i, text, true)) {1106return 0; // Merged with previous token.1107}1108// No previous token.1109add(i, text);1110return 1;1111}11121113// Tries to merge token with previous contents.1114// Returns true if token is successfully disposed of.1115// If keepSB is false, any previous StringBuffer is frozen.1116// If keepSB is true, a StringBuffer may be created to hold1117// the merged token.1118private boolean mergeWithPrev(int i, String token, boolean keepSB) {1119if (i == 0) // Trivial success if the token is length zero.1120{1121return (token.length() == 0);1122}1123Object prev = parts[i - 1];1124if (prev instanceof StringBuffer) {1125StringBuffer psb = (StringBuffer) prev;1126psb.append(token);1127if (!keepSB) {1128parts[i - 1] = psb.toString();1129}1130return true;1131}1132if (token.length() == 0) {1133return true; // Trivial success.1134}1135if (prev instanceof CharSequence) {1136// Must concatenate.1137StringBuffer psb = new StringBuffer(prev.toString());1138psb.append(token);1139if (keepSB) {1140parts[i - 1] = psb;1141} else {1142parts[i - 1] = psb.toString();1143}1144return true;1145}1146return false;1147}11481149/** Trim all strings, using String.trim().1150* Remove empty strings.1151* Normalize CharSequences to Strings.1152*/1153public void trimText() {1154checkNotFrozen();1155int fillp = 0;1156int size = this.size;1157Object[] parts = this.parts;1158for (int i = 0; i < size; i++) {1159Object e = parts[i];1160if (e instanceof CharSequence) {1161String tt = e.toString().trim();1162if (tt.length() == 0) {1163continue;1164}1165e = tt;1166}1167parts[fillp++] = e;1168}1169while (size > fillp) {1170parts[--size] = null;1171}1172this.size = fillp;1173}11741175/** Add one or more subelements at the given position.1176* If the object reference is null, nothing happens.1177* If the object is an anonymous Element, addAll is called.1178* If the object is a TokenList, addAll is called (to add the tokens).1179* Otherwise, add is called, adding a single subelement or string.1180* The net effect is to add zero or more tokens.1181* The returned value is the number of added elements.1182* <p>1183* Note that getText() can return a TokenList which preserves1184* token boundaries in the text source. Such a text will be1185* added as multiple text sub-elements.1186* <p>1187* If a text string is added adjacent to an immediately1188* preceding string, there will be a token boundary between1189* the strings, which will print as an extra space.1190*/1191public int addContent(int i, Object e) {1192if (e == null) {1193return 0;1194} else if (e instanceof TokenList) {1195return addAll(i, (Collection) e);1196} else if (e instanceof Element1197&& ((Element) e).isAnonymous()) {1198return addAll(i, (Element) e);1199} else {1200add(i, e);1201return 1;1202}1203}12041205public int addContent(Object e) {1206return addContent(size, e);1207}12081209public Object[] toArray() {1210Object[] result = new Object[size];1211System.arraycopy(parts, 0, result, 0, size);1212return result;1213}12141215public Element copyContentOnly() {1216Element content = new Element(size);1217System.arraycopy(parts, 0, content.parts, 0, size);1218content.size = size;1219return content;1220}12211222public void sort(Comparator<Object> c) {1223Arrays.sort(parts, 0, size, c);1224}12251226public void sort() {1227sort(CONTENT_ORDER);1228}12291230/** Equivalent to Collections.reverse(this.asList()). */1231public void reverse() {1232for (int i = 0, mid = size >> 1, j = size - 1; i < mid; i++, j--) {1233Object p = parts[i];1234parts[i] = parts[j];1235parts[j] = p;1236}1237}12381239/** Equivalent to Collections.shuffle(this.asList() [, rnd]). */1240public void shuffle() {1241Collections.shuffle(this.asList());1242}12431244public void shuffle(Random rnd) {1245Collections.shuffle(this.asList(), rnd);1246}12471248/** Equivalent to Collections.rotate(this.asList(), dist). */1249public void rotate(int dist) {1250Collections.rotate(this.asList(), dist);1251}12521253/** Equivalent to Collections.min(this.asList(), c). */1254public Object min(Comparator<Object> c) {1255return Collections.min(this.asList(), c);1256}12571258public Object min() {1259return min(CONTENT_ORDER);1260}12611262/** Equivalent to Collections.max(this.asList(), c). */1263public Object max(Comparator<Object> c) {1264return Collections.max(this.asList(), c);1265}12661267public Object max() {1268return max(CONTENT_ORDER);1269}12701271public int addAll(int i, Collection c) {1272if (c instanceof LView) {1273return addAll(i, ((LView) c).asElement());1274} else {1275int csize = c.size();1276if (csize == 0) {1277return 0;1278}1279openOrExpand(i, csize);1280int fill = i;1281for (Object part : c) {1282parts[fill++] = part;1283}1284return csize;1285}1286}12871288public int addAll(int i, Element e) {1289int esize = e.size;1290if (esize == 0) {1291return 0;1292}1293openOrExpand(i, esize);1294System.arraycopy(e.parts, 0, parts, i, esize);1295return esize;1296}12971298public int addAll(Collection c) {1299return addAll(size, c);1300}13011302public int addAll(Element e) {1303return addAll(size, e);1304}13051306public int addAllAttrsFrom(Element e) {1307int added = 0;1308for (int k = 0; e.containsAttr(k); k++) {1309String old = setAttr(e.getAttrName(k), e.getAttr(k));1310if (old == null) {1311added += 1;1312}1313}1314// Return number of added (not merely changed) attrs.1315return added;1316}13171318// Search.1319public boolean matches(Filter f) {1320return f.filter(this) != null;1321}13221323public Object find(Filter f) {1324return findOrRemove(f, 0, false);1325}13261327public Object find(Filter f, int fromIndex) {1328if (fromIndex < 0) {1329fromIndex = 0;1330}1331return findOrRemove(f, fromIndex, false);1332}13331334/** Find the last element matching the given filter.1335* Return the filtered value.1336*/1337public Object findLast(Filter f) {1338return findOrRemoveLast(f, size - 1, false);1339}13401341public Object findLast(Filter f, int fromIndex) {1342if (fromIndex >= size) {1343fromIndex = size - 1;1344}1345return findOrRemoveLast(f, fromIndex, false);1346}13471348/** Find all elements matching the given filter.1349* If there is a non-null collection given as a sink,1350* transfer matching elements to the given collection.1351* The int result is the number of matching elements.1352* If there is a null sink given, the matching elements are1353* not collected. If there is no sink given, the matching1354* elements are returned in an anonymous container element.1355* In no case is the receiver element changed.1356* <p>1357* Note that a simple count of matching elements can be1358* obtained by passing a null collection argument.1359*/1360public Element findAll(Filter f) {1361Element result = new Element();1362findOrRemoveAll(f, false, 0, size, result.asList(), false);1363return result;1364}13651366public Element findAll(Filter f, int fromIndex, int toIndex) {1367Element result = new Element(name);1368findOrRemoveAll(f, false, fromIndex, toIndex, result.asList(), false);1369return result;1370}13711372public int findAll(Filter f, Collection<Object> sink) {1373return findOrRemoveAll(f, false, 0, size, sink, false);1374}13751376public int findAll(Filter f, int fromIndex, int toIndex, Collection<Object> sink) {1377return findOrRemoveAll(f, false, fromIndex, toIndex, sink, false);1378}13791380/// Driver routines.1381private Object findOrRemove(Filter f, int fromIndex, boolean remove) {1382for (int i = fromIndex; i < size; i++) {1383Object x = f.filter(parts[i]);1384if (x != null) {1385if (remove) {1386close(i, 1);1387}1388return x;1389}1390}1391return null;1392}13931394private Object findOrRemoveLast(Filter f, int fromIndex, boolean remove) {1395for (int i = fromIndex; i >= 0; i--) {1396Object x = f.filter(parts[i]);1397if (x != null) {1398if (remove) {1399close(i, 1);1400}1401return x;1402}1403}1404return null;1405}14061407private int findOrRemoveAll(Filter f, boolean fInvert,1408int fromIndex, int toIndex,1409Collection<Object> sink, boolean remove) {1410if (fromIndex < 0) {1411badIndex(fromIndex);1412}1413if (toIndex > size) {1414badIndex(toIndex);1415}1416int found = 0;1417for (int i = fromIndex; i < toIndex; i++) {1418Object p = parts[i];1419Object x = f.filter(p);1420if (fInvert ? (x == null) : (x != null)) {1421if (remove) {1422close(i--, 1);1423toIndex--;1424}1425found += XMLKit.addContent(fInvert ? p : x, sink);1426}1427}1428return found;1429}14301431public void replaceAll(Filter f) {1432XMLKit.replaceAll(f, this.asList());1433}14341435public void replaceAll(Filter f, int fromIndex, int toIndex) {1436XMLKit.replaceAll(f, this.asList().subList(fromIndex, toIndex));1437}14381439/// Recursive walks.1440// findAllInTree(f) == findAll(findInTree(f,S)), S.toElement1441// findAllInTree(f,S) == findAll(findInTree(content(f,S)))1442// removeAllInTree(f) == replaceAll(replaceInTree(and(f,emptyF)))1443// removeAllInTree(f,S) == replaceAll(replaceInTree(and(content(f,S),emptyF)))1444// retainAllInTree(f) == removeAllInTree(not(f))1445// replaceAllInTree(f) == replaceAll(replaceInTree(f))1446public Element findAllInTree(Filter f) {1447Element result = new Element();1448findAllInTree(f, result.asList());1449return result;1450}14511452public int findAllInTree(Filter f, Collection<Object> sink) {1453int found = 0;1454int size = this.size; // cache copy1455for (int i = 0; i < size; i++) {1456Object p = parts[i];1457Object x = f.filter(p);1458if (x != null) {1459found += XMLKit.addContent(x, sink);1460} else if (p instanceof Element) {1461found += ((Element) p).findAllInTree(f, sink);1462}1463}1464return found;1465}14661467public int countAllInTree(Filter f) {1468return findAllInTree(f, null);1469}14701471public int removeAllInTree(Filter f, Collection<Object> sink) {1472if (sink == null) {1473sink = newCounterColl();1474}1475replaceAll(replaceInTree(and(content(f, sink), emptyFilter())));1476return sink.size();1477}14781479public Element removeAllInTree(Filter f) {1480Element result = new Element();1481removeAllInTree(f, result.asList());1482return result;1483}14841485public int retainAllInTree(Filter f, Collection<Object> sink) {1486return removeAllInTree(not(f), sink);1487}14881489public Element retainAllInTree(Filter f) {1490Element result = new Element();1491retainAllInTree(f, result.asList());1492return result;1493}14941495public void replaceAllInTree(Filter f) {1496replaceAll(replaceInTree(f));1497}14981499/** Raise a ClassCastException if any subelements are the wrong type. */1500public void checkPartsOnly(Class<?> elementClass) {1501for (int i = 0; i < size; i++) {1502elementClass.cast(parts[i]).getClass();1503}1504}15051506/** Return true if all sub-elements are of the given type. */1507public boolean isPartsOnly(Class<?> elementClass) {1508for (int i = 0; i < size; i++) {1509if (!elementClass.isInstance(parts[i])) {1510return false;1511}1512}1513return true;1514}15151516/** Provides an iterable view of this object as a series of elements.1517* All sub-elements of this Element must be of type Element.1518* A ClassCastException is raised if there are non-Element sub-elements.1519*/1520public <T> Iterable<T> partsOnly(Class<T> elementClass) {1521checkPartsOnly(elementClass);1522return (Iterable<T>) (Iterable) this;1523}15241525public Iterable<Element> elements() {1526return partsOnly(Element.class);1527}15281529/// Useful shorthands.1530// Finding or removing elements w/o regard to their type or content.1531public Element findElement() {1532return (Element) find(elementFilter());1533}15341535public Element findAllElements() {1536return findAll(elementFilter());1537}15381539public Element removeElement() {1540return (Element) remove(elementFilter());1541}15421543public Element removeAllElements() {1544return (Element) removeAll(elementFilter());1545}15461547// Finding or removing by element tag or selected attribute,1548// as if by elementFilter(name) or attrFilter(name, value).1549// Roughly akin to Common Lisp ASSOC.1550public Element findElement(String name) {1551return (Element) find(elementFilter(name));1552}15531554public Element removeElement(String name) {1555return (Element) remove(elementFilter(name));1556}15571558public Element findWithAttr(String key) {1559return (Element) find(attrFilter(name));1560}15611562public Element findWithAttr(String key, String value) {1563return (Element) find(attrFilter(name, value));1564}15651566public Element removeWithAttr(String key) {1567return (Element) remove(attrFilter(name));1568}15691570public Element removeWithAttr(String key, String value) {1571return (Element) remove(attrFilter(name, value));1572}15731574public Element findAllElements(String name) {1575return findAll(elementFilter(name));1576}15771578public Element removeAllElements(String name) {1579return removeAll(elementFilter(name));1580}15811582public Element retainAllElements(String name) {1583return retainAll(elementFilter(name));1584}15851586public Element findAllWithAttr(String key) {1587return findAll(attrFilter(key));1588}15891590public Element removeAllWithAttr(String key) {1591return removeAll(attrFilter(key));1592}15931594public Element retainAllWithAttr(String key) {1595return retainAll(attrFilter(key));1596}15971598public Element findAllWithAttr(String key, String value) {1599return findAll(attrFilter(key, value));1600}16011602public Element removeAllWithAttr(String key, String value) {1603return removeAll(attrFilter(key, value));1604}16051606public Element retainAllWithAttr(String key, String value) {1607return retainAll(attrFilter(key, value));1608}16091610public int countAll(Filter f) {1611return findAll(f, null);1612}16131614public int countAllElements() {1615return countAll(elementFilter());1616}16171618public int countAllElements(String name) {1619return countAll(elementFilter(name));1620}16211622public int countAllWithAttr(String key) {1623return countAll(attrFilter(name));1624}16251626public int countAllWithAttr(String key, String value) {1627return countAll(attrFilter(key, value));1628}16291630public int indexOf(Object e) {1631for (int i = 0; i < size; i++) {1632if (e.equals(parts[i])) {1633return i;1634}1635}1636return -1;1637}16381639public int lastIndexOf(Object e) {1640for (int i = size - 1; i >= 0; i--) {1641if (e.equals(parts[i])) {1642return i;1643}1644}1645return -1;1646}16471648/** Remove the first element matching the given filter.1649* Return the filtered value.1650*/1651public int indexOf(Filter f) {1652return indexOf(f, 0);1653}16541655public int indexOf(Filter f, int fromIndex) {1656if (fromIndex < 0) {1657fromIndex = 0;1658}1659for (int i = fromIndex; i < size; i++) {1660Object x = f.filter(parts[i]);1661if (x != null) {1662return i;1663}1664}1665return -1;1666}16671668/** Remove the last element matching the given filter.1669* Return the filtered value.1670*/1671public int lastIndexOf(Filter f) {1672return lastIndexOf(f, size - 1);1673}16741675public int lastIndexOf(Filter f, int fromIndex) {1676if (fromIndex >= size) {1677fromIndex = size - 1;1678}1679for (int i = fromIndex; i >= 0; i--) {1680Object x = f.filter(parts[i]);1681if (x != null) {1682return i;1683}1684}1685return -1;1686}16871688public boolean contains(Object e) {1689return indexOf(e) >= 0;1690}16911692// attributes1693private int findOrCreateAttr(String key, boolean create) {1694key.toString(); // null check1695int attrBase = parts.length;1696for (int i = parts.length - 2; i >= size; i -= 2) {1697String akey = (String) parts[i + 0];1698if (akey == null) {1699if (!create) {1700return -1;1701}1702if (i == size) {1703break; // NEED_SLOP1704}1705parts[i + 0] = key;1706//parts[i+1] = ""; //caller responsibility1707return i;1708}1709attrBase = i;1710if (akey.equals(key)) {1711return i;1712}1713}1714// If we fell through, we ran into an element part.1715// Therefore we have run out of empty slots.1716if (!create) {1717return -1;1718}1719assert (!isFrozen());1720int alen = parts.length - attrBase;1721expand(size, 2); // generally expands by more than 21722// since there was a reallocation, the garbage slots are really null1723assert (parts[size + 0] == null && parts[size + 1] == null);1724alen += 2;1725int i = parts.length - alen;1726parts[i + 0] = key;1727//parts[i+1] = ""; //caller responsibility1728return i;1729}17301731public int attrSize() {1732return attrLength() >>> 1;1733}17341735public int indexOfAttr(String key) {1736return findOrCreateAttr(key, false);1737}17381739public boolean containsAttr(String key) {1740return indexOfAttr(key) >= 0;1741}17421743public String getAttr(String key) {1744return getAttr(key, null);1745}17461747public String getAttr(String key, String dflt) {1748int i = findOrCreateAttr(key, false);1749return (i < 0) ? dflt : (String) parts[i + 1];1750}17511752public TokenList getAttrList(String key) {1753return convertToList(getAttr(key));1754}17551756public Number getAttrNumber(String key) {1757return convertToNumber(getAttr(key));1758}17591760public long getAttrLong(String key) {1761return getAttrLong(key, 0);1762}17631764public double getAttrDouble(String key) {1765return getAttrDouble(key, 0.0);1766}17671768public long getAttrLong(String key, long dflt) {1769return convertToLong(getAttr(key), dflt);1770}17711772public double getAttrDouble(String key, double dflt) {1773return convertToDouble(getAttr(key), dflt);1774}17751776int indexAttr(int k) {1777int i = parts.length - (k * 2) - 2;1778if (i < size || parts[i] == null) {1779return -2; // always oob1780}1781return i;1782}17831784public boolean containsAttr(int k) {1785return indexAttr(k) >= 0;1786}17871788public String getAttr(int k) {1789return (String) parts[indexAttr(k) + 1];1790}17911792public String getAttrName(int k) {1793return (String) parts[indexAttr(k) + 0];1794}17951796public Iterable<String> attrNames() {1797//return asAttrMap().keySet();1798return new Iterable<String>() {17991800public Iterator<String> iterator() {1801return new ANItr();1802}1803};1804}18051806// Hand-inlined replacement for asAttrMap().keySet().iterator():1807class ANItr implements Iterator<String> {18081809boolean lastRet;1810int cursor = -2; // pointer from end of parts18111812public boolean hasNext() {1813int i = cursor + parts.length;1814return i >= size && parts[i] == null;1815}18161817public String next() {1818int i = cursor + parts.length;1819Object x;1820if (i < size || (x = parts[i]) == null) {1821nsee();1822return null;1823}1824cursor -= 2;1825lastRet = true;1826return (String) x;1827}18281829public void remove() {1830if (!lastRet) {1831throw new IllegalStateException();1832}1833Element.this.removeAttr((-4 - cursor) / 2);1834cursor += 2;1835lastRet = false;1836}18371838Exception nsee() {1839throw new NoSuchElementException("attribute " + (-2 - cursor) / 2);1840}1841}18421843/** Return an anonymous copy of self, but only with attributes.1844*/1845public Element copyAttrsOnly() {1846int alen = attrLength();1847Element attrs = new Element(alen);1848Object[] attrParts = attrs.parts;1849assert (attrParts.length == NEED_SLOP + alen);1850System.arraycopy(parts, parts.length - alen,1851attrParts, NEED_SLOP,1852alen);1853return attrs;1854}18551856/** Get all attributes, represented as an element with sub-elements.1857* The name of each sub-element is the attribute key, and the text1858* This is a fresh copy, and can be updated with affecting the original.1859* of each sub-element is the corresponding attribute value.1860* See also asAttrMap() for a "live" view of all the attributes as a Map.1861*/1862public Element getAttrs() {1863int asize = attrSize();1864Element attrs = new Element(ANON_NAME, asize, NEED_SLOP + asize);1865for (int i = 0; i < asize; i++) {1866Element attr = new Element(getAttrName(i), 1, NEED_SLOP + 1);1867// %%% normalize attrs to token lists?1868attr.setRaw(0, getAttr(i));1869attrs.setRaw(i, attr);1870}1871return attrs;1872}18731874public void setAttrs(Element attrs) {1875int alen = attrLength();1876clearParts(parts.length - alen, alen);1877if (!hasNulls(NEED_SLOP + attrs.size * 2)) {1878expand(size, attrs.size * 2);1879}1880addAttrs(attrs);1881}18821883public void addAttrs(Element attrs) {1884for (int i = 0; i < attrs.size; i++) {1885Element attr = (Element) attrs.get(i);1886setAttr(attr.name, attr.getText().toString());1887}1888}18891890public void removeAttr(int i) {1891checkNotFrozen();1892while ((i -= 2) >= size) {1893Object k = parts[i + 0];1894Object v = parts[i + 1];1895if (k == null) {1896break;1897}1898parts[i + 2] = k;1899parts[i + 3] = v;1900}1901parts[i + 2] = null;1902parts[i + 3] = null;1903}19041905public void clearAttrs() {1906if (parts.length == 0 || parts[parts.length - 1] == null) {1907return; // no attrs to clear1908}1909checkNotFrozen();1910if (size == 0) {1911// If no elements, free the parts array.1912parts = noPartsNotFrozen;1913return;1914}1915for (int i = parts.length - 1; parts[i] != null; i--) {1916assert (i >= size);1917parts[i] = null;1918}1919}19201921public String setAttr(String key, String value) {1922String old;1923if (value == null) {1924int i = findOrCreateAttr(key, false);1925if (i >= 0) {1926old = (String) parts[i + 1];1927removeAttr(i);1928} else {1929old = null;1930}1931} else {1932checkNotFrozen();1933int i = findOrCreateAttr(key, true);1934old = (String) parts[i + 1];1935parts[i + 1] = value;1936}1937return old;1938}19391940public String setAttrList(String key, List<String> l) {1941if (l == null) {1942return setAttr(key, null);1943}1944if (!(l instanceof TokenList)) {1945l = new TokenList(l);1946}1947return setAttr(key, l.toString());1948}19491950public String setAttrNumber(String key, Number n) {1951return setAttr(key, (n == null) ? null : n.toString());1952}19531954public String setAttrLong(String key, long n) {1955return setAttr(key, (n == 0) ? null : String.valueOf(n));1956}19571958public String setAttrDouble(String key, double n) {1959return setAttr(key, (n == 0) ? null : String.valueOf(n));1960}19611962public String setAttr(int k, String value) {1963int i = indexAttr(k);1964String old = (String) parts[i + 1];1965if (value == null) {1966removeAttr(i);1967} else {1968checkNotFrozen();1969parts[i + 1] = value;1970}1971return old;1972}19731974int attrLength() {1975return parts.length - attrBase();1976}19771978/** Are the attributes of the two two elements equal?1979* Disregards name, sub-elements, and ordering of attributes.1980*/1981public boolean equalAttrs(Element that) {1982int alen = this.attrLength();1983if (alen != that.attrLength()) {1984return false;1985}1986if (alen == 0) {1987return true;1988}1989return compareAttrs(alen, that, alen, false) == 0;1990}19911992private int compareAttrs(int thisAlen,1993Element that, int thatAlen,1994boolean fullCompare) {1995Object[] thisParts = this.parts;1996Object[] thatParts = that.parts;1997int thisBase = thisParts.length - thisAlen;1998int thatBase = thatParts.length - thatAlen;1999// search indexes into unmatched parts of this.attrs:2000int firstI = 0;2001// search indexes into unmatched parts of that.attrs:2002int firstJ = 0;2003int lastJ = thatAlen - 2;2004// try to find the mismatch with the first key:2005String firstKey = null;2006int firstKeyValCmp = 0;2007int foundKeys = 0;2008for (int i = 0; i < thisAlen; i += 2) {2009String key = (String) thisParts[thisBase + i + 0];2010String val = (String) thisParts[thisBase + i + 1];2011String otherVal = null;2012for (int j = firstJ; j <= lastJ; j += 2) {2013if (key.equals(thatParts[thatBase + j + 0])) {2014foundKeys += 1;2015otherVal = (String) thatParts[thatBase + j + 1];2016// Optimization: Narrow subsequent searches when easy.2017if (j == lastJ) {2018lastJ -= 2;2019} else if (j == firstJ) {2020firstJ += 2;2021}2022if (i == firstI) {2023firstI += 2;2024}2025break;2026}2027}2028int valCmp;2029if (otherVal != null) {2030// The key was found.2031if (!fullCompare) {2032if (!val.equals(otherVal)) {2033return 1 - 0; //arb.2034}2035continue;2036}2037valCmp = val.compareTo(otherVal);2038} else {2039// Found the key in this but not that.2040// Such a mismatch puts the guy missing the key last.2041valCmp = 0 - 1;2042}2043if (valCmp != 0) {2044// found a mismatch, key present in both elems2045if (firstKey == null2046|| firstKey.compareTo(key) > 0) {2047// found a better key2048firstKey = key;2049firstKeyValCmp = valCmp;2050}2051}2052}2053// We have located the first mismatch of all keys in this.attrs.2054// In general we must also look for keys in that.attrs but missing2055// from this.attrs; such missing keys, if earlier than firstKey,2056// rule the comparison.20572058// We can sometimes prove quickly there is no missing key.2059if (foundKeys == thatAlen / 2) {2060// Exhausted all keys in that.attrs.2061return firstKeyValCmp;2062}20632064// Search for a missing key in that.attrs earlier than firstKey.2065findMissingKey:2066for (int j = firstJ; j <= lastJ; j += 2) {2067String otherKey = (String) thatParts[thatBase + j + 0];2068if (firstKey == null2069|| firstKey.compareTo(otherKey) > 0) {2070// Found a better key; is it missing?2071for (int i = firstI; i < thisAlen; i += 2) {2072if (otherKey.equals(thisParts[thisBase + i + 0])) {2073continue findMissingKey;2074}2075}2076// If we get here, there was no match in this.attrs.2077return 1 - 0;2078}2079}20802081// No missing key. Previous comparison value rules.2082return firstKeyValCmp;2083}20842085// Binary search looking for first non-null after size.2086int attrBase() {2087// Smallest & largest possible attribute indexes:2088int kmin = 0;2089int kmax = (parts.length - size) >>> 1;2090// earlist possible attribute position:2091int abase = parts.length - (kmax * 2);2092// binary search using scaled indexes:2093while (kmin != kmax) {2094int kmid = kmin + ((kmax - kmin) >>> 1);2095if (parts[abase + (kmid * 2)] == null) {2096kmin = kmid + 1;2097} else {2098kmax = kmid;2099}2100assert (kmin <= kmax);2101}2102return abase + (kmax * 2);2103}21042105/** Sort attributes by name. */2106public void sortAttrs() {2107checkNotFrozen();2108int abase = attrBase();2109int alen = parts.length - abase;2110String[] buf = new String[alen];2111// collect keys2112for (int k = 0; k < alen / 2; k++) {2113String akey = (String) parts[abase + (k * 2) + 0];2114buf[k] = akey;2115}2116Arrays.sort(buf, 0, alen / 2);2117// collect values2118for (int k = 0; k < alen / 2; k++) {2119String akey = buf[k];2120buf[k + alen / 2] = getAttr(akey);2121}2122// reorder keys and values2123int fillp = parts.length;2124for (int k = 0; k < alen / 2; k++) {2125String akey = buf[k];2126String aval = buf[k + alen / 2];2127fillp -= 2;2128parts[fillp + 0] = akey;2129parts[fillp + 1] = aval;2130}2131assert (fillp == abase);2132}21332134/*2135Notes on whitespace and tokenization.2136On input, never split CDATA blocks. They remain single tokens.2137?Try to treat encoded characters as CDATA-quoted, also?21382139Internally, each String sub-element is logically a token.2140However, if there was no token-splitting on input,2141consecutive strings are merged by the parser.21422143Internally, we need addToken (intervening blank) and addText2144(hard concatenation).21452146Optionally on input, tokenize unquoted text into words.2147Between each adjacent word pair, elide either one space2148or all space.21492150On output, we always add spaces between tokens.2151The Element("a", {"b", "c", Element("d"), "e f"})2152outputs as "<a>b c<d/>e f</a>"2153*/2154/** Split strings into tokens, using a StringTokenizer. */2155public void tokenize(String delims, boolean returnDelims) {2156checkNotFrozen();2157if (delims == null) {2158delims = WHITESPACE_CHARS; // StringTokenizer default2159}2160for (int i = 0; i < size; i++) {2161if (!(parts[i] instanceof CharSequence)) {2162continue;2163}2164int osize = size;2165String str = parts[i].toString();2166StringTokenizer st = new StringTokenizer(str, delims, returnDelims);2167int nstrs = st.countTokens();2168switch (nstrs) {2169case 0:2170close(i--, 1);2171break;2172case 1:2173parts[i] = st.nextToken();2174break;2175default:2176openOrExpand(i + 1, nstrs - 1);2177for (int j = 0; j < nstrs; j++) {2178parts[i + j] = st.nextToken();2179}2180i += nstrs - 1;2181break;2182}2183}2184}21852186public void tokenize(String delims) {2187tokenize(delims, false);2188}21892190public void tokenize() {2191tokenize(null, false);2192}21932194// views2195class LView extends AbstractList<Object> {21962197Element asElement() {2198return Element.this;2199}22002201public int size() {2202return Element.this.size();2203}22042205public Object get(int i) {2206return Element.this.get(i);2207}22082209@Override2210public boolean contains(Object e) {2211return Element.this.contains(e);2212}22132214@Override2215public Object[] toArray() {2216return Element.this.toArray();2217}22182219@Override2220public int indexOf(Object e) {2221return Element.this.indexOf(e);2222}22232224@Override2225public int lastIndexOf(Object e) {2226return Element.this.lastIndexOf(e);2227}22282229@Override2230public void add(int i, Object e) {2231++modCount;2232Element.this.add(i, e);2233}22342235@Override2236public boolean addAll(int i, Collection<? extends Object> c) {2237++modCount;2238return Element.this.addAll(i, c) > 0;2239}22402241@Override2242public boolean addAll(Collection<? extends Object> c) {2243++modCount;2244return Element.this.addAll(c) > 0;2245}22462247@Override2248public Object remove(int i) {2249++modCount;2250return Element.this.remove(i);2251}22522253@Override2254public Object set(int i, Object e) {2255++modCount;2256return Element.this.set(i, e);2257}22582259@Override2260public void clear() {2261++modCount;2262Element.this.clear();2263}2264// Others: toArray(Object[]), containsAll, removeAll, retainAll2265}22662267/** Produce a list view of sub-elements.2268* (The list view does not provide access to the element's2269* name or attributes.)2270* Changes to this view are immediately reflected in the2271* element itself.2272*/2273public List<Object> asList() {2274return new LView();2275}22762277/** Produce a list iterator on all sub-elements. */2278public ListIterator<Object> iterator() {2279//return asList().listIterator();2280return new Itr();2281}22822283// Hand-inlined replacement for LView.listIterator():2284class Itr implements ListIterator<Object> {22852286int lastRet = -1;2287int cursor = 0;22882289public boolean hasNext() {2290return cursor < size;2291}22922293public boolean hasPrevious() {2294return cursor > 0 && cursor <= size;2295}22962297public Object next() {2298if (!hasNext()) {2299nsee();2300}2301return parts[lastRet = cursor++];2302}23032304public Object previous() {2305if (!hasPrevious()) {2306nsee();2307}2308return parts[--cursor];2309}23102311public int nextIndex() {2312return cursor;2313}23142315public int previousIndex() {2316return cursor - 1;2317}23182319public void set(Object x) {2320parts[lastRet] = x;2321}23222323public void add(Object x) {2324lastRet = -1;2325Element.this.add(cursor++, x);2326}23272328public void remove() {2329if (lastRet < 0) {2330throw new IllegalStateException();2331}2332Element.this.remove(lastRet);2333if (lastRet < cursor) {2334--cursor;2335}2336lastRet = -1;2337}23382339void nsee() {2340throw new NoSuchElementException("element " + cursor);2341}2342}23432344/** A PrintWriter which always appends as if by addText.2345* Use of this stream may insert a StringBuffer at the end2346* of the Element. The user must not directly modify this2347* StringBuffer, or use it in other data structures.2348* From time to time, the StringBuffer may be replaced by a2349* constant string as a result of using the PrintWriter.2350*/2351public PrintWriter asWriter() {2352return new ElemW();2353}23542355class ElemW extends PrintWriter {23562357ElemW() {2358super(new StringWriter());2359}2360final StringBuffer buf = ((StringWriter) out).getBuffer();23612362{2363lock = buf;2364} // synchronize on this buffer23652366@Override2367public void println() {2368synchronized (buf) {2369ensureCursor();2370super.println();2371}2372}23732374@Override2375public void write(int ch) {2376synchronized (buf) {2377ensureCursor();2378//buf.append(ch);2379super.write(ch);2380}2381}23822383@Override2384public void write(char buf[], int off, int len) {2385synchronized (buf) {2386ensureCursor();2387super.write(buf, off, len);2388}2389}23902391@Override2392public void write(String s, int off, int len) {2393synchronized (buf) {2394ensureCursor();2395//buf.append(s.substring(off, off+len));2396super.write(s, off, len);2397}2398}23992400@Override2401public void write(String s) {2402synchronized (buf) {2403ensureCursor();2404//buf.append(s);2405super.write(s);2406}2407}24082409private void ensureCursor() {2410checkNotFrozen();2411if (getLast() != buf) {2412int pos = indexOf(buf);2413if (pos >= 0) {2414// Freeze the pre-existing use of buf.2415setRaw(pos, buf.toString());2416}2417add(buf);2418}2419}2420}24212422/** Produce a map view of attributes, in which the attribute2423* name strings are the keys.2424* (The map view does not provide access to the element's2425* name or sub-elements.)2426* Changes to this view are immediately reflected in the2427* element itself.2428*/2429public Map<String, String> asAttrMap() {2430class Entry implements Map.Entry<String, String> {24312432final int k;24332434Entry(int k) {2435this.k = k;2436assert (((String) getKey()).toString() != null); // check, fail-fast2437}24382439public String getKey() {2440return Element.this.getAttrName(k);2441}24422443public String getValue() {2444return Element.this.getAttr(k);2445}24462447public String setValue(String v) {2448return Element.this.setAttr(k, v.toString());2449}24502451@Override2452public boolean equals(Object o) {2453if (!(o instanceof Map.Entry)) {2454return false;2455}2456Map.Entry that = (Map.Entry) o;2457return (this.getKey().equals(that.getKey())2458&& this.getValue().equals(that.getValue()));2459}24602461@Override2462public int hashCode() {2463return getKey().hashCode() ^ getValue().hashCode();2464}2465}2466class EIter implements Iterator<Map.Entry<String, String>> {24672468int k = 0; // index of pending next() attribute24692470public boolean hasNext() {2471return Element.this.containsAttr(k);2472}24732474public Map.Entry<String, String> next() {2475return new Entry(k++);2476}24772478public void remove() {2479Element.this.removeAttr(--k);2480}2481}2482class ESet extends AbstractSet<Map.Entry<String, String>> {24832484public int size() {2485return Element.this.attrSize();2486}24872488public Iterator<Map.Entry<String, String>> iterator() {2489return new EIter();2490}24912492@Override2493public void clear() {2494Element.this.clearAttrs();2495}2496}2497class AView extends AbstractMap<String, String> {24982499private transient Set<Map.Entry<String, String>> eSet;25002501public Set<Map.Entry<String, String>> entrySet() {2502if (eSet == null) {2503eSet = new ESet();2504}2505return eSet;2506}25072508@Override2509public int size() {2510return Element.this.attrSize();2511}25122513public boolean containsKey(String k) {2514return Element.this.containsAttr(k);2515}25162517public String get(String k) {2518return Element.this.getAttr(k);2519}25202521@Override2522public String put(String k, String v) {2523return Element.this.setAttr(k, v.toString());2524}25252526public String remove(String k) {2527return Element.this.setAttr(k, null);2528}2529}2530return new AView();2531}25322533/** Reports number of additional elements this object can accommodate2534* without reallocation.2535*/2536public int getExtraCapacity() {2537int abase = attrBase();2538return Math.max(0, abase - size - NEED_SLOP);2539}25402541/** Ensures that at least the given number of additional elements2542* can be added to this object without reallocation.2543*/2544public void ensureExtraCapacity(int cap) {2545if (cap == 0 || hasNulls(cap + NEED_SLOP)) {2546return;2547}2548setExtraCapacity(cap);2549}25502551/**2552* Trim excess capacity to zero, or do nothing if frozen.2553* This minimizes the space occupied by this Element,2554* at the expense of a reallocation if sub-elements or attributes2555* are added later.2556*/2557public void trimToSize() {2558if (isFrozen()) {2559return;2560}2561setExtraCapacity(0);2562}25632564/** Changes the number of additional elements this object can accommodate2565* without reallocation.2566*/2567public void setExtraCapacity(int cap) {2568checkNotFrozen();2569int abase = attrBase();2570int alen = parts.length - abase; // slots allocated for attrs2571int nlen = size + cap + NEED_SLOP + alen;2572if (nlen != parts.length) {2573Object[] nparts = new Object[nlen];2574// copy attributes2575System.arraycopy(parts, abase, nparts, nlen - alen, alen);2576// copy sub-elements2577System.arraycopy(parts, 0, nparts, 0, size);2578parts = nparts;2579}2580assert (cap == getExtraCapacity());2581}25822583// Return true if there are at least len nulls of slop available.2584boolean hasNulls(int len) {2585if (len == 0) {2586return true;2587}2588int lastNull = size + len - 1;2589if (lastNull >= parts.length) {2590return false;2591}2592return (parts[lastNull] == null);2593}25942595// Opens up parts array at pos by len spaces.2596void open(int pos, int len) {2597assert (pos < size);2598assert (hasNulls(len + NEED_SLOP));2599checkNotFrozen();2600int nsize = size + len;2601int tlen = size - pos;2602System.arraycopy(parts, pos, parts, pos + len, tlen);2603size = nsize;2604}26052606// Reallocate and open up at parts[pos] to at least len empty places.2607// Shift anything after pos right by len. Reallocate if necessary.2608// If pos < size, caller must fill it in with non-null values.2609// Returns incremented size; caller is responsible for storing it2610// down, if desired.2611int expand(int pos, int len) {2612assert (pos <= size);2613// There must be at least len nulls between elems and attrs.2614assert (!hasNulls(NEED_SLOP + len)); // caller responsibility2615checkNotFrozen();2616int nsize = size + len; // length of all elements2617int tlen = size - pos; // length of elements in post-pos tail2618int abase = attrBase();2619int alen = parts.length - abase; // slots allocated for attrs2620int nlen = nsize + alen + NEED_SLOP;2621nlen += (nlen >>> 1); // add new slop!2622Object[] nparts = new Object[nlen];2623// copy head of sub-elements2624System.arraycopy(parts, 0, nparts, 0, pos);2625// copy tail of sub-elements2626System.arraycopy(parts, pos, nparts, pos + len, tlen);2627// copy attributes2628System.arraycopy(parts, abase, nparts, nlen - alen, alen);2629// update self2630parts = nparts;2631//assert(hasNulls(len)); <- not yet true, since size != nsize2632return nsize;2633}26342635// Open or expand at the given position, as appropriate.2636boolean openOrExpand(int pos, int len) {2637if (pos < 0 || pos > size) {2638badIndex(pos);2639}2640if (hasNulls(len + NEED_SLOP)) {2641if (pos == size) {2642size += len;2643} else {2644open(pos, len);2645}2646return false;2647} else {2648size = expand(pos, len);2649return true;2650}2651}26522653// Close up at parts[pos] len old places.2654// Shift anything after pos left by len.2655// Fill unused end of parts with null.2656void close(int pos, int len) {2657assert (len > 0);2658assert ((size - pos) >= len);2659checkNotFrozen();2660int tlen = (size - pos) - len; // length of elements in post-pos tail2661int nsize = size - len;2662System.arraycopy(parts, pos + len, parts, pos, tlen);2663// reinitialize the unoccupied slots to null2664clearParts(nsize, nsize + len);2665// update self2666size = nsize;2667assert (hasNulls(len));2668}26692670public void writeTo(Writer w) throws IOException {2671new Printer(w).print(this);2672}26732674public void writePrettyTo(Writer w) throws IOException {2675prettyPrintTo(w, this);2676}26772678public String prettyString() {2679StringWriter sw = new StringWriter();2680try {2681writePrettyTo(sw);2682} catch (IOException ee) {2683throw new Error(ee); // should not happen2684}2685return sw.toString();2686}26872688@Override2689public String toString() {2690StringWriter sw = new StringWriter();2691try {2692writeTo(sw);2693} catch (IOException ee) {2694throw new Error(ee); // should not happen2695}2696return sw.toString();2697}26982699public String dump() {2700// For debugging only. Reveals internal layout.2701StringBuilder buf = new StringBuilder();2702buf.append("<").append(name).append("[").append(size).append("]");2703for (int i = 0; i < parts.length; i++) {2704Object p = parts[i];2705if (p == null) {2706buf.append(" null");2707} else {2708buf.append(" {");2709String cname = p.getClass().getName();2710cname = cname.substring(1 + cname.indexOf('/'));2711cname = cname.substring(1 + cname.indexOf('$'));2712cname = cname.substring(1 + cname.indexOf('#'));2713if (!cname.equals("String")) {2714buf.append(cname).append(":");2715}2716buf.append(p);2717buf.append("}");2718}2719}2720return buf.append(">").toString();2721}27222723public static java.lang.reflect.Method method(String name) {2724HashMap allM = allMethods;2725if (allM == null) {2726allM = makeAllMethods();2727}2728java.lang.reflect.Method res = (java.lang.reflect.Method) allMethods.get(name);2729if (res == null) {2730throw new IllegalArgumentException(name);2731}2732return res;2733}2734private static HashMap allMethods;27352736private static synchronized HashMap makeAllMethods() {2737if (allMethods != null) {2738return allMethods;2739}2740java.lang.reflect.Method[] methods = Element.class.getMethods();2741HashMap<String, java.lang.reflect.Method> allM = new HashMap<String, java.lang.reflect.Method>(),2742ambig = new HashMap<String, java.lang.reflect.Method>();2743for (int i = 0; i < methods.length; i++) {2744java.lang.reflect.Method m = methods[i];2745Class[] args = m.getParameterTypes();2746String name = m.getName();2747assert (java.lang.reflect.Modifier.isPublic(m.getModifiers()));2748if (name.startsWith("notify")) {2749continue;2750}2751if (name.endsWith("Attr")2752&& args.length > 0 && args[0] == int.class) // ignore getAttr(int), etc.2753{2754continue;2755}2756if (name.endsWith("All")2757&& args.length > 1 && args[0] == Filter.class) // ignore findAll(Filter, int...), etc.2758{2759continue;2760}2761java.lang.reflect.Method pm = allM.put(name, m);2762if (pm != null) {2763Class[] pargs = pm.getParameterTypes();2764if (pargs.length > args.length) {2765allM.put(name, pm); // put it back2766} else if (pargs.length == args.length) {2767ambig.put(name, pm); // make a note of it2768}2769}2770}2771// Delete ambiguous methods.2772for (Map.Entry<String, java.lang.reflect.Method> e : ambig.entrySet()) {2773String name = e.getKey();2774java.lang.reflect.Method pm = e.getValue();2775java.lang.reflect.Method m = allM.get(name);2776Class[] args = m.getParameterTypes();2777Class[] pargs = pm.getParameterTypes();2778if (pargs.length == args.length) {2779//System.out.println("ambig: "+pm);2780//System.out.println(" with: "+m);2781//ambig: int addAll(int,Element)2782// with: int addAll(int,Collection)2783allM.put(name, null); // get rid of2784}2785}2786//System.out.println("allM: "+allM);2787return allMethods = allM;2788}2789}27902791static Object fixupString(Object part) {2792if (part instanceof CharSequence && !(part instanceof String)) {2793return part.toString();2794} else {2795return part;2796}2797}27982799public static final class Special implements Comparable<Special> {28002801String kind;2802Object value;28032804public Special(String kind, Object value) {2805this.kind = kind;2806this.value = value;2807}28082809public String getKind() {2810return kind;2811}28122813public Object getValue() {2814return value;2815}28162817@Override2818public boolean equals(Object o) {2819if (!(o instanceof Special)) {2820return false;2821}2822Special that = (Special) o;2823return this.kind.equals(that.kind) && this.value.equals(that.value);2824}28252826@Override2827public int hashCode() {2828return kind.hashCode() * 65 + value.hashCode();2829}28302831public int compareTo(Special that) {2832int r = this.kind.compareTo(that.kind);2833if (r != 0) {2834return r;2835}2836return ((Comparable) this.value).compareTo(that.value);2837}28382839@Override2840public String toString() {2841int split = kind.indexOf(' ');2842String pref = kind.substring(0, split < 0 ? 0 : split);2843String post = kind.substring(split + 1);2844return pref + value + post;2845}2846}28472848/** Supports sorting of mixed content. Sorts strings first,2849* then Elements, then everything else (as Comparable).2850*/2851public static Comparator<Object> contentOrder() {2852return CONTENT_ORDER;2853}2854private static Comparator<Object> CONTENT_ORDER = new ContentComparator();28552856private static class ContentComparator implements Comparator<Object> {28572858public int compare(Object o1, Object o2) {2859boolean cs1 = (o1 instanceof CharSequence);2860boolean cs2 = (o2 instanceof CharSequence);2861if (cs1 && cs2) {2862String s1 = (String) fixupString(o1);2863String s2 = (String) fixupString(o2);2864return s1.compareTo(s2);2865}2866if (cs1) {2867return 0 - 1;2868}2869if (cs2) {2870return 1 - 0;2871}2872boolean el1 = (o1 instanceof Element);2873boolean el2 = (o2 instanceof Element);2874if (el1 && el2) {2875return ((Element) o1).compareTo((Element) o2);2876}2877if (el1) {2878return 0 - 1;2879}2880if (el2) {2881return 1 - 0;2882}2883return ((Comparable) o1).compareTo(o2);2884}2885}28862887/** Used to find, filter, or transform sub-elements.2888* When used as a predicate, the filter returns a null2889* value for false, and the original object value for true.2890* When used as a transformer, the filter may return2891* null, for no values, the original object, a new object,2892* or an anonymous Element (meaning multiple results).2893*/2894public interface Filter {28952896Object filter(Object value);2897}28982899/** Use this to find an element, perhaps with a given name. */2900public static class ElementFilter implements Filter {29012902/** Subclasses may override this to implement better value tests.2903* By default, it returns the element itself, thus recognizing2904* all elements, regardless of name.2905*/2906public Element filter(Element elem) {2907return elem; // override this2908}29092910public final Object filter(Object value) {2911if (!(value instanceof Element)) {2912return null;2913}2914return filter((Element) value);2915}29162917@Override2918public String toString() {2919return "<ElementFilter name='*'/>";2920}2921}2922private static Filter elementFilter;29232924public static Filter elementFilter() {2925return (elementFilter != null) ? elementFilter : (elementFilter = new ElementFilter());2926}29272928public static Filter elementFilter(final String name) {2929name.toString(); // null check2930return new ElementFilter() {29312932@Override2933public Element filter(Element elem) {2934return name.equals(elem.name) ? elem : null;2935}29362937@Override2938public String toString() {2939return "<ElementFilter name='" + name + "'/>";2940}2941};2942}29432944public static Filter elementFilter(final Collection nameSet) {2945nameSet.getClass(); // null check2946return new ElementFilter() {29472948@Override2949public Element filter(Element elem) {2950return nameSet.contains(elem.name) ? elem : null;2951}29522953@Override2954public String toString() {2955return "<ElementFilter name='" + nameSet + "'/>";2956}2957};2958}29592960public static Filter elementFilter(String... nameSet) {2961Collection<String> ncoll = Arrays.asList(nameSet);2962if (nameSet.length > 10) {2963ncoll = new HashSet<String>(ncoll);2964}2965return elementFilter(ncoll);2966}29672968/** Use this to find an element with a named attribute,2969* possibly with a particular value.2970* (Note that an attribute is missing if and only if its value is null.)2971*/2972public static class AttrFilter extends ElementFilter {29732974protected final String attrName;29752976public AttrFilter(String attrName) {2977this.attrName = attrName.toString();2978}29792980/** Subclasses may override this to implement better value tests.2981* By default, it returns true for any non-null value, thus2982* recognizing any attribute of the given name, regardless of value.2983*/2984public boolean test(String attrVal) {2985return attrVal != null; // override this2986}29872988@Override2989public final Element filter(Element elem) {2990return test(elem.getAttr(attrName)) ? elem : null;2991}29922993@Override2994public String toString() {2995return "<AttrFilter name='" + attrName + "' value='*'/>";2996}2997}29982999public static Filter attrFilter(String attrName) {3000return new AttrFilter(attrName);3001}30023003public static Filter attrFilter(String attrName, final String attrVal) {3004if (attrVal == null) {3005return not(attrFilter(attrName));3006}3007return new AttrFilter(attrName) {30083009@Override3010public boolean test(String attrVal2) {3011return attrVal.equals(attrVal2);3012}30133014@Override3015public String toString() {3016return "<AttrFilter name='" + attrName + "' value='" + attrVal + "'/>";3017}3018};3019}30203021public static Filter attrFilter(Element matchThis, String attrName) {3022return attrFilter(attrName, matchThis.getAttr(attrName));3023}30243025/** Use this to find a sub-element of a given class. */3026public static Filter classFilter(final Class clazz) {3027return new Filter() {30283029public Object filter(Object value) {3030return clazz.isInstance(value) ? value : null;3031}30323033@Override3034public String toString() {3035return "<ClassFilter class='" + clazz.getName() + "'/>";3036}3037};3038}3039private static Filter textFilter;30403041public static Filter textFilter() {3042return (textFilter != null) ? textFilter : (textFilter = classFilter(CharSequence.class));3043}3044private static Filter specialFilter;30453046public static Filter specialFilter() {3047return (specialFilter != null) ? specialFilter : (specialFilter = classFilter(Special.class));3048}3049private static Filter selfFilter;30503051/** This filter always returns its own argument. */3052public static Filter selfFilter() {3053if (selfFilter != null) {3054return selfFilter;3055}3056return selfFilter = new Filter() {30573058public Object filter(Object value) {3059return value;3060}30613062@Override3063public String toString() {3064return "<Self/>";3065}3066};3067}30683069/** This filter always returns a fixed value, regardless of argument. */3070public static Filter constantFilter(final Object value) {3071return new Filter() {30723073public Object filter(Object ignore) {3074return value;3075}30763077@Override3078public String toString() {3079return "<Constant>" + value + "</Constant>";3080}3081};3082}3083private static Filter nullFilter;30843085public static Filter nullFilter() {3086return (nullFilter != null) ? nullFilter : (nullFilter = constantFilter(null));3087}3088private static Filter emptyFilter;30893090public static Filter emptyFilter() {3091return (emptyFilter != null) ? emptyFilter : (emptyFilter = constantFilter(Element.EMPTY));3092}30933094/** Use this to invert the logical sense of the given filter. */3095public static Filter not(final Filter f) {3096return new Filter() {30973098public Object filter(Object value) {3099return f.filter(value) == null ? value : null;3100}31013102@Override3103public String toString() {3104return "<Not>" + f + "</Not>";3105}3106};3107}31083109/** Use this to combine several filters with logical AND.3110* Returns either the first null or the last non-null value.3111*/3112public static Filter and(final Filter f0, final Filter f1) {3113return and(new Filter[]{f0, f1});3114}31153116public static Filter and(final Filter... fs) {3117switch (fs.length) {3118case 0:3119return selfFilter(); // always true (on non-null inputs)3120case 1:3121return fs[0];3122}3123return new Filter() {31243125public Object filter(Object value) {3126Object res = fs[0].filter(value);3127if (res != null) {3128res = fs[1].filter(value);3129for (int i = 2; res != null && i < fs.length; i++) {3130res = fs[i].filter(value);3131}3132}3133return res;3134}31353136@Override3137public String toString() {3138return opToString("<And>", fs, "</And>");3139}3140};3141}31423143/** Use this to combine several filters with logical OR.3144* Returns either the first non-null or the last null value.3145*/3146public static Filter or(final Filter f0, final Filter f1) {3147return or(new Filter[]{f0, f1});3148}31493150public static Filter or(final Filter... fs) {3151switch (fs.length) {3152case 0:3153return nullFilter();3154case 1:3155return fs[0];3156}3157return new Filter() {31583159public Object filter(Object value) {3160Object res = fs[0].filter(value);3161if (res == null) {3162res = fs[1].filter(value);3163for (int i = 2; res == null && i < fs.length; i++) {3164res = fs[i].filter(value);3165}3166}3167return res;3168}31693170@Override3171public String toString() {3172return opToString("<Or>", fs, "</Or>");3173}3174};3175}31763177/** Use this to combine several filters with logical AND,3178* and where each non-null result is passed as the argument3179* to the next filter.3180* Returns either the first null or the last non-null value.3181*/3182public static Filter stack(final Filter f0, final Filter f1) {3183return stack(new Filter[]{f0, f1});3184}31853186public static Filter stack(final Filter... fs) {3187switch (fs.length) {3188case 0:3189return nullFilter();3190case 1:3191return fs[0];3192}3193return new Filter() {31943195public Object filter(Object value) {3196Object res = fs[0].filter(value);3197if (res != null) {3198res = fs[1].filter(res);3199for (int i = 2; res != null && i < fs.length; i++) {3200res = fs[i].filter(res);3201}3202}3203return res;3204}32053206@Override3207public String toString() {3208return opToString("<Stack>", fs, "</Stack>");3209}3210};3211}32123213/** Copy everything produced by f to sink, using addContent. */3214public static Filter content(final Filter f, final Collection<Object> sink) {3215return new Filter() {32163217public Object filter(Object value) {3218Object res = f.filter(value);3219addContent(res, sink);3220return res;3221}32223223@Override3224public String toString() {3225return opToString("<addContent>", new Object[]{f, sink},3226"</addContent>");3227}3228};3229}32303231/** Look down the tree using f, collecting fx, else recursing into x.3232* Identities:3233* <code>3234* findInTree(f, s) == findInTree(content(f, s))3235* findInTree(f) == replaceInTree(and(f, selfFilter())).3236* </code>3237*/3238public static Filter findInTree(Filter f, Collection<Object> sink) {3239if (sink != null) {3240f = content(f, sink);3241}3242return findInTree(f);3243}32443245/** Look down the tree using f, recursing into x unless fx. */3246public static Filter findInTree(final Filter f) {3247return new Filter() {32483249public Object filter(Object value) {3250Object res = f.filter(value);3251if (res != null) {3252return res;3253}3254if (value instanceof Element) {3255// recurse3256return ((Element) value).find(this);3257}3258return null;3259}32603261@Override3262public String toString() {3263return opToString("<FindInTree>", new Object[]{f},3264"</FindInTree>");3265}3266};3267}32683269/** Look down the tree using f. Replace each x with fx, else recurse.3270* If post filter g is given, optionally replace with gx after recursion.3271*/3272public static Filter replaceInTree(final Filter f, final Filter g) {3273return new Filter() {32743275public Object filter(Object value) {3276Object res = (f == null) ? null : f.filter(value);3277if (res != null) {3278return res;3279}3280if (value instanceof Element) {3281// recurse3282((Element) value).replaceAll(this);3283// Optional postorder traversal:3284if (g != null) {3285res = g.filter(value);3286}3287}3288return res; // usually null, meaning no replacement3289}32903291@Override3292public String toString() {3293return opToString("<ReplaceInTree>",3294new Object[]{f, g},3295"</ReplaceInTree>");3296}3297};3298}32993300public static Filter replaceInTree(Filter f) {3301f.getClass(); // null check3302return replaceInTree(f, null);3303}33043305/** Make a filter which calls this method on the given element.3306* If the method is static, the first argument is passed the3307* the subtree value being filtered.3308* If the method is non-static, the receiver is the subtree value itself.3309* <p>3310* Optionally, additional arguments may be specified.3311* <p>3312* If the filtered value does not match the receiver class3313* (or else the first argument type, if the method is static),3314* the filter returns null without invoking the method.3315* <p>3316* The returned filter value is the result returned from the method.3317* Optionally, a non-null special false result value may be specified.3318* If the result returned from the method is equal to that false value,3319* the filter will return null.3320*/3321public static Filter methodFilter(java.lang.reflect.Method m, Object[] extraArgs,3322Object falseResult) {3323return methodFilter(m, false, extraArgs, falseResult);3324}33253326public static Filter methodFilter(java.lang.reflect.Method m,3327Object[] args) {3328return methodFilter(m, args, null);3329}33303331public static Filter methodFilter(java.lang.reflect.Method m) {3332return methodFilter(m, null, null);3333}33343335public static Filter testMethodFilter(java.lang.reflect.Method m, Object[] extraArgs,3336Object falseResult) {3337return methodFilter(m, true, extraArgs, falseResult);3338}33393340public static Filter testMethodFilter(java.lang.reflect.Method m, Object[] extraArgs) {3341return methodFilter(m, true, extraArgs, zeroArgs.get(m.getReturnType()));3342}33433344public static Filter testMethodFilter(java.lang.reflect.Method m) {3345return methodFilter(m, true, null, zeroArgs.get(m.getReturnType()));3346}33473348private static Filter methodFilter(final java.lang.reflect.Method m,3349final boolean isTest,3350Object[] extraArgs, final Object falseResult) {3351Class[] params = m.getParameterTypes();3352final boolean isStatic = java.lang.reflect.Modifier.isStatic(m.getModifiers());3353int insertLen = (isStatic ? 1 : 0);3354if (insertLen + (extraArgs == null ? 0 : extraArgs.length) > params.length) {3355throw new IllegalArgumentException("too many arguments");3356}3357final Object[] args = (params.length == insertLen) ? null3358: new Object[params.length];3359final Class valueType = !isStatic ? m.getDeclaringClass() : params[0];3360if (valueType.isPrimitive()) {3361throw new IllegalArgumentException("filtered value must be reference type");3362}3363int fillp = insertLen;3364if (extraArgs != null) {3365for (int i = 0; i < extraArgs.length; i++) {3366args[fillp++] = extraArgs[i];3367}3368}3369if (args != null) {3370while (fillp < args.length) {3371Class param = params[fillp];3372args[fillp++] = param.isPrimitive() ? zeroArgs.get(param) : null;3373}3374}3375final Thread curt = Thread.currentThread();3376class MFilt implements Filter {33773378public Object filter(Object value) {3379if (!valueType.isInstance(value)) {3380return null; // filter fails quickly3381}3382Object[] args1 = args;3383if (isStatic) {3384if (args1 == null) {3385args1 = new Object[1];3386} else if (curt != Thread.currentThread()) // Dirty hack to curtail array copying in common case.3387{3388args1 = (Object[]) args1.clone();3389}3390args1[0] = value;3391}3392Object res;3393try {3394res = m.invoke(value, args1);3395} catch (java.lang.reflect.InvocationTargetException te) {3396Throwable ee = te.getCause();3397if (ee instanceof RuntimeException) {3398throw (RuntimeException) ee;3399}3400if (ee instanceof Error) {3401throw (Error) ee;3402}3403throw new RuntimeException("throw in filter", ee);3404} catch (IllegalAccessException ee) {3405throw new RuntimeException("access error in filter", ee);3406}3407if (res == null) {3408if (!isTest && m.getReturnType() == Void.TYPE) {3409// Void methods return self by convention.3410// (But void "tests" always return false.)3411res = value;3412}3413} else {3414if (falseResult != null && falseResult.equals(res)) {3415res = null;3416} else if (isTest) {3417// Tests return self by convention.3418res = value;3419}3420}3421return res;3422}34233424@Override3425public String toString() {3426return "<Method>" + m + "</Method>";3427}3428}3429return new MFilt();3430}3431private static HashMap<Class, Object> zeroArgs = new HashMap<Class, Object>();34323433static {3434zeroArgs.put(Boolean.TYPE, Boolean.FALSE);3435zeroArgs.put(Character.TYPE, new Character((char) 0));3436zeroArgs.put(Byte.TYPE, new Byte((byte) 0));3437zeroArgs.put(Short.TYPE, new Short((short) 0));3438zeroArgs.put(Integer.TYPE, new Integer(0));3439zeroArgs.put(Float.TYPE, new Float(0));3440zeroArgs.put(Long.TYPE, new Long(0));3441zeroArgs.put(Double.TYPE, new Double(0));3442}34433444private static String opToString(String s1, Object[] s2, String s3) {3445StringBuilder buf = new StringBuilder(s1);3446for (int i = 0; i < s2.length; i++) {3447if (s2[i] != null) {3448buf.append(s2[i]);3449}3450}3451buf.append(s3);3452return buf.toString();3453}34543455/** Call the filter on each list element x, and replace x with the3456* resulting filter value e, or its parts.3457* If e is null, keep x. (This eases use of partial-domain filters.)3458* If e is a TokenList or an anonymous Element, add e's parts3459* to the list instead of x.3460* Otherwise, replace x by e.3461* <p>3462* The effect at each list position <code>n</code> may be expressed3463* in terms of XMLKit.addContent as follows:3464* <pre>3465* Object e = f.filter(target.get(n));3466* if (e != null) {3467* target.remove(n);3468* addContent(e, target.subList(n,n));3469* }3470* </pre>3471* <p>3472* Note: To force deletion of x, simply have the filter return3473* Element.EMPTY or TokenList.EMPTY.3474* To force null filter values to have this effect,3475* use the expression: <code>or(f, emptyFilter())</code>.3476*/3477public static void replaceAll(Filter f, List<Object> target) {3478for (ListIterator<Object> i = target.listIterator(); i.hasNext();) {3479Object x = i.next();3480Object fx = f.filter(x);3481if (fx == null) {3482// Unliked addContent, a null is a no-op here.3483// i.remove();3484} else if (fx instanceof TokenList) {3485TokenList tl = (TokenList) fx;3486if (tl.size() == 1) {3487i.set(tl);3488} else {3489i.remove();3490for (String part : tl) {3491i.add(part);3492}3493}3494} else if (fx instanceof Element3495&& ((Element) fx).isAnonymous()) {3496Element anon = (Element) fx;3497if (anon.size() == 1) {3498i.set(anon);3499} else {3500i.remove();3501for (Object part : anon) {3502i.add(part);3503}3504}3505} else if (x != fx) {3506i.set(fx);3507}3508}3509}35103511/** If e is null, return zero.3512* If e is a TokenList or an anonymous Element, add e's parts3513* to the collection, and return the number of parts.3514* Otherwise, add e to the collection, and return one.3515* If the collection reference is null, the result is as if3516* a throwaway collection were used.3517*/3518public static int addContent(Object e, Collection<Object> sink) {3519if (e == null) {3520return 0;3521} else if (e instanceof TokenList) {3522TokenList tl = (TokenList) e;3523if (sink != null) {3524sink.addAll(tl);3525}3526return tl.size();3527} else if (e instanceof Element3528&& ((Element) e).isAnonymous()) {3529Element anon = (Element) e;3530if (sink != null) {3531sink.addAll(anon.asList());3532}3533return anon.size();3534} else {3535if (sink != null) {3536sink.add(e);3537}3538return 1;3539}3540}35413542static Collection<Object> newCounterColl() {3543return new AbstractCollection<Object>() {35443545int size;35463547public int size() {3548return size;3549}35503551@Override3552public boolean add(Object o) {3553++size;3554return true;3555}35563557public Iterator<Object> iterator() {3558throw new UnsupportedOperationException();3559}3560};3561}35623563/** SAX2 document handler for building Element trees. */3564private static class Builder implements ContentHandler, LexicalHandler {3565/*, EntityResolver, DTDHandler, ErrorHandler*/35663567Collection<Object> sink;3568boolean makeFrozen;3569boolean tokenizing;35703571Builder(Collection<Object> sink, boolean tokenizing, boolean makeFrozen) {3572this.sink = sink;3573this.tokenizing = tokenizing;3574this.makeFrozen = makeFrozen;3575}3576Object[] parts = new Object[30];3577int nparts = 0;3578int[] attrBases = new int[10]; // index into parts3579int[] elemBases = new int[10]; // index into parts3580int depth = -1; // index into attrBases, elemBases3581// Parts is organized this way:3582// | name0 | akey aval ... | subelem ... | name1 | ... |3583// The position of the first "akey" after name0 is attrBases[0].3584// The position of the first "subelem" after name0 is elemBases[0].3585// The position after the last part is always nparts.3586int mergeableToken = -1; // index into parts of recent CharSequence3587boolean inCData = false;35883589void addPart(Object x) {3590//System.out.println("addPart "+x);3591if (nparts == parts.length) {3592Object[] newParts = new Object[parts.length * 2];3593System.arraycopy(parts, 0, newParts, 0, parts.length);3594parts = newParts;3595}3596parts[nparts++] = x;3597}35983599Object getMergeableToken() {3600if (mergeableToken == nparts - 1) {3601assert (parts[mergeableToken] instanceof CharSequence);3602return parts[nparts - 1];3603} else {3604return null;3605}3606}36073608void clearMergeableToken() {3609if (mergeableToken >= 0) {3610// Freeze temporary StringBuffers into strings.3611assert (parts[mergeableToken] instanceof CharSequence);3612parts[mergeableToken] = parts[mergeableToken].toString();3613mergeableToken = -1;3614}3615}36163617void setMergeableToken() {3618if (mergeableToken != nparts - 1) {3619clearMergeableToken();3620mergeableToken = nparts - 1;3621assert (parts[mergeableToken] instanceof CharSequence);3622}3623}36243625// ContentHandler callbacks3626public void startElement(String ns, String localName, String name, Attributes atts) {3627clearMergeableToken();3628addPart(name.intern());3629++depth;3630if (depth == attrBases.length) {3631int oldlen = depth;3632int newlen = depth * 2;3633int[] newAB = new int[newlen];3634int[] newEB = new int[newlen];3635System.arraycopy(attrBases, 0, newAB, 0, oldlen);3636System.arraycopy(elemBases, 0, newEB, 0, oldlen);3637attrBases = newAB;3638elemBases = newEB;3639}3640attrBases[depth] = nparts;3641// Collect attributes.3642int na = atts.getLength();3643for (int k = 0; k < na; k++) {3644addPart(atts.getQName(k).intern());3645addPart(atts.getValue(k));3646}3647// Get ready to collect elements.3648elemBases[depth] = nparts;3649}36503651public void endElement(String ns, String localName, String name) {3652assert (depth >= 0);3653clearMergeableToken();3654int ebase = elemBases[depth];3655int elen = nparts - ebase;3656int abase = attrBases[depth];3657int alen = ebase - abase;3658int nbase = abase - 1;3659int cap = alen + (makeFrozen ? 0 : NEED_SLOP) + elen;3660Element e = new Element((String) parts[nbase], elen, cap);3661// Set up attributes.3662for (int k = 0; k < alen; k += 2) {3663e.parts[cap - k - 2] = parts[abase + k + 0];3664e.parts[cap - k - 1] = parts[abase + k + 1];3665}3666// Set up sub-elements.3667System.arraycopy(parts, ebase, e.parts, 0, elen);3668// Back out of this level.3669--depth;3670nparts = nbase;3671assert (e.isFrozen() == makeFrozen);3672assert (e.size() == elen);3673assert (e.attrSize() * 2 == alen);3674if (depth >= 0) {3675addPart(e);3676} else {3677sink.add(e);3678}3679}36803681public void startCDATA() {3682inCData = true;3683}36843685public void endCDATA() {3686inCData = false;3687}36883689public void characters(char[] buf, int off, int len) {3690boolean headSpace = false;3691boolean tailSpace = false;3692int firstLen;3693if (tokenizing && !inCData) {3694// Strip unquoted blanks.3695while (len > 0 && isWhitespace(buf[off])) {3696headSpace = true;3697++off;3698--len;3699}3700if (len == 0) {3701tailSpace = true; // it is all space3702}3703while (len > 0 && isWhitespace(buf[off + len - 1])) {3704tailSpace = true;3705--len;3706}3707firstLen = 0;3708while (firstLen < len && !isWhitespace(buf[off + firstLen])) {3709++firstLen;3710}3711} else {3712firstLen = len;3713}3714if (headSpace) {3715clearMergeableToken();3716}3717boolean mergeAtEnd = !tailSpace;3718// If buffer was empty, or had only ignorable blanks, do nothing.3719if (len == 0) {3720return;3721}3722// Decide whether to merge some of these chars into a previous token.3723Object prev = getMergeableToken();3724if (prev instanceof StringBuffer) {3725((StringBuffer) prev).append(buf, off, firstLen);3726} else if (prev == null) {3727addPart(new String(buf, off, firstLen));3728} else {3729// Merge two strings.3730String prevStr = prev.toString();3731StringBuffer prevBuf = new StringBuffer(prevStr.length() + firstLen);3732prevBuf.append(prevStr);3733prevBuf.append(buf, off, firstLen);3734if (mergeAtEnd && len == firstLen) {3735// Replace previous string with new StringBuffer.3736parts[nparts - 1] = prevBuf;3737} else {3738// Freeze it now.3739parts[nparts - 1] = prevBuf.toString();3740}3741}3742off += firstLen;3743len -= firstLen;3744if (len > 0) {3745// Appended only the first token.3746clearMergeableToken();3747// Add the rest as separate parts.3748while (len > 0) {3749while (len > 0 && isWhitespace(buf[off])) {3750++off;3751--len;3752}3753int nextLen = 0;3754while (nextLen < len && !isWhitespace(buf[off + nextLen])) {3755++nextLen;3756}3757assert (nextLen > 0);3758addPart(new String(buf, off, nextLen));3759off += nextLen;3760len -= nextLen;3761}3762}3763if (mergeAtEnd) {3764setMergeableToken();3765}3766}37673768public void ignorableWhitespace(char[] buf, int off, int len) {3769clearMergeableToken();3770if (false) {3771characters(buf, off, len);3772clearMergeableToken();3773}3774}37753776public void comment(char[] buf, int off, int len) {3777addPart(new Special("<!-- -->", new String(buf, off, len)));3778}37793780public void processingInstruction(String name, String instruction) {3781Element pi = new Element(name);3782pi.add(instruction);3783addPart(new Special("<? ?>", pi));3784}37853786public void skippedEntity(String name) {3787}37883789public void startDTD(String name, String publicId, String systemId) {3790}37913792public void endDTD() {3793}37943795public void startEntity(String name) {3796}37973798public void endEntity(String name) {3799}38003801public void setDocumentLocator(org.xml.sax.Locator locator) {3802}38033804public void startDocument() {3805}38063807public void endDocument() {3808}38093810public void startPrefixMapping(String prefix, String uri) {3811}38123813public void endPrefixMapping(String prefix) {3814}3815}38163817/** Produce a ContentHandler for use with an XML parser.3818* The object is <em>also</em> a LexicalHandler.3819* Every top-level Element produced will get added to sink.3820* All elements will be frozen iff makeFrozen is true.3821*/3822public static ContentHandler makeBuilder(Collection<Object> sink, boolean tokenizing, boolean makeFrozen) {3823return new Builder(sink, tokenizing, makeFrozen);3824}38253826public static ContentHandler makeBuilder(Collection<Object> sink, boolean tokenizing) {3827return new Builder(sink, tokenizing, false);3828}38293830public static ContentHandler makeBuilder(Collection<Object> sink) {3831return makeBuilder(sink, false, false);3832}38333834public static Element readFrom(Reader in, boolean tokenizing, boolean makeFrozen) throws IOException {3835Element sink = new Element();3836ContentHandler b = makeBuilder(sink.asList(), tokenizing, makeFrozen);3837XMLReader parser;3838try {3839parser = org.xml.sax.helpers.XMLReaderFactory.createXMLReader();3840} catch (SAXException ee) {3841throw new Error(ee);3842}3843//parser.setFastStandalone(true);3844parser.setContentHandler(b);3845try {3846parser.setProperty("http://xml.org/sax/properties/lexical-handler",3847(LexicalHandler) b);3848} catch (SAXException ee) {3849// Ignore. We will miss the comments and whitespace.3850}3851try {3852parser.parse(new InputSource(in));3853} catch (SAXParseException ee) {3854throw new RuntimeException("line " + ee.getLineNumber() + " col " + ee.getColumnNumber() + ": ", ee);3855} catch (SAXException ee) {3856throw new RuntimeException(ee);3857}3858switch (sink.size()) {3859case 0:3860return null;3861case 1:3862if (sink.get(0) instanceof Element) {3863return (Element) sink.get(0);3864}3865// fall through3866default:3867if (makeFrozen) {3868sink.shallowFreeze();3869}3870return sink;3871}3872}38733874public static Element readFrom(Reader in, boolean tokenizing) throws IOException {3875return readFrom(in, tokenizing, false);3876}38773878public static Element readFrom(Reader in) throws IOException {3879return readFrom(in, false, false);3880}38813882public static void prettyPrintTo(OutputStream out, Element e) throws IOException {3883prettyPrintTo(new OutputStreamWriter(out), e);3884}38853886public static void prettyPrintTo(Writer out, Element e) throws IOException {3887Printer pr = new Printer(out);3888pr.pretty = true;3889pr.print(e);3890}38913892static class Outputter {38933894ContentHandler ch;3895LexicalHandler lh;38963897Outputter(ContentHandler ch, LexicalHandler lh) {3898this.ch = ch;3899this.lh = lh;3900}3901AttributesImpl atts = new AttributesImpl(); // handy39023903void output(Object x) throws SAXException {3904// Cf. jdom.org/jdom-b8/src/java/org/jdom/output/SAXOutputter.java3905if (x instanceof Element) {3906Element e = (Element) x;3907atts.clear();3908for (int asize = e.attrSize(), k = 0; k < asize; k++) {3909String key = e.getAttrName(k);3910String val = e.getAttr(k);3911atts.addAttribute("", "", key, "CDATA", val);3912}3913ch.startElement("", "", e.getName(), atts);3914for (int i = 0; i < e.size(); i++) {3915output(e.get(i));3916}3917ch.endElement("", "", e.getName());3918} else if (x instanceof Special) {3919Special sp = (Special) x;3920if (sp.kind.startsWith("<!--")) {3921char[] chars = sp.value.toString().toCharArray();3922lh.comment(chars, 0, chars.length);3923} else if (sp.kind.startsWith("<?")) {3924Element nameInstr = (Element) sp.value;3925ch.processingInstruction(nameInstr.name,3926nameInstr.get(0).toString());3927} else {3928// drop silently3929}3930} else {3931char[] chars = x.toString().toCharArray();3932ch.characters(chars, 0, chars.length);3933}3934}3935}39363937public static class Printer {39383939public Writer w;3940public boolean tokenizing;3941public boolean pretty;3942public boolean abbreviated; // nonstandard format cuts down on noise3943int depth = 0;3944boolean prevStr;3945int tabStop = 2;39463947public Printer(Writer w) {3948this.w = w;3949}39503951public Printer() {3952StringWriter sw = new StringWriter();3953this.w = sw;39543955}39563957public String nextString() {3958StringBuffer sb = ((StringWriter) w).getBuffer();3959String next = sb.toString();3960sb.setLength(0); // reset3961return next;3962}39633964void indent(int depth) throws IOException {3965if (depth > 0) {3966w.write("\n");3967}3968int nsp = tabStop * depth;3969while (nsp > 0) {3970String s = " ";3971String t = s.substring(0, nsp < s.length() ? nsp : s.length());3972w.write(t);3973nsp -= t.length();3974}3975}39763977public void print(Element e) throws IOException {3978if (e.isAnonymous()) {3979printParts(e);3980return;3981}3982printRecursive(e);3983}39843985public void println(Element e) throws IOException {3986print(e);3987w.write("\n");3988w.flush();3989}39903991public void printRecursive(Element e) throws IOException {3992boolean indented = false;3993if (pretty && !prevStr && e.size() + e.attrSize() > 0) {3994indent(depth);3995indented = true;3996}3997w.write("<");3998w.write(e.name);3999for (int asize = e.attrSize(), k = 0; k < asize; k++) {4000String key = e.getAttrName(k);4001String val = e.getAttr(k);4002w.write(" ");4003w.write(key);4004w.write("=");4005if (val == null) {4006w.write("null"); // Should not happen....4007} else if (val.indexOf("\"") < 0) {4008w.write("\"");4009writeToken(val, '"', w);4010w.write("\"");4011} else {4012w.write("'");4013writeToken(val, '\'', w);4014w.write("'");4015}4016}4017if (e.size() == 0) {4018w.write("/>");4019} else {4020++depth;4021if (abbreviated) {4022w.write("/");4023} else {4024w.write(">");4025}4026prevStr = false;4027printParts(e);4028if (abbreviated) {4029w.write(">");4030} else {4031if (indented && !prevStr) {4032indent(depth - 1);4033}4034w.write("</");4035w.write(e.name);4036w.write(">");4037}4038prevStr = false;4039--depth;4040}4041}40424043private void printParts(Element e) throws IOException {4044for (int i = 0; i < e.size(); i++) {4045Object x = e.get(i);4046if (x instanceof Element) {4047printRecursive((Element) x);4048prevStr = false;4049} else if (x instanceof Special) {4050w.write(((Special) x).toString());4051prevStr = false;4052} else {4053String s = String.valueOf(x);4054if (pretty) {4055s = s.trim();4056if (s.length() == 0) {4057continue;4058}4059}4060if (prevStr) {4061w.write(' ');4062}4063writeToken(s, tokenizing ? ' ' : (char) -1, w);4064prevStr = true;4065}4066if (pretty && depth == 0) {4067w.write("\n");4068prevStr = false;4069}4070}4071}4072}40734074public static void output(Object e, ContentHandler ch, LexicalHandler lh) throws SAXException {4075new Outputter(ch, lh).output(e);4076}40774078public static void output(Object e, ContentHandler ch) throws SAXException {4079if (ch instanceof LexicalHandler) {4080output(e, ch, (LexicalHandler) ch);4081} else {4082output(e, ch, null);4083}4084}40854086public static void writeToken(String val, char quote, Writer w) throws IOException {4087int len = val.length();4088boolean canUseCData = (quote != '"' && quote != '\'');4089int vpos = 0;4090for (int i = 0; i < len; i++) {4091char ch = val.charAt(i);4092if ((ch == '<' || ch == '&' || ch == '>' || ch == quote)4093|| (quote == ' ' && isWhitespace(ch))) {4094if (canUseCData) {4095assert (vpos == 0);4096writeCData(val, w);4097return;4098} else {4099if (vpos < i) {4100w.write(val, vpos, i - vpos);4101}4102String esc;4103switch (ch) {4104case '&':4105esc = "&";4106break;4107case '<':4108esc = "<";4109break;4110case '\'':4111esc = "'";4112break;4113case '"':4114esc = """;4115break;4116case '>':4117esc = ">";4118break;4119default:4120esc = "&#" + (int) ch + ";";4121break;4122}4123w.write(esc);4124vpos = i + 1; // skip escaped char4125}4126}4127}4128// write the unquoted tail4129w.write(val, vpos, val.length() - vpos);4130}41314132public static void writeCData(String val, Writer w) throws IOException {4133String begCData = "<![CDATA[";4134String endCData = "]]>";4135w.write(begCData);4136for (int vpos = 0, split;; vpos = split) {4137split = val.indexOf(endCData, vpos);4138if (split < 0) {4139w.write(val, vpos, val.length() - vpos);4140w.write(endCData);4141return;4142}4143split += 2; // bisect the "]]>" goo4144w.write(val, vpos, split - vpos);4145w.write(endCData);4146w.write(begCData);4147}4148}41494150public static TokenList convertToList(String str) {4151if (str == null) {4152return null;4153}4154return new TokenList(str);4155}41564157/** If str is null, empty, or blank, returns null.4158* Otherwise, return a Double if str spells a double value and contains '.' or 'e'.4159* Otherwise, return an Integer if str spells an int value.4160* Otherwise, return a Long if str spells a long value.4161* Otherwise, return a BigInteger for the string.4162* Otherwise, throw NumberFormatException.4163*/4164public static Number convertToNumber(String str) {4165if (str == null) {4166return null;4167}4168str = str.trim();4169if (str.length() == 0) {4170return null;4171}4172if (str.indexOf('.') >= 04173|| str.indexOf('e') >= 04174|| str.indexOf('E') >= 0) {4175return Double.valueOf(str);4176}4177try {4178long lval = Long.parseLong(str);4179if (lval == (int) lval) {4180// Narrow to Integer, if possible.4181return new Integer((int) lval);4182}4183return new Long(lval);4184} catch (NumberFormatException ee) {4185// Could not represent it as a long.4186return new java.math.BigInteger(str, 10);4187}4188}41894190public static Number convertToNumber(String str, Number dflt) {4191Number n = convertToNumber(str);4192return (n == null) ? dflt : n;4193}41944195public static long convertToLong(String str) {4196return convertToLong(str, 0);4197}41984199public static long convertToLong(String str, long dflt) {4200Number n = convertToNumber(str);4201return (n == null) ? dflt : n.longValue();4202}42034204public static double convertToDouble(String str) {4205return convertToDouble(str, 0);4206}42074208public static double convertToDouble(String str, double dflt) {4209Number n = convertToNumber(str);4210return (n == null) ? dflt : n.doubleValue();4211}42124213// Testing:4214public static void main(String... av) throws Exception {4215Element.method("getAttr");4216//new org.jdom.input.SAXBuilder().build(file).getRootElement();4217//jdom.org/jdom-b8/src/java/org/jdom/input/SAXBuilder.java4218//Document build(InputSource in) throws JDOMException42194220int reps = 0;42214222boolean tokenizing = false;4223boolean makeFrozen = false;4224if (av.length > 0) {4225tokenizing = true;4226try {4227reps = Integer.parseInt(av[0]);4228} catch (NumberFormatException ee) {4229}4230}4231Reader inR = new BufferedReader(new InputStreamReader(System.in));4232String inS = null;4233if (reps > 1) {4234StringWriter inBufR = new StringWriter(1 << 14);4235char[] cbuf = new char[1024];4236for (int nr; (nr = inR.read(cbuf)) >= 0;) {4237inBufR.write(cbuf, 0, nr);4238}4239inS = inBufR.toString();4240inR = new StringReader(inS);4241}4242Element e = XMLKit.readFrom(inR, tokenizing, makeFrozen);4243System.out.println("transform = " + e.findAll(methodFilter(Element.method("prettyString"))));4244System.out.println("transform = " + e.findAll(testMethodFilter(Element.method("hasText"))));4245long tm0 = 0;4246int warmup = 10;4247for (int i = 1; i < reps; i++) {4248inR = new StringReader(inS);4249readFrom(inR, tokenizing, makeFrozen);4250if (i == warmup) {4251System.out.println("Start timing...");4252tm0 = System.currentTimeMillis();4253}4254}4255if (tm0 != 0) {4256long tm1 = System.currentTimeMillis();4257System.out.println((reps - warmup) + " in " + (tm1 - tm0) + " ms");4258}4259System.out.println("hashCode = " + e.hashCode());4260String eStr = e.toString();4261System.out.println(eStr);4262Element e2 = readFrom(new StringReader(eStr), tokenizing, !makeFrozen);4263System.out.println("hashCode = " + e2.hashCode());4264if (!e.equals(e2)) {4265System.out.println("**** NOT EQUAL 1\n" + e2);4266}4267e = e.deepCopy();4268System.out.println("hashCode = " + e.hashCode());4269if (!e.equals(e2)) {4270System.out.println("**** NOT EQUAL 2");4271}4272e2.shallowFreeze();4273System.out.println("hashCode = " + e2.hashCode());4274if (!e.equals(e2)) {4275System.out.println("**** NOT EQUAL 3");4276}4277if (false) {4278System.out.println(e);4279} else {4280prettyPrintTo(new OutputStreamWriter(System.out), e);4281}4282System.out.println("Flat text:|" + e.getFlatText() + "|");4283{4284System.out.println("<!--- Sorted: --->");4285Element ce = e.copyContentOnly();4286ce.sort();4287prettyPrintTo(new OutputStreamWriter(System.out), ce);4288}4289{4290System.out.println("<!--- Trimmed: --->");4291Element tr = e.deepCopy();4292findInTree(testMethodFilter(Element.method("trimText"))).filter(tr);4293System.out.println(tr);4294}4295{4296System.out.println("<!--- Unstrung: --->");4297Element us = e.deepCopy();4298int nr = us.retainAllInTree(elementFilter(), null);4299System.out.println("nr=" + nr);4300System.out.println(us);4301}4302{4303System.out.println("<!--- Rollup: --->");4304Element ru = e.deepCopy();4305Filter makeAnonF =4306methodFilter(Element.method("setName"),4307new Object[]{ANON_NAME});4308Filter testSizeF =4309testMethodFilter(Element.method("size"));4310Filter walk =4311replaceInTree(and(not(elementFilter()), emptyFilter()),4312and(testSizeF, makeAnonF));4313ru = (Element) walk.filter(ru);4314//System.out.println(ru);4315prettyPrintTo(new OutputStreamWriter(System.out), ru);4316}4317}43184319static boolean isWhitespace(char c) {4320switch (c) {4321case 0x20:4322case 0x09:4323case 0x0D:4324case 0x0A:4325return true;4326}4327return false;4328}4329}433043314332