Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/openjdk-multiarch-jdk8u
Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/test/tools/pack200/pack200-verifier/src/xmlkit/XMLKit.java
38867 views
1
/*
2
* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
3
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4
*
5
* This code is free software; you can redistribute it and/or modify it
6
* under the terms of the GNU General Public License version 2 only, as
7
* published by the Free Software Foundation. Oracle designates this
8
* particular file as subject to the "Classpath" exception as provided
9
* by Oracle in the LICENSE file that accompanied this code.
10
*
11
* This code is distributed in the hope that it will be useful, but WITHOUT
12
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14
* version 2 for more details (a copy is included in the LICENSE file that
15
* accompanied this code).
16
*
17
* You should have received a copy of the GNU General Public License version
18
* 2 along with this work; if not, write to the Free Software Foundation,
19
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20
*
21
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22
* or visit www.oracle.com if you need additional information or have any
23
* questions.
24
*/
25
package xmlkit; // -*- mode: java; indent-tabs-mode: nil -*-
26
27
// XML Implementation packages:
28
import java.util.*;
29
30
import java.io.Reader;
31
import java.io.Writer;
32
import java.io.OutputStream;
33
import java.io.InputStreamReader;
34
import java.io.OutputStreamWriter;
35
import java.io.BufferedReader;
36
import java.io.PrintWriter;
37
import java.io.StringWriter;
38
import java.io.StringReader;
39
40
import java.io.IOException;
41
42
import org.xml.sax.XMLReader;
43
import org.xml.sax.InputSource;
44
import org.xml.sax.ContentHandler;
45
import org.xml.sax.SAXException;
46
import org.xml.sax.SAXParseException;
47
import org.xml.sax.Attributes;
48
import org.xml.sax.ext.LexicalHandler;
49
import org.xml.sax.helpers.AttributesImpl;
50
51
/**
52
* A kit of methods and classes useful for manipulating XML trees in
53
* memory. They are very compact and easy to use. An XML element
54
* occupies six pointers of overhead (like two arrays) plus a pointer
55
* for its name, each attribute name and value, and each sub-element.
56
* Many useful XML operations (or Lisp-like calls) can be accomplished
57
* with a single method call on an element itself.
58
* <p>
59
* There is strong integration with the Java collection classes.
60
* There are viewing and conversion operators to and from various
61
* collection types. Elements directly support list iterators.
62
* Most <tt>List</tt> methods work analogously on elements.
63
* <p>
64
* Because of implementation compromises, these XML trees are less
65
* functional than many standard XML classes.
66
* <ul>
67
* <li>There are no parent or sibling pointers in the tree.</li>
68
* <li>Attribute names are simple strings, with no namespaces.</li>
69
* <li>There is no internal support for schemas or validation.</li>
70
* </ul>
71
* <p>
72
* Here is a summary of functionality in <tt>XMLKit</tt>.
73
* (Overloaded groups of methods are summarized by marking some
74
* arguments optional with their default values. Some overloaded
75
* arguments are marked with their alternative types separated by
76
* a bar "|". Arguments or return values for which a null is
77
* specially significant are marked by an alternative "|null".
78
* Accessors which have corresponding setters are marked
79
* by "/set". Removers which have corresponding retainers are marked
80
* by "/retain".)
81
* <pre>
82
* --- element construction
83
* new Element(int elemCapacity=4), String name=""
84
* new Element(String name, String[] attrs={}, Element[] elems={}, int elemCapacity=4)
85
* new Element(String name, String[] attrs, Object[] elems, int elemCapacity=4)
86
* new Element(Element original) // shallow copy
87
* new Element(String name="", Collection elems) // coercion
88
*
89
* Element shallowCopy()
90
* Element shallowFreeze() // side-effecting
91
* Element deepCopy()
92
* Element deepFreeze() // not side-effecting
93
*
94
* EMPTY // frozen empty anonymous element
95
* void ensureExtraCapacity(int)
96
* void trimToSize()
97
* void sortAttrs() // sort by key
98
*
99
* --- field accessors
100
* String getName()/set
101
* int size()
102
* boolean isEmpty()
103
* boolean isFrozen()
104
* boolean isAnonymous()
105
* int getExtraCapacity()/set
106
* int attrSize()
107
*
108
* --- attribute accessors
109
* String getAttr(int i)/set
110
* String getAttrName(int i)
111
*
112
* String getAttr(String key)/set
113
* List getAttrList(String key)/set
114
* Number getAttrNumber(String key)/set
115
* long getAttrLong(String key)/set
116
* double getAttrDouble(String key)/set
117
*
118
* String getAttr(String key, String dflt=null)
119
* long getAttrLong(String key, long dflt=0)
120
* double getAttrDouble(String key, double dflt=0)
121
*
122
* Element copyAttrsOnly()
123
* Element getAttrs()/set =&gt; <em>&lt;&gt;&lt;key&gt;value&lt;/key&gt;...&lt;/&gt;</em>
124
* void addAttrs(Element attrs)
125
*
126
* void removeAttr(int i)
127
* void clearAttrs()
128
*
129
* --- element accessors
130
* Object get(int i)/set
131
* Object getLast() | null
132
* Object[] toArray()
133
* Element copyContentOnly()
134
*
135
* void add(int i=0, Object subElem)
136
* int addAll(int i=0, Collection | Element elems)
137
* int addContent(int i=0, TokenList|Element|Object|null)
138
* void XMLKit.addContent(TokenList|Element|Object|null, Collection sink|null)
139
*
140
* void clear(int beg=0, int end=size)
141
* void sort(Comparator=contentOrder())
142
* void reverse()
143
* void shuffle(Random rnd=(anonymous))
144
* void rotate(int distance)
145
* Object min/max(Comparator=contentOrder())
146
*
147
* --- text accessors
148
* CharSequence getText()/set
149
* CharSequence getUnmarkedText()
150
* int addText(int i=size, CharSequence)
151
* void trimText();
152
*
153
* --- views
154
* List asList() // element view
155
* ListIterator iterator()
156
* PrintWriter asWriter()
157
* Map asAttrMap()
158
* Iterable<CharSequence> texts()
159
* Iterable<Element> elements()
160
* Iterable<T> partsOnly(Class<T>)
161
* String[] toStrings()
162
*
163
* --- queries
164
* boolean equals(Element | Object)
165
* int compareTo(Element | Object)
166
* boolean equalAttrs(Element)
167
* int hashCode()
168
* boolean isText() // every sub-elem is CharSequence
169
* boolean hasText() // some sub-elem is CharSequence
170
*
171
* boolean contains(Object)
172
* boolean containsAttr(String)
173
*
174
* int indexOf(Object)
175
* int indexOf(Filter, int fromIndex=0)
176
* int lastIndexOf(Object)
177
* int lastIndexOf(Filter, int fromIndex=size-1)
178
*
179
* int indexOfAttr(String)
180
*
181
* // finders, removers, and replacers do addContent of each filtered value
182
* // (i.e., TokenLists and anonymous Elements are broken out into their parts)
183
* boolean matches(Filter)
184
*
185
* Object find(Filter, int fromIndex=0)
186
* Object findLast(Filter, int fromIndex=size-1)
187
* Element findAll(Filter, int fromIndex=0 &amp; int toIndex=size)
188
* int findAll(Filter, Collection sink | null, int fromIndex=0 &amp; int toIndex=size)
189
*
190
* Element removeAllInTree(Filter)/retain
191
* int findAllInTree(Filter, Collection sink | null)
192
* int countAllInTree(Filter)
193
* Element removeAllInTree(Filter)/retain
194
* int removeAllInTree(Filter, Collection sink | null)/retain
195
* void replaceAllInTree(Filter)
196
*
197
* Element findElement(String name=any)
198
* Element findAllElements(String name=any)
199
*
200
* Element findWithAttr(String key, String value=any)
201
* Element findAllWithAttr(String key, String value=any)
202
*
203
* Element removeElement(String name=any)
204
* Element removeAllElements(String name=any)/retain
205
*
206
* Element removeWithAttr(String key, String value=any)
207
* Element removeAllWithAttr(String key, String value=any)/retain
208
*
209
* //countAll is the same as findAll but with null sink
210
* int countAll(Filter)
211
* int countAllElements(String name=any)
212
* int countAllWithAttr(String key, String value=any)
213
*
214
* void replaceAll(Filter, int fromIndex=0 &amp; int toIndex=size)
215
* void replaceAllInTree(Filter)
216
* void XMLKit.replaceAll(Filter, List target) //if(fx){remove x;addContent fx}
217
*
218
* --- element mutators
219
* boolean remove(Object)
220
* Object remove(int)
221
* Object removeLast() | null
222
*
223
* Object remove(Filter, int fromIndex=0)
224
* Object removeLast(Filter, int fromIndex=size-1)
225
* Element sink = removeAll(Filter, int fromIndex=0 &amp; int toIndex=size)/retain
226
* int count = removeAll(Filter, int fromIndex=0 &amp; int toIndex=size, Collection sink | null)/retain
227
*
228
* Element removeAllElements(String name=any)
229
*
230
* --- attribute mutators
231
* ??int addAllAttrsFrom(Element attrSource)
232
*
233
* --- parsing and printing
234
* void tokenize(String delims=whitespace, returnDelims=false)
235
* void writeTo(Writer)
236
* void writePrettyTo(Writer)
237
* String prettyString()
238
* String toString()
239
*
240
* ContentHandler XMLKit.makeBuilder(Collection sink, tokenizing=false, makeFrozen=false) // for standard XML parser
241
* Element XMLKit.readFrom(Reader, tokenizing=false, makeFrozen=false)
242
* void XMLKit.prettyPrintTo(Writer | OutputStream, Element)
243
* class XMLKit.Printer(Writer) { void print/Recursive(Element) }
244
* void XMLKit.output(Object elem, ContentHandler, LexicalHandler=null)
245
* void XMLKit.writeToken(String, char quote, Writer)
246
* void XMLKit.writeCData(String, Writer)
247
* Number XMLKit.convertToNumber(String, Number dflt=null)
248
* long XMLKit.convertToLong(String, long dflt=0)
249
* double XMLKit.convertToDouble(String, double dflt=0)
250
*
251
* --- filters
252
* XMLKit.ElementFilter { Element filter(Element) }
253
* XMLKit.elementFilter(String name=any | Collection nameSet)
254
* XMLKit.AttrFilter(String key) { boolean test(String value) }
255
* XMLKit.attrFilter(String key, String value=any)
256
* XMLKit.attrFilter(Element matchThis, String key)
257
* XMLKit.classFilter(Class)
258
* XMLKit.textFilter() // matches any CharSequence
259
* XMLKit.specialFilter() // matches any Special element
260
* XMLKit.methodFilter(Method m, Object[] args=null, falseResult=null)
261
* XMLKit.testMethodFilter(Method m, Object[] args=null)
262
* XMLKit.not(Filter) // inverts sense of Filter
263
* XMLKit.and(Filter&amp;Filter | Filter[])
264
* XMLKit.or(Filter&amp;Filter | Filter[])
265
* XMLKit.stack(Filter&amp;Filter | Filter[]) // result is (fx && g(fx))
266
* XMLKit.content(Filter, Collection sink) // copies content to sink
267
* XMLKit.replaceInTree(Filter pre, Filter post=null) // pre-replace else recur
268
* XMLKit.findInTree(Filter pre, Collection sink=null) // pre-find else recur
269
* XMLKit.nullFilter() // ignores input, always returns null (i.e., false)
270
* XMLKit.selfFilter( ) // always returns input (i.e., true)
271
* XMLKit.emptyFilter() // ignores input, always returns EMPTY
272
* XMLKit.constantFilter(Object) // ignores input, always returns constant
273
*
274
* --- misc
275
* Comparator XMLKit.contentOrder() // for comparing/sorting mixed content
276
* Method XMLKit.Element.method(String name) // returns Element method
277
* </pre>
278
*
279
* @author jrose
280
*/
281
public abstract class XMLKit {
282
283
private XMLKit() {
284
}
285
// We need at least this much slop if the element is to stay unfrozen.
286
static final int NEED_SLOP = 1;
287
static final Object[] noPartsFrozen = {};
288
static final Object[] noPartsNotFrozen = new Object[NEED_SLOP];
289
static final String WHITESPACE_CHARS = " \t\n\r\f";
290
static final String ANON_NAME = new String("*"); // unique copy of "*"
291
292
public static final class Element implements Comparable<Element>, Iterable<Object> {
293
// Note: Does not implement List, because it has more
294
// significant parts besides its sub-elements. Therefore,
295
// hashCode and equals must be more distinctive than Lists.
296
297
// <name> of element
298
String name;
299
// number of child elements, in parts[0..size-1]
300
int size;
301
// The parts start with child elements:: {e0, e1, e2, ...}.
302
// Following that are optional filler elements, all null.
303
// Following that are attributes as key/value pairs.
304
// They are in reverse: {...key2, val2, key1, val1, key0, val0}.
305
// Child elements and attr keys and values are never null.
306
Object[] parts;
307
308
// Build a partially-constructed node.
309
// Caller is responsible for initializing promised attributes.
310
Element(String name, int size, int capacity) {
311
this.name = name.toString();
312
this.size = size;
313
assert (size <= capacity);
314
this.parts = capacity > 0 ? new Object[capacity] : noPartsFrozen;
315
}
316
317
/** An anonymous, empty element.
318
* Optional elemCapacity argument is expected number of sub-elements.
319
*/
320
public Element() {
321
this(ANON_NAME, 0, NEED_SLOP + 4);
322
}
323
324
public Element(int extraCapacity) {
325
this(ANON_NAME, 0, NEED_SLOP + Math.max(0, extraCapacity));
326
}
327
328
/** An empty element with the given name.
329
* Optional extraCapacity argument is expected number of sub-elements.
330
*/
331
public Element(String name) {
332
this(name, 0, NEED_SLOP + 4);
333
}
334
335
public Element(String name, int extraCapacity) {
336
this(name, 0, NEED_SLOP + Math.max(0, extraCapacity));
337
}
338
339
/** An empty element with the given name and attributes.
340
* Optional extraCapacity argument is expected number of sub-elements.
341
*/
342
public Element(String name, String... attrs) {
343
this(name, attrs, (Element[]) null, 0);
344
}
345
346
public Element(String name, String[] attrs, int extraCapacity) {
347
this(name, attrs, (Element[]) null, extraCapacity);
348
}
349
350
/** An empty element with the given name and sub-elements.
351
* Optional extraCapacity argument is expected extra sub-elements.
352
*/
353
public Element(String name, Element... elems) {
354
this(name, (String[]) null, elems, 0);
355
}
356
357
public Element(String name, Element[] elems, int extraCapacity) {
358
this(name, (String[]) null, elems, extraCapacity);
359
}
360
361
/** An empty element with the given name, attributes, and sub-elements.
362
* Optional extraCapacity argument is expected extra sub-elements.
363
*/
364
public Element(String name, String[] attrs, Object... elems) {
365
this(name, attrs, elems, 0);
366
}
367
368
public Element(String name, String[] attrs, Object[] elems, int extraCapacity) {
369
this(name, 0,
370
((elems == null) ? 0 : elems.length)
371
+ Math.max(0, extraCapacity)
372
+ NEED_SLOP
373
+ ((attrs == null) ? 0 : attrs.length));
374
int ne = ((elems == null) ? 0 : elems.length);
375
int na = ((attrs == null) ? 0 : attrs.length);
376
int fillp = 0;
377
for (int i = 0; i < ne; i++) {
378
if (elems[i] != null) {
379
parts[fillp++] = elems[i];
380
}
381
}
382
size = fillp;
383
for (int i = 0; i < na; i += 2) {
384
setAttr(attrs[i + 0], attrs[i + 1]);
385
}
386
}
387
388
public Element(Collection c) {
389
this(c.size());
390
addAll(c);
391
}
392
393
public Element(String name, Collection c) {
394
this(name, c.size());
395
addAll(c);
396
}
397
398
/** Shallow copy. Same as old.shallowCopy().
399
* Optional extraCapacity argument is expected extra sub-elements.
400
*/
401
public Element(Element old) {
402
this(old, 0);
403
}
404
405
public Element(Element old, int extraCapacity) {
406
this(old.name, old.size,
407
old.size
408
+ Math.max(0, extraCapacity) + NEED_SLOP
409
+ old.attrLength());
410
// copy sub-elements
411
System.arraycopy(old.parts, 0, parts, 0, size);
412
int alen = parts.length
413
- (size + Math.max(0, extraCapacity) + NEED_SLOP);
414
// copy attributes
415
System.arraycopy(old.parts, old.parts.length - alen,
416
parts, parts.length - alen,
417
alen);
418
assert (!isFrozen());
419
}
420
421
/** Shallow copy. Same as new Element(this). */
422
public Element shallowCopy() {
423
return new Element(this);
424
}
425
static public final Element EMPTY = new Element(ANON_NAME, 0, 0);
426
427
Element deepFreezeOrCopy(boolean makeFrozen) {
428
if (makeFrozen && isFrozen()) {
429
return this; // no need to copy it
430
}
431
int alen = attrLength();
432
int plen = size + (makeFrozen ? 0 : NEED_SLOP) + alen;
433
Element copy = new Element(name, size, plen);
434
// copy attributes
435
System.arraycopy(parts, parts.length - alen, copy.parts, plen - alen, alen);
436
// copy sub-elements
437
for (int i = 0; i < size; i++) {
438
Object e = parts[i];
439
String str;
440
if (e instanceof Element) { // recursion is common case
441
e = ((Element) e).deepFreezeOrCopy(makeFrozen);
442
} else if (makeFrozen) {
443
// Freeze StringBuffers, etc.
444
e = fixupString(e);
445
}
446
copy.setRaw(i, e);
447
}
448
return copy;
449
}
450
451
/** Returns new Element(this), and also recursively copies sub-elements. */
452
public Element deepCopy() {
453
return deepFreezeOrCopy(false);
454
}
455
456
/** Returns frozen version of deepCopy. */
457
public Element deepFreeze() {
458
return deepFreezeOrCopy(true);
459
}
460
461
/** Freeze this element.
462
* Throw an IllegalArgumentException if any sub-element is not already frozen.
463
* (Use deepFreeze() to make a frozen copy of an entire element tree.)
464
*/
465
public void shallowFreeze() {
466
if (isFrozen()) {
467
return;
468
}
469
int alen = attrLength();
470
Object[] nparts = new Object[size + alen];
471
// copy attributes
472
System.arraycopy(parts, parts.length - alen, nparts, size, alen);
473
// copy sub-elements
474
for (int i = 0; i < size; i++) {
475
Object e = parts[i];
476
String str;
477
if (e instanceof Element) { // recursion is common case
478
if (!((Element) e).isFrozen()) {
479
throw new IllegalArgumentException("Sub-element must be frozen.");
480
}
481
} else {
482
// Freeze StringBuffers, etc.
483
e = fixupString(e);
484
}
485
nparts[i] = e;
486
}
487
parts = nparts;
488
assert (isFrozen());
489
}
490
491
/** Return the name of this element. */
492
public String getName() {
493
return name;
494
}
495
496
/** Change the name of this element. */
497
public void setName(String name) {
498
checkNotFrozen();
499
this.name = name.toString();
500
}
501
502
/** Reports if the element's name is a particular string (spelled "*").
503
* Such elements are created by the nullary Element constructor,
504
* and by query functions which return multiple values,
505
* such as <tt>findAll</tt>.
506
*/
507
public boolean isAnonymous() {
508
return name == ANON_NAME;
509
}
510
511
/** Return number of elements. (Does not include attributes.) */
512
public int size() {
513
return size;
514
}
515
516
/** True if no elements. (Does not consider attributes.) */
517
public boolean isEmpty() {
518
return size == 0;
519
}
520
521
/** True if this element does not allow modification. */
522
public boolean isFrozen() {
523
// It is frozen iff there is no slop space.
524
return !hasNulls(NEED_SLOP);
525
}
526
527
void checkNotFrozen() {
528
if (isFrozen()) {
529
throw new UnsupportedOperationException("cannot modify frozen element");
530
}
531
}
532
533
/** Remove specified elements. (Does not affect attributes.) */
534
public void clear() {
535
clear(0, size);
536
}
537
538
public void clear(int beg) {
539
clear(beg, size);
540
}
541
542
public void clear(int beg, int end) {
543
if (end > size) {
544
badIndex(end);
545
}
546
if (beg < 0 || beg > end) {
547
badIndex(beg);
548
}
549
if (beg == end) {
550
return;
551
}
552
checkNotFrozen();
553
if (end == size) {
554
if (beg == 0
555
&& parts.length > 0 && parts[parts.length - 1] == null) {
556
// If no attributes, free the parts array.
557
parts = noPartsNotFrozen;
558
size = 0;
559
} else {
560
clearParts(beg, size);
561
size = beg;
562
}
563
} else {
564
close(beg, end - beg);
565
}
566
}
567
568
void clearParts(int beg, int end) {
569
for (int i = beg; i < end; i++) {
570
parts[i] = null;
571
}
572
}
573
574
/** True if name, attributes, and elements are the same. */
575
public boolean equals(Element that) {
576
if (!this.name.equals(that.name)) {
577
return false;
578
}
579
if (this.size != that.size) {
580
return false;
581
}
582
// elements must be equal and ordered
583
Object[] thisParts = this.parts;
584
Object[] thatParts = that.parts;
585
for (int i = 0; i < size; i++) {
586
Object thisPart = thisParts[i];
587
Object thatPart = thatParts[i];
588
589
if (thisPart instanceof Element) { // recursion is common case
590
if (!thisPart.equals(thatPart)) {
591
return false;
592
}
593
} else {
594
// If either is a non-string char sequence, normalize it.
595
thisPart = fixupString(thisPart);
596
thatPart = fixupString(thatPart);
597
if (!thisPart.equals(thatPart)) {
598
return false;
599
}
600
}
601
}
602
// finally, attributes must be equal (unordered)
603
return this.equalAttrs(that);
604
}
605
// bridge method
606
607
public boolean equals(Object o) {
608
if (!(o instanceof Element)) {
609
return false;
610
}
611
return equals((Element) o);
612
}
613
614
public int hashCode() {
615
int hc = 0;
616
int alen = attrLength();
617
for (int i = parts.length - alen; i < parts.length; i += 2) {
618
hc += (parts[i + 0].hashCode() ^ parts[i + 1].hashCode());
619
}
620
hc ^= hc << 11;
621
hc += name.hashCode();
622
for (int i = 0; i < size; i++) {
623
hc ^= hc << 7;
624
Object p = parts[i];
625
if (p instanceof Element) {
626
hc += p.hashCode(); // recursion is common case
627
} else {
628
hc += fixupString(p).hashCode();
629
}
630
}
631
hc ^= hc >>> 19;
632
return hc;
633
}
634
635
/** Compare lexicographically. Earlier-spelled attrs are more sigificant. */
636
public int compareTo(Element that) {
637
int r;
638
// Primary key is element name.
639
r = this.name.compareTo(that.name);
640
if (r != 0) {
641
return r;
642
}
643
644
// Secondary key is attributes, as if in normal key order.
645
// The key/value pairs are sorted as a token sequence.
646
int thisAlen = this.attrLength();
647
int thatAlen = that.attrLength();
648
if (thisAlen != 0 || thatAlen != 0) {
649
r = compareAttrs(thisAlen, that, thatAlen, true);
650
assert (assertAttrCompareOK(r, that));
651
if (r != 0) {
652
return r;
653
}
654
}
655
656
// Finally, elements should be equal and ordered,
657
// and the first difference rules.
658
Object[] thisParts = this.parts;
659
Object[] thatParts = that.parts;
660
int minSize = this.size;
661
if (minSize > that.size) {
662
minSize = that.size;
663
}
664
Comparator<Object> cc = contentOrder();
665
for (int i = 0; i < minSize; i++) {
666
r = cc.compare(thisParts[i], thatParts[i]);
667
if (r != 0) {
668
return r;
669
}
670
}
671
//if (this.size < that.size) return -1;
672
return this.size - that.size;
673
}
674
675
private boolean assertAttrCompareOK(int r, Element that) {
676
Element e0 = this.copyAttrsOnly();
677
Element e1 = that.copyAttrsOnly();
678
e0.sortAttrs();
679
e1.sortAttrs();
680
int r2;
681
for (int k = 0;; k++) {
682
boolean con0 = e0.containsAttr(k);
683
boolean con1 = e1.containsAttr(k);
684
if (con0 != con1) {
685
if (!con0) {
686
r2 = 0 - 1;
687
break;
688
}
689
if (!con1) {
690
r2 = 1 - 0;
691
break;
692
}
693
}
694
if (!con0) {
695
r2 = 0;
696
break;
697
}
698
String k0 = e0.getAttrName(k);
699
String k1 = e1.getAttrName(k);
700
r2 = k0.compareTo(k1);
701
if (r2 != 0) {
702
break;
703
}
704
String v0 = e0.getAttr(k);
705
String v1 = e1.getAttr(k);
706
r2 = v0.compareTo(v1);
707
if (r2 != 0) {
708
break;
709
}
710
}
711
if (r != 0) {
712
r = (r > 0) ? 1 : -1;
713
}
714
if (r2 != 0) {
715
r2 = (r2 > 0) ? 1 : -1;
716
}
717
if (r != r2) {
718
System.out.println("*** wrong attr compare, " + r + " != " + r2);
719
System.out.println(" this = " + this);
720
System.out.println(" attr->" + e0);
721
System.out.println(" that = " + that);
722
System.out.println(" attr->" + e1);
723
}
724
return r == r2;
725
}
726
727
private void badIndex(int i) {
728
Object badRef = (new Object[0])[i];
729
}
730
731
public Object get(int i) {
732
if (i >= size) {
733
badIndex(i);
734
}
735
return parts[i];
736
}
737
738
public Object set(int i, Object e) {
739
if (i >= size) {
740
badIndex(i);
741
}
742
e.getClass(); // null check
743
checkNotFrozen();
744
Object old = parts[i];
745
setRaw(i, e);
746
return old;
747
}
748
749
void setRaw(int i, Object e) {
750
parts[i] = e;
751
}
752
753
public boolean remove(Object e) {
754
int i = indexOf(e);
755
if (i < 0) {
756
return false;
757
}
758
close(i, 1);
759
return true;
760
}
761
762
public Object remove(int i) {
763
if (i >= size) {
764
badIndex(i);
765
}
766
Object e = parts[i];
767
close(i, 1);
768
return e;
769
}
770
771
public Object removeLast() {
772
if (size == 0) {
773
return null;
774
}
775
return remove(size - 1);
776
}
777
778
/** Remove the first element matching the given filter.
779
* Return the filtered value.
780
*/
781
public Object remove(Filter f) {
782
return findOrRemove(f, 0, true);
783
}
784
785
public Object remove(Filter f, int fromIndex) {
786
if (fromIndex < 0) {
787
fromIndex = 0;
788
}
789
return findOrRemove(f, fromIndex, true);
790
}
791
792
/** Remove the last element matching the given filter.
793
* Return the filtered value.
794
*/
795
public Object removeLast(Filter f) {
796
return findOrRemoveLast(f, size - 1, true);
797
}
798
799
public Object removeLast(Filter f, int fromIndex) {
800
if (fromIndex >= size) {
801
fromIndex = size - 1;
802
}
803
return findOrRemoveLast(f, fromIndex, true);
804
}
805
806
/** Remove all elements matching the given filter.
807
* If there is a non-null collection given as a sink,
808
* transfer removed elements to the given collection.
809
* The int result is the number of removed elements.
810
* If there is a null sink given, the removed elements
811
* are discarded. If there is no sink given, the removed
812
* elements are returned in an anonymous container element.
813
*/
814
public Element removeAll(Filter f) {
815
Element result = new Element();
816
findOrRemoveAll(f, false, 0, size, result.asList(), true);
817
return result;
818
}
819
820
public Element removeAll(Filter f, int fromIndex, int toIndex) {
821
Element result = new Element();
822
findOrRemoveAll(f, true, fromIndex, toIndex, result.asList(), true);
823
return result;
824
}
825
826
public int removeAll(Filter f, Collection<Object> sink) {
827
return findOrRemoveAll(f, false, 0, size, sink, true);
828
}
829
830
public int removeAll(Filter f, int fromIndex, int toIndex, Collection<Object> sink) {
831
return findOrRemoveAll(f, false, fromIndex, toIndex, sink, true);
832
}
833
834
/** Remove all elements not matching the given filter.
835
* If there is a non-null collection given as a sink,
836
* transfer removed elements to the given collection.
837
* The int result is the number of removed elements.
838
* If there is a null sink given, the removed elements
839
* are discarded. If there is no sink given, the removed
840
* elements are returned in an anonymous container element.
841
*/
842
public Element retainAll(Filter f) {
843
Element result = new Element();
844
findOrRemoveAll(f, true, 0, size, result.asList(), true);
845
return result;
846
}
847
848
public Element retainAll(Filter f, int fromIndex, int toIndex) {
849
Element result = new Element();
850
findOrRemoveAll(f, true, fromIndex, toIndex, result.asList(), true);
851
return result;
852
}
853
854
public int retainAll(Filter f, Collection<Object> sink) {
855
return findOrRemoveAll(f, true, 0, size, sink, true);
856
}
857
858
public int retainAll(Filter f, int fromIndex, int toIndex, Collection<Object> sink) {
859
return findOrRemoveAll(f, true, fromIndex, toIndex, sink, true);
860
}
861
862
public void add(int i, Object e) {
863
// (The shape of this method is tweaked for common cases.)
864
e.getClass(); // force a null check on e
865
if (hasNulls(1 + NEED_SLOP)) {
866
// Common case: Have some slop space.
867
if (i == size) {
868
// Most common case: Append.
869
setRaw(i, e);
870
size++;
871
return;
872
}
873
if (i > size) {
874
badIndex(i);
875
}
876
// Second most common case: Shift right by one.
877
open(i, 1);
878
setRaw(i, e);
879
return;
880
}
881
// Ran out of space. Do something complicated.
882
size = expand(i, 1);
883
setRaw(i, e);
884
}
885
886
public boolean add(Object e) {
887
add(size, e);
888
return true;
889
}
890
891
public Object getLast() {
892
return size == 0 ? null : parts[size - 1];
893
}
894
895
/** Returns the text of this Element.
896
* All sub-elements of this Element must be of type CharSequence.
897
* A ClassCastException is raised if there are non-character sub-elements.
898
* If there is one sub-element, return it.
899
* Otherwise, returns a TokenList of all sub-elements.
900
* This results in a space being placed between each adjacent pair of sub-elements.
901
*/
902
public CharSequence getText() {
903
checkTextOnly();
904
if (size == 1) {
905
return parts[0].toString();
906
} else {
907
return new TokenList(parts, 0, size);
908
}
909
}
910
911
/** Provides an iterable view of this object as a series of texts.
912
* All sub-elements of this Element must be of type CharSequence.
913
* A ClassCastException is raised if there are non-character sub-elements.
914
*/
915
public Iterable<CharSequence> texts() {
916
checkTextOnly();
917
return (Iterable<CharSequence>) (Iterable) this;
918
}
919
920
/** Returns an array of strings derived from the sub-elements of this object.
921
* All sub-elements of this Element must be of type CharSequence.
922
* A ClassCastException is raised if there are non-character sub-elements.
923
*/
924
public String[] toStrings() {
925
//checkTextOnly();
926
String[] result = new String[size];
927
for (int i = 0; i < size; i++) {
928
result[i] = ((CharSequence) parts[i]).toString();
929
}
930
return result;
931
}
932
933
/** Like getText, except that it disregards non-text elements.
934
* Non-text elements are replaced by their textual contents, if any.
935
* Text elements which were separated only by non-text element
936
* boundaries are merged into single tokens.
937
* <p>
938
* There is no corresponding setter, since this accessor does
939
* not report the full state of the element.
940
*/
941
public CharSequence getFlatText() {
942
if (size == 1) {
943
// Simple cases.
944
if (parts[0] instanceof CharSequence) {
945
return parts[0].toString();
946
} else {
947
return new TokenList();
948
}
949
}
950
if (isText()) {
951
return getText();
952
}
953
// Filter and merge.
954
Element result = new Element(size);
955
boolean merge = false;
956
for (int i = 0; i < size; i++) {
957
Object text = parts[i];
958
if (!(text instanceof CharSequence)) {
959
// Skip, but erase this boundary.
960
if (text instanceof Element) {
961
Element te = (Element) text;
962
if (!te.isEmpty()) {
963
result.addText(te.getFlatText());
964
}
965
}
966
merge = true;
967
continue;
968
}
969
if (merge) {
970
// Merge w/ previous token.
971
result.addText((CharSequence) text);
972
merge = false;
973
} else {
974
result.add(text);
975
}
976
}
977
if (result.size() == 1) {
978
return (CharSequence) result.parts[0];
979
} else {
980
return result.getText();
981
}
982
}
983
984
/** Return true if all sub-elements are of type CharSequence. */
985
public boolean isText() {
986
for (int i = 0; i < size; i++) {
987
if (!(parts[i] instanceof CharSequence)) {
988
return false;
989
}
990
}
991
return true;
992
}
993
994
/** Return true if at least one sub-element is of type CharSequence. */
995
public boolean hasText() {
996
for (int i = 0; i < size; i++) {
997
if (parts[i] instanceof CharSequence) {
998
return true;
999
}
1000
}
1001
return false;
1002
}
1003
1004
/** Raise a ClassCastException if !isText. */
1005
public void checkTextOnly() {
1006
for (int i = 0; i < size; i++) {
1007
((CharSequence) parts[i]).getClass();
1008
}
1009
}
1010
1011
/** Clears out all sub-elements, and replaces them by the given text.
1012
* A ClassCastException is raised if there are non-character sub-elements,
1013
* either before or after the change.
1014
*/
1015
public void setText(CharSequence text) {
1016
checkTextOnly();
1017
clear();
1018
if (text instanceof TokenList) {
1019
// TL's contain only strings
1020
addAll(0, (TokenList) text);
1021
} else {
1022
add(text);
1023
}
1024
}
1025
1026
/** Add text at the given position, merging with any previous
1027
* text element, but preserving token boundaries where possible.
1028
* <p>
1029
* In all cases, the new value of getText() is the string
1030
* concatenation of the old value of getText() plus the new text.
1031
* <p>
1032
* The total effect is to concatenate the given text to any
1033
* pre-existing text, and to do so efficiently even if there
1034
* are many such concatenations. Also, getText calls which
1035
* return multiple tokens (in a TokenList) are respected.
1036
* For example, if x is empty, x.addText(y.getText()) puts
1037
* an exact structural copy of y's text into x.
1038
* <p>
1039
* Internal token boundaries in the original text, and in the new
1040
* text (i.e., if it is a TokenList), are preserved. However,
1041
* at the point where new text joins old text, a StringBuffer
1042
* or new String may be created to join the last old and first
1043
* new token.
1044
* <p>
1045
* If the given text is a TokenList, add the tokens as
1046
* separate sub-elements, possibly merging the first token to
1047
* a previous text item (to avoid making a new token boundary).
1048
* <p>
1049
* If the element preceding position i is a StringBuffer,
1050
* append the first new token to it.
1051
* <p>
1052
* If the preceding element is a CharSequence, replace it by a
1053
* StringBuffer containing both its and the first new token.
1054
* <p>
1055
* If tokens are added after a StringBuffer, freeze it into a String.
1056
* <p>
1057
* Every token not merged into a previous CharSequence is added
1058
* as a new sub-element, starting at position i.
1059
* <p>
1060
* Returns the number of elements added, which is useful
1061
* for further calls to addText. This number is zero
1062
* if the input string was null, or was successfully
1063
* merged into a StringBuffer at position i-1.
1064
* <p>
1065
* By contrast, calling add(text) always adds a new sub-element.
1066
* In that case, if there is a previous string, a separating
1067
* space is virtually present also, and will be observed if
1068
* getText() is used to return all the text together.
1069
*/
1070
public int addText(int i, CharSequence text) {
1071
if (text instanceof String) {
1072
return addText(i, (String) text);
1073
} else if (text instanceof TokenList) {
1074
// Text is a list of tokens.
1075
TokenList tl = (TokenList) text;
1076
int tlsize = tl.size();
1077
if (tlsize == 0) {
1078
return 0;
1079
}
1080
String token0 = tl.get(0).toString();
1081
if (tlsize == 1) {
1082
return addText(i, token0);
1083
}
1084
if (mergeWithPrev(i, token0, false)) {
1085
// Add the n-1 remaining tokens.
1086
addAll(i, tl.subList(1, tlsize));
1087
return tlsize - 1;
1088
} else {
1089
addAll(i, (Collection) tl);
1090
return tlsize;
1091
}
1092
} else {
1093
return addText(i, text.toString());
1094
}
1095
}
1096
1097
public int addText(CharSequence text) {
1098
return addText(size, text);
1099
}
1100
1101
private // no reason to make this helper public
1102
int addText(int i, String text) {
1103
if (text.length() == 0) {
1104
return 0; // Trivial success.
1105
}
1106
if (mergeWithPrev(i, text, true)) {
1107
return 0; // Merged with previous token.
1108
}
1109
// No previous token.
1110
add(i, text);
1111
return 1;
1112
}
1113
1114
// Tries to merge token with previous contents.
1115
// Returns true if token is successfully disposed of.
1116
// If keepSB is false, any previous StringBuffer is frozen.
1117
// If keepSB is true, a StringBuffer may be created to hold
1118
// the merged token.
1119
private boolean mergeWithPrev(int i, String token, boolean keepSB) {
1120
if (i == 0) // Trivial success if the token is length zero.
1121
{
1122
return (token.length() == 0);
1123
}
1124
Object prev = parts[i - 1];
1125
if (prev instanceof StringBuffer) {
1126
StringBuffer psb = (StringBuffer) prev;
1127
psb.append(token);
1128
if (!keepSB) {
1129
parts[i - 1] = psb.toString();
1130
}
1131
return true;
1132
}
1133
if (token.length() == 0) {
1134
return true; // Trivial success.
1135
}
1136
if (prev instanceof CharSequence) {
1137
// Must concatenate.
1138
StringBuffer psb = new StringBuffer(prev.toString());
1139
psb.append(token);
1140
if (keepSB) {
1141
parts[i - 1] = psb;
1142
} else {
1143
parts[i - 1] = psb.toString();
1144
}
1145
return true;
1146
}
1147
return false;
1148
}
1149
1150
/** Trim all strings, using String.trim().
1151
* Remove empty strings.
1152
* Normalize CharSequences to Strings.
1153
*/
1154
public void trimText() {
1155
checkNotFrozen();
1156
int fillp = 0;
1157
int size = this.size;
1158
Object[] parts = this.parts;
1159
for (int i = 0; i < size; i++) {
1160
Object e = parts[i];
1161
if (e instanceof CharSequence) {
1162
String tt = e.toString().trim();
1163
if (tt.length() == 0) {
1164
continue;
1165
}
1166
e = tt;
1167
}
1168
parts[fillp++] = e;
1169
}
1170
while (size > fillp) {
1171
parts[--size] = null;
1172
}
1173
this.size = fillp;
1174
}
1175
1176
/** Add one or more subelements at the given position.
1177
* If the object reference is null, nothing happens.
1178
* If the object is an anonymous Element, addAll is called.
1179
* If the object is a TokenList, addAll is called (to add the tokens).
1180
* Otherwise, add is called, adding a single subelement or string.
1181
* The net effect is to add zero or more tokens.
1182
* The returned value is the number of added elements.
1183
* <p>
1184
* Note that getText() can return a TokenList which preserves
1185
* token boundaries in the text source. Such a text will be
1186
* added as multiple text sub-elements.
1187
* <p>
1188
* If a text string is added adjacent to an immediately
1189
* preceding string, there will be a token boundary between
1190
* the strings, which will print as an extra space.
1191
*/
1192
public int addContent(int i, Object e) {
1193
if (e == null) {
1194
return 0;
1195
} else if (e instanceof TokenList) {
1196
return addAll(i, (Collection) e);
1197
} else if (e instanceof Element
1198
&& ((Element) e).isAnonymous()) {
1199
return addAll(i, (Element) e);
1200
} else {
1201
add(i, e);
1202
return 1;
1203
}
1204
}
1205
1206
public int addContent(Object e) {
1207
return addContent(size, e);
1208
}
1209
1210
public Object[] toArray() {
1211
Object[] result = new Object[size];
1212
System.arraycopy(parts, 0, result, 0, size);
1213
return result;
1214
}
1215
1216
public Element copyContentOnly() {
1217
Element content = new Element(size);
1218
System.arraycopy(parts, 0, content.parts, 0, size);
1219
content.size = size;
1220
return content;
1221
}
1222
1223
public void sort(Comparator<Object> c) {
1224
Arrays.sort(parts, 0, size, c);
1225
}
1226
1227
public void sort() {
1228
sort(CONTENT_ORDER);
1229
}
1230
1231
/** Equivalent to Collections.reverse(this.asList()). */
1232
public void reverse() {
1233
for (int i = 0, mid = size >> 1, j = size - 1; i < mid; i++, j--) {
1234
Object p = parts[i];
1235
parts[i] = parts[j];
1236
parts[j] = p;
1237
}
1238
}
1239
1240
/** Equivalent to Collections.shuffle(this.asList() [, rnd]). */
1241
public void shuffle() {
1242
Collections.shuffle(this.asList());
1243
}
1244
1245
public void shuffle(Random rnd) {
1246
Collections.shuffle(this.asList(), rnd);
1247
}
1248
1249
/** Equivalent to Collections.rotate(this.asList(), dist). */
1250
public void rotate(int dist) {
1251
Collections.rotate(this.asList(), dist);
1252
}
1253
1254
/** Equivalent to Collections.min(this.asList(), c). */
1255
public Object min(Comparator<Object> c) {
1256
return Collections.min(this.asList(), c);
1257
}
1258
1259
public Object min() {
1260
return min(CONTENT_ORDER);
1261
}
1262
1263
/** Equivalent to Collections.max(this.asList(), c). */
1264
public Object max(Comparator<Object> c) {
1265
return Collections.max(this.asList(), c);
1266
}
1267
1268
public Object max() {
1269
return max(CONTENT_ORDER);
1270
}
1271
1272
public int addAll(int i, Collection c) {
1273
if (c instanceof LView) {
1274
return addAll(i, ((LView) c).asElement());
1275
} else {
1276
int csize = c.size();
1277
if (csize == 0) {
1278
return 0;
1279
}
1280
openOrExpand(i, csize);
1281
int fill = i;
1282
for (Object part : c) {
1283
parts[fill++] = part;
1284
}
1285
return csize;
1286
}
1287
}
1288
1289
public int addAll(int i, Element e) {
1290
int esize = e.size;
1291
if (esize == 0) {
1292
return 0;
1293
}
1294
openOrExpand(i, esize);
1295
System.arraycopy(e.parts, 0, parts, i, esize);
1296
return esize;
1297
}
1298
1299
public int addAll(Collection c) {
1300
return addAll(size, c);
1301
}
1302
1303
public int addAll(Element e) {
1304
return addAll(size, e);
1305
}
1306
1307
public int addAllAttrsFrom(Element e) {
1308
int added = 0;
1309
for (int k = 0; e.containsAttr(k); k++) {
1310
String old = setAttr(e.getAttrName(k), e.getAttr(k));
1311
if (old == null) {
1312
added += 1;
1313
}
1314
}
1315
// Return number of added (not merely changed) attrs.
1316
return added;
1317
}
1318
1319
// Search.
1320
public boolean matches(Filter f) {
1321
return f.filter(this) != null;
1322
}
1323
1324
public Object find(Filter f) {
1325
return findOrRemove(f, 0, false);
1326
}
1327
1328
public Object find(Filter f, int fromIndex) {
1329
if (fromIndex < 0) {
1330
fromIndex = 0;
1331
}
1332
return findOrRemove(f, fromIndex, false);
1333
}
1334
1335
/** Find the last element matching the given filter.
1336
* Return the filtered value.
1337
*/
1338
public Object findLast(Filter f) {
1339
return findOrRemoveLast(f, size - 1, false);
1340
}
1341
1342
public Object findLast(Filter f, int fromIndex) {
1343
if (fromIndex >= size) {
1344
fromIndex = size - 1;
1345
}
1346
return findOrRemoveLast(f, fromIndex, false);
1347
}
1348
1349
/** Find all elements matching the given filter.
1350
* If there is a non-null collection given as a sink,
1351
* transfer matching elements to the given collection.
1352
* The int result is the number of matching elements.
1353
* If there is a null sink given, the matching elements are
1354
* not collected. If there is no sink given, the matching
1355
* elements are returned in an anonymous container element.
1356
* In no case is the receiver element changed.
1357
* <p>
1358
* Note that a simple count of matching elements can be
1359
* obtained by passing a null collection argument.
1360
*/
1361
public Element findAll(Filter f) {
1362
Element result = new Element();
1363
findOrRemoveAll(f, false, 0, size, result.asList(), false);
1364
return result;
1365
}
1366
1367
public Element findAll(Filter f, int fromIndex, int toIndex) {
1368
Element result = new Element(name);
1369
findOrRemoveAll(f, false, fromIndex, toIndex, result.asList(), false);
1370
return result;
1371
}
1372
1373
public int findAll(Filter f, Collection<Object> sink) {
1374
return findOrRemoveAll(f, false, 0, size, sink, false);
1375
}
1376
1377
public int findAll(Filter f, int fromIndex, int toIndex, Collection<Object> sink) {
1378
return findOrRemoveAll(f, false, fromIndex, toIndex, sink, false);
1379
}
1380
1381
/// Driver routines.
1382
private Object findOrRemove(Filter f, int fromIndex, boolean remove) {
1383
for (int i = fromIndex; i < size; i++) {
1384
Object x = f.filter(parts[i]);
1385
if (x != null) {
1386
if (remove) {
1387
close(i, 1);
1388
}
1389
return x;
1390
}
1391
}
1392
return null;
1393
}
1394
1395
private Object findOrRemoveLast(Filter f, int fromIndex, boolean remove) {
1396
for (int i = fromIndex; i >= 0; i--) {
1397
Object x = f.filter(parts[i]);
1398
if (x != null) {
1399
if (remove) {
1400
close(i, 1);
1401
}
1402
return x;
1403
}
1404
}
1405
return null;
1406
}
1407
1408
private int findOrRemoveAll(Filter f, boolean fInvert,
1409
int fromIndex, int toIndex,
1410
Collection<Object> sink, boolean remove) {
1411
if (fromIndex < 0) {
1412
badIndex(fromIndex);
1413
}
1414
if (toIndex > size) {
1415
badIndex(toIndex);
1416
}
1417
int found = 0;
1418
for (int i = fromIndex; i < toIndex; i++) {
1419
Object p = parts[i];
1420
Object x = f.filter(p);
1421
if (fInvert ? (x == null) : (x != null)) {
1422
if (remove) {
1423
close(i--, 1);
1424
toIndex--;
1425
}
1426
found += XMLKit.addContent(fInvert ? p : x, sink);
1427
}
1428
}
1429
return found;
1430
}
1431
1432
public void replaceAll(Filter f) {
1433
XMLKit.replaceAll(f, this.asList());
1434
}
1435
1436
public void replaceAll(Filter f, int fromIndex, int toIndex) {
1437
XMLKit.replaceAll(f, this.asList().subList(fromIndex, toIndex));
1438
}
1439
1440
/// Recursive walks.
1441
// findAllInTree(f) == findAll(findInTree(f,S)), S.toElement
1442
// findAllInTree(f,S) == findAll(findInTree(content(f,S)))
1443
// removeAllInTree(f) == replaceAll(replaceInTree(and(f,emptyF)))
1444
// removeAllInTree(f,S) == replaceAll(replaceInTree(and(content(f,S),emptyF)))
1445
// retainAllInTree(f) == removeAllInTree(not(f))
1446
// replaceAllInTree(f) == replaceAll(replaceInTree(f))
1447
public Element findAllInTree(Filter f) {
1448
Element result = new Element();
1449
findAllInTree(f, result.asList());
1450
return result;
1451
}
1452
1453
public int findAllInTree(Filter f, Collection<Object> sink) {
1454
int found = 0;
1455
int size = this.size; // cache copy
1456
for (int i = 0; i < size; i++) {
1457
Object p = parts[i];
1458
Object x = f.filter(p);
1459
if (x != null) {
1460
found += XMLKit.addContent(x, sink);
1461
} else if (p instanceof Element) {
1462
found += ((Element) p).findAllInTree(f, sink);
1463
}
1464
}
1465
return found;
1466
}
1467
1468
public int countAllInTree(Filter f) {
1469
return findAllInTree(f, null);
1470
}
1471
1472
public int removeAllInTree(Filter f, Collection<Object> sink) {
1473
if (sink == null) {
1474
sink = newCounterColl();
1475
}
1476
replaceAll(replaceInTree(and(content(f, sink), emptyFilter())));
1477
return sink.size();
1478
}
1479
1480
public Element removeAllInTree(Filter f) {
1481
Element result = new Element();
1482
removeAllInTree(f, result.asList());
1483
return result;
1484
}
1485
1486
public int retainAllInTree(Filter f, Collection<Object> sink) {
1487
return removeAllInTree(not(f), sink);
1488
}
1489
1490
public Element retainAllInTree(Filter f) {
1491
Element result = new Element();
1492
retainAllInTree(f, result.asList());
1493
return result;
1494
}
1495
1496
public void replaceAllInTree(Filter f) {
1497
replaceAll(replaceInTree(f));
1498
}
1499
1500
/** Raise a ClassCastException if any subelements are the wrong type. */
1501
public void checkPartsOnly(Class<?> elementClass) {
1502
for (int i = 0; i < size; i++) {
1503
elementClass.cast(parts[i]).getClass();
1504
}
1505
}
1506
1507
/** Return true if all sub-elements are of the given type. */
1508
public boolean isPartsOnly(Class<?> elementClass) {
1509
for (int i = 0; i < size; i++) {
1510
if (!elementClass.isInstance(parts[i])) {
1511
return false;
1512
}
1513
}
1514
return true;
1515
}
1516
1517
/** Provides an iterable view of this object as a series of elements.
1518
* All sub-elements of this Element must be of type Element.
1519
* A ClassCastException is raised if there are non-Element sub-elements.
1520
*/
1521
public <T> Iterable<T> partsOnly(Class<T> elementClass) {
1522
checkPartsOnly(elementClass);
1523
return (Iterable<T>) (Iterable) this;
1524
}
1525
1526
public Iterable<Element> elements() {
1527
return partsOnly(Element.class);
1528
}
1529
1530
/// Useful shorthands.
1531
// Finding or removing elements w/o regard to their type or content.
1532
public Element findElement() {
1533
return (Element) find(elementFilter());
1534
}
1535
1536
public Element findAllElements() {
1537
return findAll(elementFilter());
1538
}
1539
1540
public Element removeElement() {
1541
return (Element) remove(elementFilter());
1542
}
1543
1544
public Element removeAllElements() {
1545
return (Element) removeAll(elementFilter());
1546
}
1547
1548
// Finding or removing by element tag or selected attribute,
1549
// as if by elementFilter(name) or attrFilter(name, value).
1550
// Roughly akin to Common Lisp ASSOC.
1551
public Element findElement(String name) {
1552
return (Element) find(elementFilter(name));
1553
}
1554
1555
public Element removeElement(String name) {
1556
return (Element) remove(elementFilter(name));
1557
}
1558
1559
public Element findWithAttr(String key) {
1560
return (Element) find(attrFilter(name));
1561
}
1562
1563
public Element findWithAttr(String key, String value) {
1564
return (Element) find(attrFilter(name, value));
1565
}
1566
1567
public Element removeWithAttr(String key) {
1568
return (Element) remove(attrFilter(name));
1569
}
1570
1571
public Element removeWithAttr(String key, String value) {
1572
return (Element) remove(attrFilter(name, value));
1573
}
1574
1575
public Element findAllElements(String name) {
1576
return findAll(elementFilter(name));
1577
}
1578
1579
public Element removeAllElements(String name) {
1580
return removeAll(elementFilter(name));
1581
}
1582
1583
public Element retainAllElements(String name) {
1584
return retainAll(elementFilter(name));
1585
}
1586
1587
public Element findAllWithAttr(String key) {
1588
return findAll(attrFilter(key));
1589
}
1590
1591
public Element removeAllWithAttr(String key) {
1592
return removeAll(attrFilter(key));
1593
}
1594
1595
public Element retainAllWithAttr(String key) {
1596
return retainAll(attrFilter(key));
1597
}
1598
1599
public Element findAllWithAttr(String key, String value) {
1600
return findAll(attrFilter(key, value));
1601
}
1602
1603
public Element removeAllWithAttr(String key, String value) {
1604
return removeAll(attrFilter(key, value));
1605
}
1606
1607
public Element retainAllWithAttr(String key, String value) {
1608
return retainAll(attrFilter(key, value));
1609
}
1610
1611
public int countAll(Filter f) {
1612
return findAll(f, null);
1613
}
1614
1615
public int countAllElements() {
1616
return countAll(elementFilter());
1617
}
1618
1619
public int countAllElements(String name) {
1620
return countAll(elementFilter(name));
1621
}
1622
1623
public int countAllWithAttr(String key) {
1624
return countAll(attrFilter(name));
1625
}
1626
1627
public int countAllWithAttr(String key, String value) {
1628
return countAll(attrFilter(key, value));
1629
}
1630
1631
public int indexOf(Object e) {
1632
for (int i = 0; i < size; i++) {
1633
if (e.equals(parts[i])) {
1634
return i;
1635
}
1636
}
1637
return -1;
1638
}
1639
1640
public int lastIndexOf(Object e) {
1641
for (int i = size - 1; i >= 0; i--) {
1642
if (e.equals(parts[i])) {
1643
return i;
1644
}
1645
}
1646
return -1;
1647
}
1648
1649
/** Remove the first element matching the given filter.
1650
* Return the filtered value.
1651
*/
1652
public int indexOf(Filter f) {
1653
return indexOf(f, 0);
1654
}
1655
1656
public int indexOf(Filter f, int fromIndex) {
1657
if (fromIndex < 0) {
1658
fromIndex = 0;
1659
}
1660
for (int i = fromIndex; i < size; i++) {
1661
Object x = f.filter(parts[i]);
1662
if (x != null) {
1663
return i;
1664
}
1665
}
1666
return -1;
1667
}
1668
1669
/** Remove the last element matching the given filter.
1670
* Return the filtered value.
1671
*/
1672
public int lastIndexOf(Filter f) {
1673
return lastIndexOf(f, size - 1);
1674
}
1675
1676
public int lastIndexOf(Filter f, int fromIndex) {
1677
if (fromIndex >= size) {
1678
fromIndex = size - 1;
1679
}
1680
for (int i = fromIndex; i >= 0; i--) {
1681
Object x = f.filter(parts[i]);
1682
if (x != null) {
1683
return i;
1684
}
1685
}
1686
return -1;
1687
}
1688
1689
public boolean contains(Object e) {
1690
return indexOf(e) >= 0;
1691
}
1692
1693
// attributes
1694
private int findOrCreateAttr(String key, boolean create) {
1695
key.toString(); // null check
1696
int attrBase = parts.length;
1697
for (int i = parts.length - 2; i >= size; i -= 2) {
1698
String akey = (String) parts[i + 0];
1699
if (akey == null) {
1700
if (!create) {
1701
return -1;
1702
}
1703
if (i == size) {
1704
break; // NEED_SLOP
1705
}
1706
parts[i + 0] = key;
1707
//parts[i+1] = ""; //caller responsibility
1708
return i;
1709
}
1710
attrBase = i;
1711
if (akey.equals(key)) {
1712
return i;
1713
}
1714
}
1715
// If we fell through, we ran into an element part.
1716
// Therefore we have run out of empty slots.
1717
if (!create) {
1718
return -1;
1719
}
1720
assert (!isFrozen());
1721
int alen = parts.length - attrBase;
1722
expand(size, 2); // generally expands by more than 2
1723
// since there was a reallocation, the garbage slots are really null
1724
assert (parts[size + 0] == null && parts[size + 1] == null);
1725
alen += 2;
1726
int i = parts.length - alen;
1727
parts[i + 0] = key;
1728
//parts[i+1] = ""; //caller responsibility
1729
return i;
1730
}
1731
1732
public int attrSize() {
1733
return attrLength() >>> 1;
1734
}
1735
1736
public int indexOfAttr(String key) {
1737
return findOrCreateAttr(key, false);
1738
}
1739
1740
public boolean containsAttr(String key) {
1741
return indexOfAttr(key) >= 0;
1742
}
1743
1744
public String getAttr(String key) {
1745
return getAttr(key, null);
1746
}
1747
1748
public String getAttr(String key, String dflt) {
1749
int i = findOrCreateAttr(key, false);
1750
return (i < 0) ? dflt : (String) parts[i + 1];
1751
}
1752
1753
public TokenList getAttrList(String key) {
1754
return convertToList(getAttr(key));
1755
}
1756
1757
public Number getAttrNumber(String key) {
1758
return convertToNumber(getAttr(key));
1759
}
1760
1761
public long getAttrLong(String key) {
1762
return getAttrLong(key, 0);
1763
}
1764
1765
public double getAttrDouble(String key) {
1766
return getAttrDouble(key, 0.0);
1767
}
1768
1769
public long getAttrLong(String key, long dflt) {
1770
return convertToLong(getAttr(key), dflt);
1771
}
1772
1773
public double getAttrDouble(String key, double dflt) {
1774
return convertToDouble(getAttr(key), dflt);
1775
}
1776
1777
int indexAttr(int k) {
1778
int i = parts.length - (k * 2) - 2;
1779
if (i < size || parts[i] == null) {
1780
return -2; // always oob
1781
}
1782
return i;
1783
}
1784
1785
public boolean containsAttr(int k) {
1786
return indexAttr(k) >= 0;
1787
}
1788
1789
public String getAttr(int k) {
1790
return (String) parts[indexAttr(k) + 1];
1791
}
1792
1793
public String getAttrName(int k) {
1794
return (String) parts[indexAttr(k) + 0];
1795
}
1796
1797
public Iterable<String> attrNames() {
1798
//return asAttrMap().keySet();
1799
return new Iterable<String>() {
1800
1801
public Iterator<String> iterator() {
1802
return new ANItr();
1803
}
1804
};
1805
}
1806
1807
// Hand-inlined replacement for asAttrMap().keySet().iterator():
1808
class ANItr implements Iterator<String> {
1809
1810
boolean lastRet;
1811
int cursor = -2; // pointer from end of parts
1812
1813
public boolean hasNext() {
1814
int i = cursor + parts.length;
1815
return i >= size && parts[i] == null;
1816
}
1817
1818
public String next() {
1819
int i = cursor + parts.length;
1820
Object x;
1821
if (i < size || (x = parts[i]) == null) {
1822
nsee();
1823
return null;
1824
}
1825
cursor -= 2;
1826
lastRet = true;
1827
return (String) x;
1828
}
1829
1830
public void remove() {
1831
if (!lastRet) {
1832
throw new IllegalStateException();
1833
}
1834
Element.this.removeAttr((-4 - cursor) / 2);
1835
cursor += 2;
1836
lastRet = false;
1837
}
1838
1839
Exception nsee() {
1840
throw new NoSuchElementException("attribute " + (-2 - cursor) / 2);
1841
}
1842
}
1843
1844
/** Return an anonymous copy of self, but only with attributes.
1845
*/
1846
public Element copyAttrsOnly() {
1847
int alen = attrLength();
1848
Element attrs = new Element(alen);
1849
Object[] attrParts = attrs.parts;
1850
assert (attrParts.length == NEED_SLOP + alen);
1851
System.arraycopy(parts, parts.length - alen,
1852
attrParts, NEED_SLOP,
1853
alen);
1854
return attrs;
1855
}
1856
1857
/** Get all attributes, represented as an element with sub-elements.
1858
* The name of each sub-element is the attribute key, and the text
1859
* This is a fresh copy, and can be updated with affecting the original.
1860
* of each sub-element is the corresponding attribute value.
1861
* See also asAttrMap() for a "live" view of all the attributes as a Map.
1862
*/
1863
public Element getAttrs() {
1864
int asize = attrSize();
1865
Element attrs = new Element(ANON_NAME, asize, NEED_SLOP + asize);
1866
for (int i = 0; i < asize; i++) {
1867
Element attr = new Element(getAttrName(i), 1, NEED_SLOP + 1);
1868
// %%% normalize attrs to token lists?
1869
attr.setRaw(0, getAttr(i));
1870
attrs.setRaw(i, attr);
1871
}
1872
return attrs;
1873
}
1874
1875
public void setAttrs(Element attrs) {
1876
int alen = attrLength();
1877
clearParts(parts.length - alen, alen);
1878
if (!hasNulls(NEED_SLOP + attrs.size * 2)) {
1879
expand(size, attrs.size * 2);
1880
}
1881
addAttrs(attrs);
1882
}
1883
1884
public void addAttrs(Element attrs) {
1885
for (int i = 0; i < attrs.size; i++) {
1886
Element attr = (Element) attrs.get(i);
1887
setAttr(attr.name, attr.getText().toString());
1888
}
1889
}
1890
1891
public void removeAttr(int i) {
1892
checkNotFrozen();
1893
while ((i -= 2) >= size) {
1894
Object k = parts[i + 0];
1895
Object v = parts[i + 1];
1896
if (k == null) {
1897
break;
1898
}
1899
parts[i + 2] = k;
1900
parts[i + 3] = v;
1901
}
1902
parts[i + 2] = null;
1903
parts[i + 3] = null;
1904
}
1905
1906
public void clearAttrs() {
1907
if (parts.length == 0 || parts[parts.length - 1] == null) {
1908
return; // no attrs to clear
1909
}
1910
checkNotFrozen();
1911
if (size == 0) {
1912
// If no elements, free the parts array.
1913
parts = noPartsNotFrozen;
1914
return;
1915
}
1916
for (int i = parts.length - 1; parts[i] != null; i--) {
1917
assert (i >= size);
1918
parts[i] = null;
1919
}
1920
}
1921
1922
public String setAttr(String key, String value) {
1923
String old;
1924
if (value == null) {
1925
int i = findOrCreateAttr(key, false);
1926
if (i >= 0) {
1927
old = (String) parts[i + 1];
1928
removeAttr(i);
1929
} else {
1930
old = null;
1931
}
1932
} else {
1933
checkNotFrozen();
1934
int i = findOrCreateAttr(key, true);
1935
old = (String) parts[i + 1];
1936
parts[i + 1] = value;
1937
}
1938
return old;
1939
}
1940
1941
public String setAttrList(String key, List<String> l) {
1942
if (l == null) {
1943
return setAttr(key, null);
1944
}
1945
if (!(l instanceof TokenList)) {
1946
l = new TokenList(l);
1947
}
1948
return setAttr(key, l.toString());
1949
}
1950
1951
public String setAttrNumber(String key, Number n) {
1952
return setAttr(key, (n == null) ? null : n.toString());
1953
}
1954
1955
public String setAttrLong(String key, long n) {
1956
return setAttr(key, (n == 0) ? null : String.valueOf(n));
1957
}
1958
1959
public String setAttrDouble(String key, double n) {
1960
return setAttr(key, (n == 0) ? null : String.valueOf(n));
1961
}
1962
1963
public String setAttr(int k, String value) {
1964
int i = indexAttr(k);
1965
String old = (String) parts[i + 1];
1966
if (value == null) {
1967
removeAttr(i);
1968
} else {
1969
checkNotFrozen();
1970
parts[i + 1] = value;
1971
}
1972
return old;
1973
}
1974
1975
int attrLength() {
1976
return parts.length - attrBase();
1977
}
1978
1979
/** Are the attributes of the two two elements equal?
1980
* Disregards name, sub-elements, and ordering of attributes.
1981
*/
1982
public boolean equalAttrs(Element that) {
1983
int alen = this.attrLength();
1984
if (alen != that.attrLength()) {
1985
return false;
1986
}
1987
if (alen == 0) {
1988
return true;
1989
}
1990
return compareAttrs(alen, that, alen, false) == 0;
1991
}
1992
1993
private int compareAttrs(int thisAlen,
1994
Element that, int thatAlen,
1995
boolean fullCompare) {
1996
Object[] thisParts = this.parts;
1997
Object[] thatParts = that.parts;
1998
int thisBase = thisParts.length - thisAlen;
1999
int thatBase = thatParts.length - thatAlen;
2000
// search indexes into unmatched parts of this.attrs:
2001
int firstI = 0;
2002
// search indexes into unmatched parts of that.attrs:
2003
int firstJ = 0;
2004
int lastJ = thatAlen - 2;
2005
// try to find the mismatch with the first key:
2006
String firstKey = null;
2007
int firstKeyValCmp = 0;
2008
int foundKeys = 0;
2009
for (int i = 0; i < thisAlen; i += 2) {
2010
String key = (String) thisParts[thisBase + i + 0];
2011
String val = (String) thisParts[thisBase + i + 1];
2012
String otherVal = null;
2013
for (int j = firstJ; j <= lastJ; j += 2) {
2014
if (key.equals(thatParts[thatBase + j + 0])) {
2015
foundKeys += 1;
2016
otherVal = (String) thatParts[thatBase + j + 1];
2017
// Optimization: Narrow subsequent searches when easy.
2018
if (j == lastJ) {
2019
lastJ -= 2;
2020
} else if (j == firstJ) {
2021
firstJ += 2;
2022
}
2023
if (i == firstI) {
2024
firstI += 2;
2025
}
2026
break;
2027
}
2028
}
2029
int valCmp;
2030
if (otherVal != null) {
2031
// The key was found.
2032
if (!fullCompare) {
2033
if (!val.equals(otherVal)) {
2034
return 1 - 0; //arb.
2035
}
2036
continue;
2037
}
2038
valCmp = val.compareTo(otherVal);
2039
} else {
2040
// Found the key in this but not that.
2041
// Such a mismatch puts the guy missing the key last.
2042
valCmp = 0 - 1;
2043
}
2044
if (valCmp != 0) {
2045
// found a mismatch, key present in both elems
2046
if (firstKey == null
2047
|| firstKey.compareTo(key) > 0) {
2048
// found a better key
2049
firstKey = key;
2050
firstKeyValCmp = valCmp;
2051
}
2052
}
2053
}
2054
// We have located the first mismatch of all keys in this.attrs.
2055
// In general we must also look for keys in that.attrs but missing
2056
// from this.attrs; such missing keys, if earlier than firstKey,
2057
// rule the comparison.
2058
2059
// We can sometimes prove quickly there is no missing key.
2060
if (foundKeys == thatAlen / 2) {
2061
// Exhausted all keys in that.attrs.
2062
return firstKeyValCmp;
2063
}
2064
2065
// Search for a missing key in that.attrs earlier than firstKey.
2066
findMissingKey:
2067
for (int j = firstJ; j <= lastJ; j += 2) {
2068
String otherKey = (String) thatParts[thatBase + j + 0];
2069
if (firstKey == null
2070
|| firstKey.compareTo(otherKey) > 0) {
2071
// Found a better key; is it missing?
2072
for (int i = firstI; i < thisAlen; i += 2) {
2073
if (otherKey.equals(thisParts[thisBase + i + 0])) {
2074
continue findMissingKey;
2075
}
2076
}
2077
// If we get here, there was no match in this.attrs.
2078
return 1 - 0;
2079
}
2080
}
2081
2082
// No missing key. Previous comparison value rules.
2083
return firstKeyValCmp;
2084
}
2085
2086
// Binary search looking for first non-null after size.
2087
int attrBase() {
2088
// Smallest & largest possible attribute indexes:
2089
int kmin = 0;
2090
int kmax = (parts.length - size) >>> 1;
2091
// earlist possible attribute position:
2092
int abase = parts.length - (kmax * 2);
2093
// binary search using scaled indexes:
2094
while (kmin != kmax) {
2095
int kmid = kmin + ((kmax - kmin) >>> 1);
2096
if (parts[abase + (kmid * 2)] == null) {
2097
kmin = kmid + 1;
2098
} else {
2099
kmax = kmid;
2100
}
2101
assert (kmin <= kmax);
2102
}
2103
return abase + (kmax * 2);
2104
}
2105
2106
/** Sort attributes by name. */
2107
public void sortAttrs() {
2108
checkNotFrozen();
2109
int abase = attrBase();
2110
int alen = parts.length - abase;
2111
String[] buf = new String[alen];
2112
// collect keys
2113
for (int k = 0; k < alen / 2; k++) {
2114
String akey = (String) parts[abase + (k * 2) + 0];
2115
buf[k] = akey;
2116
}
2117
Arrays.sort(buf, 0, alen / 2);
2118
// collect values
2119
for (int k = 0; k < alen / 2; k++) {
2120
String akey = buf[k];
2121
buf[k + alen / 2] = getAttr(akey);
2122
}
2123
// reorder keys and values
2124
int fillp = parts.length;
2125
for (int k = 0; k < alen / 2; k++) {
2126
String akey = buf[k];
2127
String aval = buf[k + alen / 2];
2128
fillp -= 2;
2129
parts[fillp + 0] = akey;
2130
parts[fillp + 1] = aval;
2131
}
2132
assert (fillp == abase);
2133
}
2134
2135
/*
2136
Notes on whitespace and tokenization.
2137
On input, never split CDATA blocks. They remain single tokens.
2138
?Try to treat encoded characters as CDATA-quoted, also?
2139
2140
Internally, each String sub-element is logically a token.
2141
However, if there was no token-splitting on input,
2142
consecutive strings are merged by the parser.
2143
2144
Internally, we need addToken (intervening blank) and addText
2145
(hard concatenation).
2146
2147
Optionally on input, tokenize unquoted text into words.
2148
Between each adjacent word pair, elide either one space
2149
or all space.
2150
2151
On output, we always add spaces between tokens.
2152
The Element("a", {"b", "c", Element("d"), "e f"})
2153
outputs as "<a>b c<d/>e f</a>"
2154
*/
2155
/** Split strings into tokens, using a StringTokenizer. */
2156
public void tokenize(String delims, boolean returnDelims) {
2157
checkNotFrozen();
2158
if (delims == null) {
2159
delims = WHITESPACE_CHARS; // StringTokenizer default
2160
}
2161
for (int i = 0; i < size; i++) {
2162
if (!(parts[i] instanceof CharSequence)) {
2163
continue;
2164
}
2165
int osize = size;
2166
String str = parts[i].toString();
2167
StringTokenizer st = new StringTokenizer(str, delims, returnDelims);
2168
int nstrs = st.countTokens();
2169
switch (nstrs) {
2170
case 0:
2171
close(i--, 1);
2172
break;
2173
case 1:
2174
parts[i] = st.nextToken();
2175
break;
2176
default:
2177
openOrExpand(i + 1, nstrs - 1);
2178
for (int j = 0; j < nstrs; j++) {
2179
parts[i + j] = st.nextToken();
2180
}
2181
i += nstrs - 1;
2182
break;
2183
}
2184
}
2185
}
2186
2187
public void tokenize(String delims) {
2188
tokenize(delims, false);
2189
}
2190
2191
public void tokenize() {
2192
tokenize(null, false);
2193
}
2194
2195
// views
2196
class LView extends AbstractList<Object> {
2197
2198
Element asElement() {
2199
return Element.this;
2200
}
2201
2202
public int size() {
2203
return Element.this.size();
2204
}
2205
2206
public Object get(int i) {
2207
return Element.this.get(i);
2208
}
2209
2210
@Override
2211
public boolean contains(Object e) {
2212
return Element.this.contains(e);
2213
}
2214
2215
@Override
2216
public Object[] toArray() {
2217
return Element.this.toArray();
2218
}
2219
2220
@Override
2221
public int indexOf(Object e) {
2222
return Element.this.indexOf(e);
2223
}
2224
2225
@Override
2226
public int lastIndexOf(Object e) {
2227
return Element.this.lastIndexOf(e);
2228
}
2229
2230
@Override
2231
public void add(int i, Object e) {
2232
++modCount;
2233
Element.this.add(i, e);
2234
}
2235
2236
@Override
2237
public boolean addAll(int i, Collection<? extends Object> c) {
2238
++modCount;
2239
return Element.this.addAll(i, c) > 0;
2240
}
2241
2242
@Override
2243
public boolean addAll(Collection<? extends Object> c) {
2244
++modCount;
2245
return Element.this.addAll(c) > 0;
2246
}
2247
2248
@Override
2249
public Object remove(int i) {
2250
++modCount;
2251
return Element.this.remove(i);
2252
}
2253
2254
@Override
2255
public Object set(int i, Object e) {
2256
++modCount;
2257
return Element.this.set(i, e);
2258
}
2259
2260
@Override
2261
public void clear() {
2262
++modCount;
2263
Element.this.clear();
2264
}
2265
// Others: toArray(Object[]), containsAll, removeAll, retainAll
2266
}
2267
2268
/** Produce a list view of sub-elements.
2269
* (The list view does not provide access to the element's
2270
* name or attributes.)
2271
* Changes to this view are immediately reflected in the
2272
* element itself.
2273
*/
2274
public List<Object> asList() {
2275
return new LView();
2276
}
2277
2278
/** Produce a list iterator on all sub-elements. */
2279
public ListIterator<Object> iterator() {
2280
//return asList().listIterator();
2281
return new Itr();
2282
}
2283
2284
// Hand-inlined replacement for LView.listIterator():
2285
class Itr implements ListIterator<Object> {
2286
2287
int lastRet = -1;
2288
int cursor = 0;
2289
2290
public boolean hasNext() {
2291
return cursor < size;
2292
}
2293
2294
public boolean hasPrevious() {
2295
return cursor > 0 && cursor <= size;
2296
}
2297
2298
public Object next() {
2299
if (!hasNext()) {
2300
nsee();
2301
}
2302
return parts[lastRet = cursor++];
2303
}
2304
2305
public Object previous() {
2306
if (!hasPrevious()) {
2307
nsee();
2308
}
2309
return parts[--cursor];
2310
}
2311
2312
public int nextIndex() {
2313
return cursor;
2314
}
2315
2316
public int previousIndex() {
2317
return cursor - 1;
2318
}
2319
2320
public void set(Object x) {
2321
parts[lastRet] = x;
2322
}
2323
2324
public void add(Object x) {
2325
lastRet = -1;
2326
Element.this.add(cursor++, x);
2327
}
2328
2329
public void remove() {
2330
if (lastRet < 0) {
2331
throw new IllegalStateException();
2332
}
2333
Element.this.remove(lastRet);
2334
if (lastRet < cursor) {
2335
--cursor;
2336
}
2337
lastRet = -1;
2338
}
2339
2340
void nsee() {
2341
throw new NoSuchElementException("element " + cursor);
2342
}
2343
}
2344
2345
/** A PrintWriter which always appends as if by addText.
2346
* Use of this stream may insert a StringBuffer at the end
2347
* of the Element. The user must not directly modify this
2348
* StringBuffer, or use it in other data structures.
2349
* From time to time, the StringBuffer may be replaced by a
2350
* constant string as a result of using the PrintWriter.
2351
*/
2352
public PrintWriter asWriter() {
2353
return new ElemW();
2354
}
2355
2356
class ElemW extends PrintWriter {
2357
2358
ElemW() {
2359
super(new StringWriter());
2360
}
2361
final StringBuffer buf = ((StringWriter) out).getBuffer();
2362
2363
{
2364
lock = buf;
2365
} // synchronize on this buffer
2366
2367
@Override
2368
public void println() {
2369
synchronized (buf) {
2370
ensureCursor();
2371
super.println();
2372
}
2373
}
2374
2375
@Override
2376
public void write(int ch) {
2377
synchronized (buf) {
2378
ensureCursor();
2379
//buf.append(ch);
2380
super.write(ch);
2381
}
2382
}
2383
2384
@Override
2385
public void write(char buf[], int off, int len) {
2386
synchronized (buf) {
2387
ensureCursor();
2388
super.write(buf, off, len);
2389
}
2390
}
2391
2392
@Override
2393
public void write(String s, int off, int len) {
2394
synchronized (buf) {
2395
ensureCursor();
2396
//buf.append(s.substring(off, off+len));
2397
super.write(s, off, len);
2398
}
2399
}
2400
2401
@Override
2402
public void write(String s) {
2403
synchronized (buf) {
2404
ensureCursor();
2405
//buf.append(s);
2406
super.write(s);
2407
}
2408
}
2409
2410
private void ensureCursor() {
2411
checkNotFrozen();
2412
if (getLast() != buf) {
2413
int pos = indexOf(buf);
2414
if (pos >= 0) {
2415
// Freeze the pre-existing use of buf.
2416
setRaw(pos, buf.toString());
2417
}
2418
add(buf);
2419
}
2420
}
2421
}
2422
2423
/** Produce a map view of attributes, in which the attribute
2424
* name strings are the keys.
2425
* (The map view does not provide access to the element's
2426
* name or sub-elements.)
2427
* Changes to this view are immediately reflected in the
2428
* element itself.
2429
*/
2430
public Map<String, String> asAttrMap() {
2431
class Entry implements Map.Entry<String, String> {
2432
2433
final int k;
2434
2435
Entry(int k) {
2436
this.k = k;
2437
assert (((String) getKey()).toString() != null); // check, fail-fast
2438
}
2439
2440
public String getKey() {
2441
return Element.this.getAttrName(k);
2442
}
2443
2444
public String getValue() {
2445
return Element.this.getAttr(k);
2446
}
2447
2448
public String setValue(String v) {
2449
return Element.this.setAttr(k, v.toString());
2450
}
2451
2452
@Override
2453
public boolean equals(Object o) {
2454
if (!(o instanceof Map.Entry)) {
2455
return false;
2456
}
2457
Map.Entry that = (Map.Entry) o;
2458
return (this.getKey().equals(that.getKey())
2459
&& this.getValue().equals(that.getValue()));
2460
}
2461
2462
@Override
2463
public int hashCode() {
2464
return getKey().hashCode() ^ getValue().hashCode();
2465
}
2466
}
2467
class EIter implements Iterator<Map.Entry<String, String>> {
2468
2469
int k = 0; // index of pending next() attribute
2470
2471
public boolean hasNext() {
2472
return Element.this.containsAttr(k);
2473
}
2474
2475
public Map.Entry<String, String> next() {
2476
return new Entry(k++);
2477
}
2478
2479
public void remove() {
2480
Element.this.removeAttr(--k);
2481
}
2482
}
2483
class ESet extends AbstractSet<Map.Entry<String, String>> {
2484
2485
public int size() {
2486
return Element.this.attrSize();
2487
}
2488
2489
public Iterator<Map.Entry<String, String>> iterator() {
2490
return new EIter();
2491
}
2492
2493
@Override
2494
public void clear() {
2495
Element.this.clearAttrs();
2496
}
2497
}
2498
class AView extends AbstractMap<String, String> {
2499
2500
private transient Set<Map.Entry<String, String>> eSet;
2501
2502
public Set<Map.Entry<String, String>> entrySet() {
2503
if (eSet == null) {
2504
eSet = new ESet();
2505
}
2506
return eSet;
2507
}
2508
2509
@Override
2510
public int size() {
2511
return Element.this.attrSize();
2512
}
2513
2514
public boolean containsKey(String k) {
2515
return Element.this.containsAttr(k);
2516
}
2517
2518
public String get(String k) {
2519
return Element.this.getAttr(k);
2520
}
2521
2522
@Override
2523
public String put(String k, String v) {
2524
return Element.this.setAttr(k, v.toString());
2525
}
2526
2527
public String remove(String k) {
2528
return Element.this.setAttr(k, null);
2529
}
2530
}
2531
return new AView();
2532
}
2533
2534
/** Reports number of additional elements this object can accommodate
2535
* without reallocation.
2536
*/
2537
public int getExtraCapacity() {
2538
int abase = attrBase();
2539
return Math.max(0, abase - size - NEED_SLOP);
2540
}
2541
2542
/** Ensures that at least the given number of additional elements
2543
* can be added to this object without reallocation.
2544
*/
2545
public void ensureExtraCapacity(int cap) {
2546
if (cap == 0 || hasNulls(cap + NEED_SLOP)) {
2547
return;
2548
}
2549
setExtraCapacity(cap);
2550
}
2551
2552
/**
2553
* Trim excess capacity to zero, or do nothing if frozen.
2554
* This minimizes the space occupied by this Element,
2555
* at the expense of a reallocation if sub-elements or attributes
2556
* are added later.
2557
*/
2558
public void trimToSize() {
2559
if (isFrozen()) {
2560
return;
2561
}
2562
setExtraCapacity(0);
2563
}
2564
2565
/** Changes the number of additional elements this object can accommodate
2566
* without reallocation.
2567
*/
2568
public void setExtraCapacity(int cap) {
2569
checkNotFrozen();
2570
int abase = attrBase();
2571
int alen = parts.length - abase; // slots allocated for attrs
2572
int nlen = size + cap + NEED_SLOP + alen;
2573
if (nlen != parts.length) {
2574
Object[] nparts = new Object[nlen];
2575
// copy attributes
2576
System.arraycopy(parts, abase, nparts, nlen - alen, alen);
2577
// copy sub-elements
2578
System.arraycopy(parts, 0, nparts, 0, size);
2579
parts = nparts;
2580
}
2581
assert (cap == getExtraCapacity());
2582
}
2583
2584
// Return true if there are at least len nulls of slop available.
2585
boolean hasNulls(int len) {
2586
if (len == 0) {
2587
return true;
2588
}
2589
int lastNull = size + len - 1;
2590
if (lastNull >= parts.length) {
2591
return false;
2592
}
2593
return (parts[lastNull] == null);
2594
}
2595
2596
// Opens up parts array at pos by len spaces.
2597
void open(int pos, int len) {
2598
assert (pos < size);
2599
assert (hasNulls(len + NEED_SLOP));
2600
checkNotFrozen();
2601
int nsize = size + len;
2602
int tlen = size - pos;
2603
System.arraycopy(parts, pos, parts, pos + len, tlen);
2604
size = nsize;
2605
}
2606
2607
// Reallocate and open up at parts[pos] to at least len empty places.
2608
// Shift anything after pos right by len. Reallocate if necessary.
2609
// If pos < size, caller must fill it in with non-null values.
2610
// Returns incremented size; caller is responsible for storing it
2611
// down, if desired.
2612
int expand(int pos, int len) {
2613
assert (pos <= size);
2614
// There must be at least len nulls between elems and attrs.
2615
assert (!hasNulls(NEED_SLOP + len)); // caller responsibility
2616
checkNotFrozen();
2617
int nsize = size + len; // length of all elements
2618
int tlen = size - pos; // length of elements in post-pos tail
2619
int abase = attrBase();
2620
int alen = parts.length - abase; // slots allocated for attrs
2621
int nlen = nsize + alen + NEED_SLOP;
2622
nlen += (nlen >>> 1); // add new slop!
2623
Object[] nparts = new Object[nlen];
2624
// copy head of sub-elements
2625
System.arraycopy(parts, 0, nparts, 0, pos);
2626
// copy tail of sub-elements
2627
System.arraycopy(parts, pos, nparts, pos + len, tlen);
2628
// copy attributes
2629
System.arraycopy(parts, abase, nparts, nlen - alen, alen);
2630
// update self
2631
parts = nparts;
2632
//assert(hasNulls(len)); <- not yet true, since size != nsize
2633
return nsize;
2634
}
2635
2636
// Open or expand at the given position, as appropriate.
2637
boolean openOrExpand(int pos, int len) {
2638
if (pos < 0 || pos > size) {
2639
badIndex(pos);
2640
}
2641
if (hasNulls(len + NEED_SLOP)) {
2642
if (pos == size) {
2643
size += len;
2644
} else {
2645
open(pos, len);
2646
}
2647
return false;
2648
} else {
2649
size = expand(pos, len);
2650
return true;
2651
}
2652
}
2653
2654
// Close up at parts[pos] len old places.
2655
// Shift anything after pos left by len.
2656
// Fill unused end of parts with null.
2657
void close(int pos, int len) {
2658
assert (len > 0);
2659
assert ((size - pos) >= len);
2660
checkNotFrozen();
2661
int tlen = (size - pos) - len; // length of elements in post-pos tail
2662
int nsize = size - len;
2663
System.arraycopy(parts, pos + len, parts, pos, tlen);
2664
// reinitialize the unoccupied slots to null
2665
clearParts(nsize, nsize + len);
2666
// update self
2667
size = nsize;
2668
assert (hasNulls(len));
2669
}
2670
2671
public void writeTo(Writer w) throws IOException {
2672
new Printer(w).print(this);
2673
}
2674
2675
public void writePrettyTo(Writer w) throws IOException {
2676
prettyPrintTo(w, this);
2677
}
2678
2679
public String prettyString() {
2680
StringWriter sw = new StringWriter();
2681
try {
2682
writePrettyTo(sw);
2683
} catch (IOException ee) {
2684
throw new Error(ee); // should not happen
2685
}
2686
return sw.toString();
2687
}
2688
2689
@Override
2690
public String toString() {
2691
StringWriter sw = new StringWriter();
2692
try {
2693
writeTo(sw);
2694
} catch (IOException ee) {
2695
throw new Error(ee); // should not happen
2696
}
2697
return sw.toString();
2698
}
2699
2700
public String dump() {
2701
// For debugging only. Reveals internal layout.
2702
StringBuilder buf = new StringBuilder();
2703
buf.append("<").append(name).append("[").append(size).append("]");
2704
for (int i = 0; i < parts.length; i++) {
2705
Object p = parts[i];
2706
if (p == null) {
2707
buf.append(" null");
2708
} else {
2709
buf.append(" {");
2710
String cname = p.getClass().getName();
2711
cname = cname.substring(1 + cname.indexOf('/'));
2712
cname = cname.substring(1 + cname.indexOf('$'));
2713
cname = cname.substring(1 + cname.indexOf('#'));
2714
if (!cname.equals("String")) {
2715
buf.append(cname).append(":");
2716
}
2717
buf.append(p);
2718
buf.append("}");
2719
}
2720
}
2721
return buf.append(">").toString();
2722
}
2723
2724
public static java.lang.reflect.Method method(String name) {
2725
HashMap allM = allMethods;
2726
if (allM == null) {
2727
allM = makeAllMethods();
2728
}
2729
java.lang.reflect.Method res = (java.lang.reflect.Method) allMethods.get(name);
2730
if (res == null) {
2731
throw new IllegalArgumentException(name);
2732
}
2733
return res;
2734
}
2735
private static HashMap allMethods;
2736
2737
private static synchronized HashMap makeAllMethods() {
2738
if (allMethods != null) {
2739
return allMethods;
2740
}
2741
java.lang.reflect.Method[] methods = Element.class.getMethods();
2742
HashMap<String, java.lang.reflect.Method> allM = new HashMap<String, java.lang.reflect.Method>(),
2743
ambig = new HashMap<String, java.lang.reflect.Method>();
2744
for (int i = 0; i < methods.length; i++) {
2745
java.lang.reflect.Method m = methods[i];
2746
Class[] args = m.getParameterTypes();
2747
String name = m.getName();
2748
assert (java.lang.reflect.Modifier.isPublic(m.getModifiers()));
2749
if (name.startsWith("notify")) {
2750
continue;
2751
}
2752
if (name.endsWith("Attr")
2753
&& args.length > 0 && args[0] == int.class) // ignore getAttr(int), etc.
2754
{
2755
continue;
2756
}
2757
if (name.endsWith("All")
2758
&& args.length > 1 && args[0] == Filter.class) // ignore findAll(Filter, int...), etc.
2759
{
2760
continue;
2761
}
2762
java.lang.reflect.Method pm = allM.put(name, m);
2763
if (pm != null) {
2764
Class[] pargs = pm.getParameterTypes();
2765
if (pargs.length > args.length) {
2766
allM.put(name, pm); // put it back
2767
} else if (pargs.length == args.length) {
2768
ambig.put(name, pm); // make a note of it
2769
}
2770
}
2771
}
2772
// Delete ambiguous methods.
2773
for (Map.Entry<String, java.lang.reflect.Method> e : ambig.entrySet()) {
2774
String name = e.getKey();
2775
java.lang.reflect.Method pm = e.getValue();
2776
java.lang.reflect.Method m = allM.get(name);
2777
Class[] args = m.getParameterTypes();
2778
Class[] pargs = pm.getParameterTypes();
2779
if (pargs.length == args.length) {
2780
//System.out.println("ambig: "+pm);
2781
//System.out.println(" with: "+m);
2782
//ambig: int addAll(int,Element)
2783
// with: int addAll(int,Collection)
2784
allM.put(name, null); // get rid of
2785
}
2786
}
2787
//System.out.println("allM: "+allM);
2788
return allMethods = allM;
2789
}
2790
}
2791
2792
static Object fixupString(Object part) {
2793
if (part instanceof CharSequence && !(part instanceof String)) {
2794
return part.toString();
2795
} else {
2796
return part;
2797
}
2798
}
2799
2800
public static final class Special implements Comparable<Special> {
2801
2802
String kind;
2803
Object value;
2804
2805
public Special(String kind, Object value) {
2806
this.kind = kind;
2807
this.value = value;
2808
}
2809
2810
public String getKind() {
2811
return kind;
2812
}
2813
2814
public Object getValue() {
2815
return value;
2816
}
2817
2818
@Override
2819
public boolean equals(Object o) {
2820
if (!(o instanceof Special)) {
2821
return false;
2822
}
2823
Special that = (Special) o;
2824
return this.kind.equals(that.kind) && this.value.equals(that.value);
2825
}
2826
2827
@Override
2828
public int hashCode() {
2829
return kind.hashCode() * 65 + value.hashCode();
2830
}
2831
2832
public int compareTo(Special that) {
2833
int r = this.kind.compareTo(that.kind);
2834
if (r != 0) {
2835
return r;
2836
}
2837
return ((Comparable) this.value).compareTo(that.value);
2838
}
2839
2840
@Override
2841
public String toString() {
2842
int split = kind.indexOf(' ');
2843
String pref = kind.substring(0, split < 0 ? 0 : split);
2844
String post = kind.substring(split + 1);
2845
return pref + value + post;
2846
}
2847
}
2848
2849
/** Supports sorting of mixed content. Sorts strings first,
2850
* then Elements, then everything else (as Comparable).
2851
*/
2852
public static Comparator<Object> contentOrder() {
2853
return CONTENT_ORDER;
2854
}
2855
private static Comparator<Object> CONTENT_ORDER = new ContentComparator();
2856
2857
private static class ContentComparator implements Comparator<Object> {
2858
2859
public int compare(Object o1, Object o2) {
2860
boolean cs1 = (o1 instanceof CharSequence);
2861
boolean cs2 = (o2 instanceof CharSequence);
2862
if (cs1 && cs2) {
2863
String s1 = (String) fixupString(o1);
2864
String s2 = (String) fixupString(o2);
2865
return s1.compareTo(s2);
2866
}
2867
if (cs1) {
2868
return 0 - 1;
2869
}
2870
if (cs2) {
2871
return 1 - 0;
2872
}
2873
boolean el1 = (o1 instanceof Element);
2874
boolean el2 = (o2 instanceof Element);
2875
if (el1 && el2) {
2876
return ((Element) o1).compareTo((Element) o2);
2877
}
2878
if (el1) {
2879
return 0 - 1;
2880
}
2881
if (el2) {
2882
return 1 - 0;
2883
}
2884
return ((Comparable) o1).compareTo(o2);
2885
}
2886
}
2887
2888
/** Used to find, filter, or transform sub-elements.
2889
* When used as a predicate, the filter returns a null
2890
* value for false, and the original object value for true.
2891
* When used as a transformer, the filter may return
2892
* null, for no values, the original object, a new object,
2893
* or an anonymous Element (meaning multiple results).
2894
*/
2895
public interface Filter {
2896
2897
Object filter(Object value);
2898
}
2899
2900
/** Use this to find an element, perhaps with a given name. */
2901
public static class ElementFilter implements Filter {
2902
2903
/** Subclasses may override this to implement better value tests.
2904
* By default, it returns the element itself, thus recognizing
2905
* all elements, regardless of name.
2906
*/
2907
public Element filter(Element elem) {
2908
return elem; // override this
2909
}
2910
2911
public final Object filter(Object value) {
2912
if (!(value instanceof Element)) {
2913
return null;
2914
}
2915
return filter((Element) value);
2916
}
2917
2918
@Override
2919
public String toString() {
2920
return "<ElementFilter name='*'/>";
2921
}
2922
}
2923
private static Filter elementFilter;
2924
2925
public static Filter elementFilter() {
2926
return (elementFilter != null) ? elementFilter : (elementFilter = new ElementFilter());
2927
}
2928
2929
public static Filter elementFilter(final String name) {
2930
name.toString(); // null check
2931
return new ElementFilter() {
2932
2933
@Override
2934
public Element filter(Element elem) {
2935
return name.equals(elem.name) ? elem : null;
2936
}
2937
2938
@Override
2939
public String toString() {
2940
return "<ElementFilter name='" + name + "'/>";
2941
}
2942
};
2943
}
2944
2945
public static Filter elementFilter(final Collection nameSet) {
2946
nameSet.getClass(); // null check
2947
return new ElementFilter() {
2948
2949
@Override
2950
public Element filter(Element elem) {
2951
return nameSet.contains(elem.name) ? elem : null;
2952
}
2953
2954
@Override
2955
public String toString() {
2956
return "<ElementFilter name='" + nameSet + "'/>";
2957
}
2958
};
2959
}
2960
2961
public static Filter elementFilter(String... nameSet) {
2962
Collection<String> ncoll = Arrays.asList(nameSet);
2963
if (nameSet.length > 10) {
2964
ncoll = new HashSet<String>(ncoll);
2965
}
2966
return elementFilter(ncoll);
2967
}
2968
2969
/** Use this to find an element with a named attribute,
2970
* possibly with a particular value.
2971
* (Note that an attribute is missing if and only if its value is null.)
2972
*/
2973
public static class AttrFilter extends ElementFilter {
2974
2975
protected final String attrName;
2976
2977
public AttrFilter(String attrName) {
2978
this.attrName = attrName.toString();
2979
}
2980
2981
/** Subclasses may override this to implement better value tests.
2982
* By default, it returns true for any non-null value, thus
2983
* recognizing any attribute of the given name, regardless of value.
2984
*/
2985
public boolean test(String attrVal) {
2986
return attrVal != null; // override this
2987
}
2988
2989
@Override
2990
public final Element filter(Element elem) {
2991
return test(elem.getAttr(attrName)) ? elem : null;
2992
}
2993
2994
@Override
2995
public String toString() {
2996
return "<AttrFilter name='" + attrName + "' value='*'/>";
2997
}
2998
}
2999
3000
public static Filter attrFilter(String attrName) {
3001
return new AttrFilter(attrName);
3002
}
3003
3004
public static Filter attrFilter(String attrName, final String attrVal) {
3005
if (attrVal == null) {
3006
return not(attrFilter(attrName));
3007
}
3008
return new AttrFilter(attrName) {
3009
3010
@Override
3011
public boolean test(String attrVal2) {
3012
return attrVal.equals(attrVal2);
3013
}
3014
3015
@Override
3016
public String toString() {
3017
return "<AttrFilter name='" + attrName + "' value='" + attrVal + "'/>";
3018
}
3019
};
3020
}
3021
3022
public static Filter attrFilter(Element matchThis, String attrName) {
3023
return attrFilter(attrName, matchThis.getAttr(attrName));
3024
}
3025
3026
/** Use this to find a sub-element of a given class. */
3027
public static Filter classFilter(final Class clazz) {
3028
return new Filter() {
3029
3030
public Object filter(Object value) {
3031
return clazz.isInstance(value) ? value : null;
3032
}
3033
3034
@Override
3035
public String toString() {
3036
return "<ClassFilter class='" + clazz.getName() + "'/>";
3037
}
3038
};
3039
}
3040
private static Filter textFilter;
3041
3042
public static Filter textFilter() {
3043
return (textFilter != null) ? textFilter : (textFilter = classFilter(CharSequence.class));
3044
}
3045
private static Filter specialFilter;
3046
3047
public static Filter specialFilter() {
3048
return (specialFilter != null) ? specialFilter : (specialFilter = classFilter(Special.class));
3049
}
3050
private static Filter selfFilter;
3051
3052
/** This filter always returns its own argument. */
3053
public static Filter selfFilter() {
3054
if (selfFilter != null) {
3055
return selfFilter;
3056
}
3057
return selfFilter = new Filter() {
3058
3059
public Object filter(Object value) {
3060
return value;
3061
}
3062
3063
@Override
3064
public String toString() {
3065
return "<Self/>";
3066
}
3067
};
3068
}
3069
3070
/** This filter always returns a fixed value, regardless of argument. */
3071
public static Filter constantFilter(final Object value) {
3072
return new Filter() {
3073
3074
public Object filter(Object ignore) {
3075
return value;
3076
}
3077
3078
@Override
3079
public String toString() {
3080
return "<Constant>" + value + "</Constant>";
3081
}
3082
};
3083
}
3084
private static Filter nullFilter;
3085
3086
public static Filter nullFilter() {
3087
return (nullFilter != null) ? nullFilter : (nullFilter = constantFilter(null));
3088
}
3089
private static Filter emptyFilter;
3090
3091
public static Filter emptyFilter() {
3092
return (emptyFilter != null) ? emptyFilter : (emptyFilter = constantFilter(Element.EMPTY));
3093
}
3094
3095
/** Use this to invert the logical sense of the given filter. */
3096
public static Filter not(final Filter f) {
3097
return new Filter() {
3098
3099
public Object filter(Object value) {
3100
return f.filter(value) == null ? value : null;
3101
}
3102
3103
@Override
3104
public String toString() {
3105
return "<Not>" + f + "</Not>";
3106
}
3107
};
3108
}
3109
3110
/** Use this to combine several filters with logical AND.
3111
* Returns either the first null or the last non-null value.
3112
*/
3113
public static Filter and(final Filter f0, final Filter f1) {
3114
return and(new Filter[]{f0, f1});
3115
}
3116
3117
public static Filter and(final Filter... fs) {
3118
switch (fs.length) {
3119
case 0:
3120
return selfFilter(); // always true (on non-null inputs)
3121
case 1:
3122
return fs[0];
3123
}
3124
return new Filter() {
3125
3126
public Object filter(Object value) {
3127
Object res = fs[0].filter(value);
3128
if (res != null) {
3129
res = fs[1].filter(value);
3130
for (int i = 2; res != null && i < fs.length; i++) {
3131
res = fs[i].filter(value);
3132
}
3133
}
3134
return res;
3135
}
3136
3137
@Override
3138
public String toString() {
3139
return opToString("<And>", fs, "</And>");
3140
}
3141
};
3142
}
3143
3144
/** Use this to combine several filters with logical OR.
3145
* Returns either the first non-null or the last null value.
3146
*/
3147
public static Filter or(final Filter f0, final Filter f1) {
3148
return or(new Filter[]{f0, f1});
3149
}
3150
3151
public static Filter or(final Filter... fs) {
3152
switch (fs.length) {
3153
case 0:
3154
return nullFilter();
3155
case 1:
3156
return fs[0];
3157
}
3158
return new Filter() {
3159
3160
public Object filter(Object value) {
3161
Object res = fs[0].filter(value);
3162
if (res == null) {
3163
res = fs[1].filter(value);
3164
for (int i = 2; res == null && i < fs.length; i++) {
3165
res = fs[i].filter(value);
3166
}
3167
}
3168
return res;
3169
}
3170
3171
@Override
3172
public String toString() {
3173
return opToString("<Or>", fs, "</Or>");
3174
}
3175
};
3176
}
3177
3178
/** Use this to combine several filters with logical AND,
3179
* and where each non-null result is passed as the argument
3180
* to the next filter.
3181
* Returns either the first null or the last non-null value.
3182
*/
3183
public static Filter stack(final Filter f0, final Filter f1) {
3184
return stack(new Filter[]{f0, f1});
3185
}
3186
3187
public static Filter stack(final Filter... fs) {
3188
switch (fs.length) {
3189
case 0:
3190
return nullFilter();
3191
case 1:
3192
return fs[0];
3193
}
3194
return new Filter() {
3195
3196
public Object filter(Object value) {
3197
Object res = fs[0].filter(value);
3198
if (res != null) {
3199
res = fs[1].filter(res);
3200
for (int i = 2; res != null && i < fs.length; i++) {
3201
res = fs[i].filter(res);
3202
}
3203
}
3204
return res;
3205
}
3206
3207
@Override
3208
public String toString() {
3209
return opToString("<Stack>", fs, "</Stack>");
3210
}
3211
};
3212
}
3213
3214
/** Copy everything produced by f to sink, using addContent. */
3215
public static Filter content(final Filter f, final Collection<Object> sink) {
3216
return new Filter() {
3217
3218
public Object filter(Object value) {
3219
Object res = f.filter(value);
3220
addContent(res, sink);
3221
return res;
3222
}
3223
3224
@Override
3225
public String toString() {
3226
return opToString("<addContent>", new Object[]{f, sink},
3227
"</addContent>");
3228
}
3229
};
3230
}
3231
3232
/** Look down the tree using f, collecting fx, else recursing into x.
3233
* Identities:
3234
* <code>
3235
* findInTree(f, s) == findInTree(content(f, s))
3236
* findInTree(f) == replaceInTree(and(f, selfFilter())).
3237
* </code>
3238
*/
3239
public static Filter findInTree(Filter f, Collection<Object> sink) {
3240
if (sink != null) {
3241
f = content(f, sink);
3242
}
3243
return findInTree(f);
3244
}
3245
3246
/** Look down the tree using f, recursing into x unless fx. */
3247
public static Filter findInTree(final Filter f) {
3248
return new Filter() {
3249
3250
public Object filter(Object value) {
3251
Object res = f.filter(value);
3252
if (res != null) {
3253
return res;
3254
}
3255
if (value instanceof Element) {
3256
// recurse
3257
return ((Element) value).find(this);
3258
}
3259
return null;
3260
}
3261
3262
@Override
3263
public String toString() {
3264
return opToString("<FindInTree>", new Object[]{f},
3265
"</FindInTree>");
3266
}
3267
};
3268
}
3269
3270
/** Look down the tree using f. Replace each x with fx, else recurse.
3271
* If post filter g is given, optionally replace with gx after recursion.
3272
*/
3273
public static Filter replaceInTree(final Filter f, final Filter g) {
3274
return new Filter() {
3275
3276
public Object filter(Object value) {
3277
Object res = (f == null) ? null : f.filter(value);
3278
if (res != null) {
3279
return res;
3280
}
3281
if (value instanceof Element) {
3282
// recurse
3283
((Element) value).replaceAll(this);
3284
// Optional postorder traversal:
3285
if (g != null) {
3286
res = g.filter(value);
3287
}
3288
}
3289
return res; // usually null, meaning no replacement
3290
}
3291
3292
@Override
3293
public String toString() {
3294
return opToString("<ReplaceInTree>",
3295
new Object[]{f, g},
3296
"</ReplaceInTree>");
3297
}
3298
};
3299
}
3300
3301
public static Filter replaceInTree(Filter f) {
3302
f.getClass(); // null check
3303
return replaceInTree(f, null);
3304
}
3305
3306
/** Make a filter which calls this method on the given element.
3307
* If the method is static, the first argument is passed the
3308
* the subtree value being filtered.
3309
* If the method is non-static, the receiver is the subtree value itself.
3310
* <p>
3311
* Optionally, additional arguments may be specified.
3312
* <p>
3313
* If the filtered value does not match the receiver class
3314
* (or else the first argument type, if the method is static),
3315
* the filter returns null without invoking the method.
3316
* <p>
3317
* The returned filter value is the result returned from the method.
3318
* Optionally, a non-null special false result value may be specified.
3319
* If the result returned from the method is equal to that false value,
3320
* the filter will return null.
3321
*/
3322
public static Filter methodFilter(java.lang.reflect.Method m, Object[] extraArgs,
3323
Object falseResult) {
3324
return methodFilter(m, false, extraArgs, falseResult);
3325
}
3326
3327
public static Filter methodFilter(java.lang.reflect.Method m,
3328
Object[] args) {
3329
return methodFilter(m, args, null);
3330
}
3331
3332
public static Filter methodFilter(java.lang.reflect.Method m) {
3333
return methodFilter(m, null, null);
3334
}
3335
3336
public static Filter testMethodFilter(java.lang.reflect.Method m, Object[] extraArgs,
3337
Object falseResult) {
3338
return methodFilter(m, true, extraArgs, falseResult);
3339
}
3340
3341
public static Filter testMethodFilter(java.lang.reflect.Method m, Object[] extraArgs) {
3342
return methodFilter(m, true, extraArgs, zeroArgs.get(m.getReturnType()));
3343
}
3344
3345
public static Filter testMethodFilter(java.lang.reflect.Method m) {
3346
return methodFilter(m, true, null, zeroArgs.get(m.getReturnType()));
3347
}
3348
3349
private static Filter methodFilter(final java.lang.reflect.Method m,
3350
final boolean isTest,
3351
Object[] extraArgs, final Object falseResult) {
3352
Class[] params = m.getParameterTypes();
3353
final boolean isStatic = java.lang.reflect.Modifier.isStatic(m.getModifiers());
3354
int insertLen = (isStatic ? 1 : 0);
3355
if (insertLen + (extraArgs == null ? 0 : extraArgs.length) > params.length) {
3356
throw new IllegalArgumentException("too many arguments");
3357
}
3358
final Object[] args = (params.length == insertLen) ? null
3359
: new Object[params.length];
3360
final Class valueType = !isStatic ? m.getDeclaringClass() : params[0];
3361
if (valueType.isPrimitive()) {
3362
throw new IllegalArgumentException("filtered value must be reference type");
3363
}
3364
int fillp = insertLen;
3365
if (extraArgs != null) {
3366
for (int i = 0; i < extraArgs.length; i++) {
3367
args[fillp++] = extraArgs[i];
3368
}
3369
}
3370
if (args != null) {
3371
while (fillp < args.length) {
3372
Class param = params[fillp];
3373
args[fillp++] = param.isPrimitive() ? zeroArgs.get(param) : null;
3374
}
3375
}
3376
final Thread curt = Thread.currentThread();
3377
class MFilt implements Filter {
3378
3379
public Object filter(Object value) {
3380
if (!valueType.isInstance(value)) {
3381
return null; // filter fails quickly
3382
}
3383
Object[] args1 = args;
3384
if (isStatic) {
3385
if (args1 == null) {
3386
args1 = new Object[1];
3387
} else if (curt != Thread.currentThread()) // Dirty hack to curtail array copying in common case.
3388
{
3389
args1 = (Object[]) args1.clone();
3390
}
3391
args1[0] = value;
3392
}
3393
Object res;
3394
try {
3395
res = m.invoke(value, args1);
3396
} catch (java.lang.reflect.InvocationTargetException te) {
3397
Throwable ee = te.getCause();
3398
if (ee instanceof RuntimeException) {
3399
throw (RuntimeException) ee;
3400
}
3401
if (ee instanceof Error) {
3402
throw (Error) ee;
3403
}
3404
throw new RuntimeException("throw in filter", ee);
3405
} catch (IllegalAccessException ee) {
3406
throw new RuntimeException("access error in filter", ee);
3407
}
3408
if (res == null) {
3409
if (!isTest && m.getReturnType() == Void.TYPE) {
3410
// Void methods return self by convention.
3411
// (But void "tests" always return false.)
3412
res = value;
3413
}
3414
} else {
3415
if (falseResult != null && falseResult.equals(res)) {
3416
res = null;
3417
} else if (isTest) {
3418
// Tests return self by convention.
3419
res = value;
3420
}
3421
}
3422
return res;
3423
}
3424
3425
@Override
3426
public String toString() {
3427
return "<Method>" + m + "</Method>";
3428
}
3429
}
3430
return new MFilt();
3431
}
3432
private static HashMap<Class, Object> zeroArgs = new HashMap<Class, Object>();
3433
3434
static {
3435
zeroArgs.put(Boolean.TYPE, Boolean.FALSE);
3436
zeroArgs.put(Character.TYPE, new Character((char) 0));
3437
zeroArgs.put(Byte.TYPE, new Byte((byte) 0));
3438
zeroArgs.put(Short.TYPE, new Short((short) 0));
3439
zeroArgs.put(Integer.TYPE, new Integer(0));
3440
zeroArgs.put(Float.TYPE, new Float(0));
3441
zeroArgs.put(Long.TYPE, new Long(0));
3442
zeroArgs.put(Double.TYPE, new Double(0));
3443
}
3444
3445
private static String opToString(String s1, Object[] s2, String s3) {
3446
StringBuilder buf = new StringBuilder(s1);
3447
for (int i = 0; i < s2.length; i++) {
3448
if (s2[i] != null) {
3449
buf.append(s2[i]);
3450
}
3451
}
3452
buf.append(s3);
3453
return buf.toString();
3454
}
3455
3456
/** Call the filter on each list element x, and replace x with the
3457
* resulting filter value e, or its parts.
3458
* If e is null, keep x. (This eases use of partial-domain filters.)
3459
* If e is a TokenList or an anonymous Element, add e's parts
3460
* to the list instead of x.
3461
* Otherwise, replace x by e.
3462
* <p>
3463
* The effect at each list position <code>n</code> may be expressed
3464
* in terms of XMLKit.addContent as follows:
3465
* <pre>
3466
* Object e = f.filter(target.get(n));
3467
* if (e != null) {
3468
* target.remove(n);
3469
* addContent(e, target.subList(n,n));
3470
* }
3471
* </pre>
3472
* <p>
3473
* Note: To force deletion of x, simply have the filter return
3474
* Element.EMPTY or TokenList.EMPTY.
3475
* To force null filter values to have this effect,
3476
* use the expression: <code>or(f, emptyFilter())</code>.
3477
*/
3478
public static void replaceAll(Filter f, List<Object> target) {
3479
for (ListIterator<Object> i = target.listIterator(); i.hasNext();) {
3480
Object x = i.next();
3481
Object fx = f.filter(x);
3482
if (fx == null) {
3483
// Unliked addContent, a null is a no-op here.
3484
// i.remove();
3485
} else if (fx instanceof TokenList) {
3486
TokenList tl = (TokenList) fx;
3487
if (tl.size() == 1) {
3488
i.set(tl);
3489
} else {
3490
i.remove();
3491
for (String part : tl) {
3492
i.add(part);
3493
}
3494
}
3495
} else if (fx instanceof Element
3496
&& ((Element) fx).isAnonymous()) {
3497
Element anon = (Element) fx;
3498
if (anon.size() == 1) {
3499
i.set(anon);
3500
} else {
3501
i.remove();
3502
for (Object part : anon) {
3503
i.add(part);
3504
}
3505
}
3506
} else if (x != fx) {
3507
i.set(fx);
3508
}
3509
}
3510
}
3511
3512
/** If e is null, return zero.
3513
* If e is a TokenList or an anonymous Element, add e's parts
3514
* to the collection, and return the number of parts.
3515
* Otherwise, add e to the collection, and return one.
3516
* If the collection reference is null, the result is as if
3517
* a throwaway collection were used.
3518
*/
3519
public static int addContent(Object e, Collection<Object> sink) {
3520
if (e == null) {
3521
return 0;
3522
} else if (e instanceof TokenList) {
3523
TokenList tl = (TokenList) e;
3524
if (sink != null) {
3525
sink.addAll(tl);
3526
}
3527
return tl.size();
3528
} else if (e instanceof Element
3529
&& ((Element) e).isAnonymous()) {
3530
Element anon = (Element) e;
3531
if (sink != null) {
3532
sink.addAll(anon.asList());
3533
}
3534
return anon.size();
3535
} else {
3536
if (sink != null) {
3537
sink.add(e);
3538
}
3539
return 1;
3540
}
3541
}
3542
3543
static Collection<Object> newCounterColl() {
3544
return new AbstractCollection<Object>() {
3545
3546
int size;
3547
3548
public int size() {
3549
return size;
3550
}
3551
3552
@Override
3553
public boolean add(Object o) {
3554
++size;
3555
return true;
3556
}
3557
3558
public Iterator<Object> iterator() {
3559
throw new UnsupportedOperationException();
3560
}
3561
};
3562
}
3563
3564
/** SAX2 document handler for building Element trees. */
3565
private static class Builder implements ContentHandler, LexicalHandler {
3566
/*, EntityResolver, DTDHandler, ErrorHandler*/
3567
3568
Collection<Object> sink;
3569
boolean makeFrozen;
3570
boolean tokenizing;
3571
3572
Builder(Collection<Object> sink, boolean tokenizing, boolean makeFrozen) {
3573
this.sink = sink;
3574
this.tokenizing = tokenizing;
3575
this.makeFrozen = makeFrozen;
3576
}
3577
Object[] parts = new Object[30];
3578
int nparts = 0;
3579
int[] attrBases = new int[10]; // index into parts
3580
int[] elemBases = new int[10]; // index into parts
3581
int depth = -1; // index into attrBases, elemBases
3582
// Parts is organized this way:
3583
// | name0 | akey aval ... | subelem ... | name1 | ... |
3584
// The position of the first "akey" after name0 is attrBases[0].
3585
// The position of the first "subelem" after name0 is elemBases[0].
3586
// The position after the last part is always nparts.
3587
int mergeableToken = -1; // index into parts of recent CharSequence
3588
boolean inCData = false;
3589
3590
void addPart(Object x) {
3591
//System.out.println("addPart "+x);
3592
if (nparts == parts.length) {
3593
Object[] newParts = new Object[parts.length * 2];
3594
System.arraycopy(parts, 0, newParts, 0, parts.length);
3595
parts = newParts;
3596
}
3597
parts[nparts++] = x;
3598
}
3599
3600
Object getMergeableToken() {
3601
if (mergeableToken == nparts - 1) {
3602
assert (parts[mergeableToken] instanceof CharSequence);
3603
return parts[nparts - 1];
3604
} else {
3605
return null;
3606
}
3607
}
3608
3609
void clearMergeableToken() {
3610
if (mergeableToken >= 0) {
3611
// Freeze temporary StringBuffers into strings.
3612
assert (parts[mergeableToken] instanceof CharSequence);
3613
parts[mergeableToken] = parts[mergeableToken].toString();
3614
mergeableToken = -1;
3615
}
3616
}
3617
3618
void setMergeableToken() {
3619
if (mergeableToken != nparts - 1) {
3620
clearMergeableToken();
3621
mergeableToken = nparts - 1;
3622
assert (parts[mergeableToken] instanceof CharSequence);
3623
}
3624
}
3625
3626
// ContentHandler callbacks
3627
public void startElement(String ns, String localName, String name, Attributes atts) {
3628
clearMergeableToken();
3629
addPart(name.intern());
3630
++depth;
3631
if (depth == attrBases.length) {
3632
int oldlen = depth;
3633
int newlen = depth * 2;
3634
int[] newAB = new int[newlen];
3635
int[] newEB = new int[newlen];
3636
System.arraycopy(attrBases, 0, newAB, 0, oldlen);
3637
System.arraycopy(elemBases, 0, newEB, 0, oldlen);
3638
attrBases = newAB;
3639
elemBases = newEB;
3640
}
3641
attrBases[depth] = nparts;
3642
// Collect attributes.
3643
int na = atts.getLength();
3644
for (int k = 0; k < na; k++) {
3645
addPart(atts.getQName(k).intern());
3646
addPart(atts.getValue(k));
3647
}
3648
// Get ready to collect elements.
3649
elemBases[depth] = nparts;
3650
}
3651
3652
public void endElement(String ns, String localName, String name) {
3653
assert (depth >= 0);
3654
clearMergeableToken();
3655
int ebase = elemBases[depth];
3656
int elen = nparts - ebase;
3657
int abase = attrBases[depth];
3658
int alen = ebase - abase;
3659
int nbase = abase - 1;
3660
int cap = alen + (makeFrozen ? 0 : NEED_SLOP) + elen;
3661
Element e = new Element((String) parts[nbase], elen, cap);
3662
// Set up attributes.
3663
for (int k = 0; k < alen; k += 2) {
3664
e.parts[cap - k - 2] = parts[abase + k + 0];
3665
e.parts[cap - k - 1] = parts[abase + k + 1];
3666
}
3667
// Set up sub-elements.
3668
System.arraycopy(parts, ebase, e.parts, 0, elen);
3669
// Back out of this level.
3670
--depth;
3671
nparts = nbase;
3672
assert (e.isFrozen() == makeFrozen);
3673
assert (e.size() == elen);
3674
assert (e.attrSize() * 2 == alen);
3675
if (depth >= 0) {
3676
addPart(e);
3677
} else {
3678
sink.add(e);
3679
}
3680
}
3681
3682
public void startCDATA() {
3683
inCData = true;
3684
}
3685
3686
public void endCDATA() {
3687
inCData = false;
3688
}
3689
3690
public void characters(char[] buf, int off, int len) {
3691
boolean headSpace = false;
3692
boolean tailSpace = false;
3693
int firstLen;
3694
if (tokenizing && !inCData) {
3695
// Strip unquoted blanks.
3696
while (len > 0 && isWhitespace(buf[off])) {
3697
headSpace = true;
3698
++off;
3699
--len;
3700
}
3701
if (len == 0) {
3702
tailSpace = true; // it is all space
3703
}
3704
while (len > 0 && isWhitespace(buf[off + len - 1])) {
3705
tailSpace = true;
3706
--len;
3707
}
3708
firstLen = 0;
3709
while (firstLen < len && !isWhitespace(buf[off + firstLen])) {
3710
++firstLen;
3711
}
3712
} else {
3713
firstLen = len;
3714
}
3715
if (headSpace) {
3716
clearMergeableToken();
3717
}
3718
boolean mergeAtEnd = !tailSpace;
3719
// If buffer was empty, or had only ignorable blanks, do nothing.
3720
if (len == 0) {
3721
return;
3722
}
3723
// Decide whether to merge some of these chars into a previous token.
3724
Object prev = getMergeableToken();
3725
if (prev instanceof StringBuffer) {
3726
((StringBuffer) prev).append(buf, off, firstLen);
3727
} else if (prev == null) {
3728
addPart(new String(buf, off, firstLen));
3729
} else {
3730
// Merge two strings.
3731
String prevStr = prev.toString();
3732
StringBuffer prevBuf = new StringBuffer(prevStr.length() + firstLen);
3733
prevBuf.append(prevStr);
3734
prevBuf.append(buf, off, firstLen);
3735
if (mergeAtEnd && len == firstLen) {
3736
// Replace previous string with new StringBuffer.
3737
parts[nparts - 1] = prevBuf;
3738
} else {
3739
// Freeze it now.
3740
parts[nparts - 1] = prevBuf.toString();
3741
}
3742
}
3743
off += firstLen;
3744
len -= firstLen;
3745
if (len > 0) {
3746
// Appended only the first token.
3747
clearMergeableToken();
3748
// Add the rest as separate parts.
3749
while (len > 0) {
3750
while (len > 0 && isWhitespace(buf[off])) {
3751
++off;
3752
--len;
3753
}
3754
int nextLen = 0;
3755
while (nextLen < len && !isWhitespace(buf[off + nextLen])) {
3756
++nextLen;
3757
}
3758
assert (nextLen > 0);
3759
addPart(new String(buf, off, nextLen));
3760
off += nextLen;
3761
len -= nextLen;
3762
}
3763
}
3764
if (mergeAtEnd) {
3765
setMergeableToken();
3766
}
3767
}
3768
3769
public void ignorableWhitespace(char[] buf, int off, int len) {
3770
clearMergeableToken();
3771
if (false) {
3772
characters(buf, off, len);
3773
clearMergeableToken();
3774
}
3775
}
3776
3777
public void comment(char[] buf, int off, int len) {
3778
addPart(new Special("<!-- -->", new String(buf, off, len)));
3779
}
3780
3781
public void processingInstruction(String name, String instruction) {
3782
Element pi = new Element(name);
3783
pi.add(instruction);
3784
addPart(new Special("<? ?>", pi));
3785
}
3786
3787
public void skippedEntity(String name) {
3788
}
3789
3790
public void startDTD(String name, String publicId, String systemId) {
3791
}
3792
3793
public void endDTD() {
3794
}
3795
3796
public void startEntity(String name) {
3797
}
3798
3799
public void endEntity(String name) {
3800
}
3801
3802
public void setDocumentLocator(org.xml.sax.Locator locator) {
3803
}
3804
3805
public void startDocument() {
3806
}
3807
3808
public void endDocument() {
3809
}
3810
3811
public void startPrefixMapping(String prefix, String uri) {
3812
}
3813
3814
public void endPrefixMapping(String prefix) {
3815
}
3816
}
3817
3818
/** Produce a ContentHandler for use with an XML parser.
3819
* The object is <em>also</em> a LexicalHandler.
3820
* Every top-level Element produced will get added to sink.
3821
* All elements will be frozen iff makeFrozen is true.
3822
*/
3823
public static ContentHandler makeBuilder(Collection<Object> sink, boolean tokenizing, boolean makeFrozen) {
3824
return new Builder(sink, tokenizing, makeFrozen);
3825
}
3826
3827
public static ContentHandler makeBuilder(Collection<Object> sink, boolean tokenizing) {
3828
return new Builder(sink, tokenizing, false);
3829
}
3830
3831
public static ContentHandler makeBuilder(Collection<Object> sink) {
3832
return makeBuilder(sink, false, false);
3833
}
3834
3835
public static Element readFrom(Reader in, boolean tokenizing, boolean makeFrozen) throws IOException {
3836
Element sink = new Element();
3837
ContentHandler b = makeBuilder(sink.asList(), tokenizing, makeFrozen);
3838
XMLReader parser;
3839
try {
3840
parser = org.xml.sax.helpers.XMLReaderFactory.createXMLReader();
3841
} catch (SAXException ee) {
3842
throw new Error(ee);
3843
}
3844
//parser.setFastStandalone(true);
3845
parser.setContentHandler(b);
3846
try {
3847
parser.setProperty("http://xml.org/sax/properties/lexical-handler",
3848
(LexicalHandler) b);
3849
} catch (SAXException ee) {
3850
// Ignore. We will miss the comments and whitespace.
3851
}
3852
try {
3853
parser.parse(new InputSource(in));
3854
} catch (SAXParseException ee) {
3855
throw new RuntimeException("line " + ee.getLineNumber() + " col " + ee.getColumnNumber() + ": ", ee);
3856
} catch (SAXException ee) {
3857
throw new RuntimeException(ee);
3858
}
3859
switch (sink.size()) {
3860
case 0:
3861
return null;
3862
case 1:
3863
if (sink.get(0) instanceof Element) {
3864
return (Element) sink.get(0);
3865
}
3866
// fall through
3867
default:
3868
if (makeFrozen) {
3869
sink.shallowFreeze();
3870
}
3871
return sink;
3872
}
3873
}
3874
3875
public static Element readFrom(Reader in, boolean tokenizing) throws IOException {
3876
return readFrom(in, tokenizing, false);
3877
}
3878
3879
public static Element readFrom(Reader in) throws IOException {
3880
return readFrom(in, false, false);
3881
}
3882
3883
public static void prettyPrintTo(OutputStream out, Element e) throws IOException {
3884
prettyPrintTo(new OutputStreamWriter(out), e);
3885
}
3886
3887
public static void prettyPrintTo(Writer out, Element e) throws IOException {
3888
Printer pr = new Printer(out);
3889
pr.pretty = true;
3890
pr.print(e);
3891
}
3892
3893
static class Outputter {
3894
3895
ContentHandler ch;
3896
LexicalHandler lh;
3897
3898
Outputter(ContentHandler ch, LexicalHandler lh) {
3899
this.ch = ch;
3900
this.lh = lh;
3901
}
3902
AttributesImpl atts = new AttributesImpl(); // handy
3903
3904
void output(Object x) throws SAXException {
3905
// Cf. jdom.org/jdom-b8/src/java/org/jdom/output/SAXOutputter.java
3906
if (x instanceof Element) {
3907
Element e = (Element) x;
3908
atts.clear();
3909
for (int asize = e.attrSize(), k = 0; k < asize; k++) {
3910
String key = e.getAttrName(k);
3911
String val = e.getAttr(k);
3912
atts.addAttribute("", "", key, "CDATA", val);
3913
}
3914
ch.startElement("", "", e.getName(), atts);
3915
for (int i = 0; i < e.size(); i++) {
3916
output(e.get(i));
3917
}
3918
ch.endElement("", "", e.getName());
3919
} else if (x instanceof Special) {
3920
Special sp = (Special) x;
3921
if (sp.kind.startsWith("<!--")) {
3922
char[] chars = sp.value.toString().toCharArray();
3923
lh.comment(chars, 0, chars.length);
3924
} else if (sp.kind.startsWith("<?")) {
3925
Element nameInstr = (Element) sp.value;
3926
ch.processingInstruction(nameInstr.name,
3927
nameInstr.get(0).toString());
3928
} else {
3929
// drop silently
3930
}
3931
} else {
3932
char[] chars = x.toString().toCharArray();
3933
ch.characters(chars, 0, chars.length);
3934
}
3935
}
3936
}
3937
3938
public static class Printer {
3939
3940
public Writer w;
3941
public boolean tokenizing;
3942
public boolean pretty;
3943
public boolean abbreviated; // nonstandard format cuts down on noise
3944
int depth = 0;
3945
boolean prevStr;
3946
int tabStop = 2;
3947
3948
public Printer(Writer w) {
3949
this.w = w;
3950
}
3951
3952
public Printer() {
3953
StringWriter sw = new StringWriter();
3954
this.w = sw;
3955
3956
}
3957
3958
public String nextString() {
3959
StringBuffer sb = ((StringWriter) w).getBuffer();
3960
String next = sb.toString();
3961
sb.setLength(0); // reset
3962
return next;
3963
}
3964
3965
void indent(int depth) throws IOException {
3966
if (depth > 0) {
3967
w.write("\n");
3968
}
3969
int nsp = tabStop * depth;
3970
while (nsp > 0) {
3971
String s = " ";
3972
String t = s.substring(0, nsp < s.length() ? nsp : s.length());
3973
w.write(t);
3974
nsp -= t.length();
3975
}
3976
}
3977
3978
public void print(Element e) throws IOException {
3979
if (e.isAnonymous()) {
3980
printParts(e);
3981
return;
3982
}
3983
printRecursive(e);
3984
}
3985
3986
public void println(Element e) throws IOException {
3987
print(e);
3988
w.write("\n");
3989
w.flush();
3990
}
3991
3992
public void printRecursive(Element e) throws IOException {
3993
boolean indented = false;
3994
if (pretty && !prevStr && e.size() + e.attrSize() > 0) {
3995
indent(depth);
3996
indented = true;
3997
}
3998
w.write("<");
3999
w.write(e.name);
4000
for (int asize = e.attrSize(), k = 0; k < asize; k++) {
4001
String key = e.getAttrName(k);
4002
String val = e.getAttr(k);
4003
w.write(" ");
4004
w.write(key);
4005
w.write("=");
4006
if (val == null) {
4007
w.write("null"); // Should not happen....
4008
} else if (val.indexOf("\"") < 0) {
4009
w.write("\"");
4010
writeToken(val, '"', w);
4011
w.write("\"");
4012
} else {
4013
w.write("'");
4014
writeToken(val, '\'', w);
4015
w.write("'");
4016
}
4017
}
4018
if (e.size() == 0) {
4019
w.write("/>");
4020
} else {
4021
++depth;
4022
if (abbreviated) {
4023
w.write("/");
4024
} else {
4025
w.write(">");
4026
}
4027
prevStr = false;
4028
printParts(e);
4029
if (abbreviated) {
4030
w.write(">");
4031
} else {
4032
if (indented && !prevStr) {
4033
indent(depth - 1);
4034
}
4035
w.write("</");
4036
w.write(e.name);
4037
w.write(">");
4038
}
4039
prevStr = false;
4040
--depth;
4041
}
4042
}
4043
4044
private void printParts(Element e) throws IOException {
4045
for (int i = 0; i < e.size(); i++) {
4046
Object x = e.get(i);
4047
if (x instanceof Element) {
4048
printRecursive((Element) x);
4049
prevStr = false;
4050
} else if (x instanceof Special) {
4051
w.write(((Special) x).toString());
4052
prevStr = false;
4053
} else {
4054
String s = String.valueOf(x);
4055
if (pretty) {
4056
s = s.trim();
4057
if (s.length() == 0) {
4058
continue;
4059
}
4060
}
4061
if (prevStr) {
4062
w.write(' ');
4063
}
4064
writeToken(s, tokenizing ? ' ' : (char) -1, w);
4065
prevStr = true;
4066
}
4067
if (pretty && depth == 0) {
4068
w.write("\n");
4069
prevStr = false;
4070
}
4071
}
4072
}
4073
}
4074
4075
public static void output(Object e, ContentHandler ch, LexicalHandler lh) throws SAXException {
4076
new Outputter(ch, lh).output(e);
4077
}
4078
4079
public static void output(Object e, ContentHandler ch) throws SAXException {
4080
if (ch instanceof LexicalHandler) {
4081
output(e, ch, (LexicalHandler) ch);
4082
} else {
4083
output(e, ch, null);
4084
}
4085
}
4086
4087
public static void writeToken(String val, char quote, Writer w) throws IOException {
4088
int len = val.length();
4089
boolean canUseCData = (quote != '"' && quote != '\'');
4090
int vpos = 0;
4091
for (int i = 0; i < len; i++) {
4092
char ch = val.charAt(i);
4093
if ((ch == '<' || ch == '&' || ch == '>' || ch == quote)
4094
|| (quote == ' ' && isWhitespace(ch))) {
4095
if (canUseCData) {
4096
assert (vpos == 0);
4097
writeCData(val, w);
4098
return;
4099
} else {
4100
if (vpos < i) {
4101
w.write(val, vpos, i - vpos);
4102
}
4103
String esc;
4104
switch (ch) {
4105
case '&':
4106
esc = "&amp;";
4107
break;
4108
case '<':
4109
esc = "&lt;";
4110
break;
4111
case '\'':
4112
esc = "&apos;";
4113
break;
4114
case '"':
4115
esc = "&quot;";
4116
break;
4117
case '>':
4118
esc = "&gt;";
4119
break;
4120
default:
4121
esc = "&#" + (int) ch + ";";
4122
break;
4123
}
4124
w.write(esc);
4125
vpos = i + 1; // skip escaped char
4126
}
4127
}
4128
}
4129
// write the unquoted tail
4130
w.write(val, vpos, val.length() - vpos);
4131
}
4132
4133
public static void writeCData(String val, Writer w) throws IOException {
4134
String begCData = "<![CDATA[";
4135
String endCData = "]]>";
4136
w.write(begCData);
4137
for (int vpos = 0, split;; vpos = split) {
4138
split = val.indexOf(endCData, vpos);
4139
if (split < 0) {
4140
w.write(val, vpos, val.length() - vpos);
4141
w.write(endCData);
4142
return;
4143
}
4144
split += 2; // bisect the "]]>" goo
4145
w.write(val, vpos, split - vpos);
4146
w.write(endCData);
4147
w.write(begCData);
4148
}
4149
}
4150
4151
public static TokenList convertToList(String str) {
4152
if (str == null) {
4153
return null;
4154
}
4155
return new TokenList(str);
4156
}
4157
4158
/** If str is null, empty, or blank, returns null.
4159
* Otherwise, return a Double if str spells a double value and contains '.' or 'e'.
4160
* Otherwise, return an Integer if str spells an int value.
4161
* Otherwise, return a Long if str spells a long value.
4162
* Otherwise, return a BigInteger for the string.
4163
* Otherwise, throw NumberFormatException.
4164
*/
4165
public static Number convertToNumber(String str) {
4166
if (str == null) {
4167
return null;
4168
}
4169
str = str.trim();
4170
if (str.length() == 0) {
4171
return null;
4172
}
4173
if (str.indexOf('.') >= 0
4174
|| str.indexOf('e') >= 0
4175
|| str.indexOf('E') >= 0) {
4176
return Double.valueOf(str);
4177
}
4178
try {
4179
long lval = Long.parseLong(str);
4180
if (lval == (int) lval) {
4181
// Narrow to Integer, if possible.
4182
return new Integer((int) lval);
4183
}
4184
return new Long(lval);
4185
} catch (NumberFormatException ee) {
4186
// Could not represent it as a long.
4187
return new java.math.BigInteger(str, 10);
4188
}
4189
}
4190
4191
public static Number convertToNumber(String str, Number dflt) {
4192
Number n = convertToNumber(str);
4193
return (n == null) ? dflt : n;
4194
}
4195
4196
public static long convertToLong(String str) {
4197
return convertToLong(str, 0);
4198
}
4199
4200
public static long convertToLong(String str, long dflt) {
4201
Number n = convertToNumber(str);
4202
return (n == null) ? dflt : n.longValue();
4203
}
4204
4205
public static double convertToDouble(String str) {
4206
return convertToDouble(str, 0);
4207
}
4208
4209
public static double convertToDouble(String str, double dflt) {
4210
Number n = convertToNumber(str);
4211
return (n == null) ? dflt : n.doubleValue();
4212
}
4213
4214
// Testing:
4215
public static void main(String... av) throws Exception {
4216
Element.method("getAttr");
4217
//new org.jdom.input.SAXBuilder().build(file).getRootElement();
4218
//jdom.org/jdom-b8/src/java/org/jdom/input/SAXBuilder.java
4219
//Document build(InputSource in) throws JDOMException
4220
4221
int reps = 0;
4222
4223
boolean tokenizing = false;
4224
boolean makeFrozen = false;
4225
if (av.length > 0) {
4226
tokenizing = true;
4227
try {
4228
reps = Integer.parseInt(av[0]);
4229
} catch (NumberFormatException ee) {
4230
}
4231
}
4232
Reader inR = new BufferedReader(new InputStreamReader(System.in));
4233
String inS = null;
4234
if (reps > 1) {
4235
StringWriter inBufR = new StringWriter(1 << 14);
4236
char[] cbuf = new char[1024];
4237
for (int nr; (nr = inR.read(cbuf)) >= 0;) {
4238
inBufR.write(cbuf, 0, nr);
4239
}
4240
inS = inBufR.toString();
4241
inR = new StringReader(inS);
4242
}
4243
Element e = XMLKit.readFrom(inR, tokenizing, makeFrozen);
4244
System.out.println("transform = " + e.findAll(methodFilter(Element.method("prettyString"))));
4245
System.out.println("transform = " + e.findAll(testMethodFilter(Element.method("hasText"))));
4246
long tm0 = 0;
4247
int warmup = 10;
4248
for (int i = 1; i < reps; i++) {
4249
inR = new StringReader(inS);
4250
readFrom(inR, tokenizing, makeFrozen);
4251
if (i == warmup) {
4252
System.out.println("Start timing...");
4253
tm0 = System.currentTimeMillis();
4254
}
4255
}
4256
if (tm0 != 0) {
4257
long tm1 = System.currentTimeMillis();
4258
System.out.println((reps - warmup) + " in " + (tm1 - tm0) + " ms");
4259
}
4260
System.out.println("hashCode = " + e.hashCode());
4261
String eStr = e.toString();
4262
System.out.println(eStr);
4263
Element e2 = readFrom(new StringReader(eStr), tokenizing, !makeFrozen);
4264
System.out.println("hashCode = " + e2.hashCode());
4265
if (!e.equals(e2)) {
4266
System.out.println("**** NOT EQUAL 1\n" + e2);
4267
}
4268
e = e.deepCopy();
4269
System.out.println("hashCode = " + e.hashCode());
4270
if (!e.equals(e2)) {
4271
System.out.println("**** NOT EQUAL 2");
4272
}
4273
e2.shallowFreeze();
4274
System.out.println("hashCode = " + e2.hashCode());
4275
if (!e.equals(e2)) {
4276
System.out.println("**** NOT EQUAL 3");
4277
}
4278
if (false) {
4279
System.out.println(e);
4280
} else {
4281
prettyPrintTo(new OutputStreamWriter(System.out), e);
4282
}
4283
System.out.println("Flat text:|" + e.getFlatText() + "|");
4284
{
4285
System.out.println("<!--- Sorted: --->");
4286
Element ce = e.copyContentOnly();
4287
ce.sort();
4288
prettyPrintTo(new OutputStreamWriter(System.out), ce);
4289
}
4290
{
4291
System.out.println("<!--- Trimmed: --->");
4292
Element tr = e.deepCopy();
4293
findInTree(testMethodFilter(Element.method("trimText"))).filter(tr);
4294
System.out.println(tr);
4295
}
4296
{
4297
System.out.println("<!--- Unstrung: --->");
4298
Element us = e.deepCopy();
4299
int nr = us.retainAllInTree(elementFilter(), null);
4300
System.out.println("nr=" + nr);
4301
System.out.println(us);
4302
}
4303
{
4304
System.out.println("<!--- Rollup: --->");
4305
Element ru = e.deepCopy();
4306
Filter makeAnonF =
4307
methodFilter(Element.method("setName"),
4308
new Object[]{ANON_NAME});
4309
Filter testSizeF =
4310
testMethodFilter(Element.method("size"));
4311
Filter walk =
4312
replaceInTree(and(not(elementFilter()), emptyFilter()),
4313
and(testSizeF, makeAnonF));
4314
ru = (Element) walk.filter(ru);
4315
//System.out.println(ru);
4316
prettyPrintTo(new OutputStreamWriter(System.out), ru);
4317
}
4318
}
4319
4320
static boolean isWhitespace(char c) {
4321
switch (c) {
4322
case 0x20:
4323
case 0x09:
4324
case 0x0D:
4325
case 0x0A:
4326
return true;
4327
}
4328
return false;
4329
}
4330
}
4331
4332