KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > gnu > xml > XMLFilter


1 // Copyright (c) 2001, 2002, 2003, 2006 Per M.A. Bothner.
2
// This is free software; for terms and warranty disclaimer see ./COPYING.
3

4 package gnu.xml;
5 import gnu.lists.*;
6 import gnu.text.Char;
7 import gnu.text.SourceLocator;
8 import gnu.text.SourceMessages;
9 import gnu.mapping.Symbol;
10 import gnu.expr.Keyword; // FIXME - bad cross-package dependency.
11

12 /** Fixup XML input events.
13  * Handles namespace resolution, and adds "namespace nodes" if needed.
14  * Does various error checking.
15  * This wrapper should be used when creating a NodeTree,
16  * has is done XQuery node constructor expressions.
17  * Can also be called directly from XMLParser, in which case we use a slightly
18  * lower-level interface where we array char array segments rather than
19  * Strings. This is to avoid duplicate String allocation and interning.
20  * The combination XMLParser+XMLFilter+NodeTree makes for a fast and
21  * compact way to read an XML file into a DOM.
22  * Future: Subsume ConsumeSAXHandler as well. FIXME
23  */

24
25 public class XMLFilter implements XConsumer, PositionConsumer
26 {
27   /** This is where we save attributes while processing a begin element.
28    * It may be the final output if {@code out instanceof NodeTree}. */

29   TreeList tlist;
30
31   /** The specified target Consumer that accepts the output.
32    * In contrast, base may be either {@code ==out} or {@code ==tlist}. */

33   public Consumer out;
34
35   Consumer base;
36
37   public static final int COPY_NAMESPACES_PRESERVE = 1;
38   public static final int COPY_NAMESPACES_INHERIT = 2;
39   public transient int copyNamespacesMode = COPY_NAMESPACES_PRESERVE;
40
41   /** A helper stack.
42    * This is logically multiple separate stacks, but we combine them into
43    * a single array. While this makes the code a little harder to read,
44    * it reduces memory overhead and (more importantly) should improve locality.
45    * For each nested document or group there is the saved value of
46    * namespaceBindings followed by a either a MappingInfo or Symbol
47    * from the emitBeginElement/beginGroup. This is followed by a MappingInfo
48    * or Symbol for each attribute we seen for the current group. */

49   Object JavaDoc[] workStack;
50   NamespaceBinding namespaceBindings;
51
52   private SourceMessages messages;
53   SourceLocator locator;
54
55   public void setSourceLocator (SourceLocator locator)
56   { this.locator = locator; }
57   public void setMessages (SourceMessages messages)
58   { this.messages = messages; }
59
60   /** Twice the number of active beginGroup and beginDocument calls. */
61   protected int nesting;
62
63   int previous = 0;
64   private static final int SAW_CR = 1;
65   private static final int SAW_KEYWORD = 2;
66   private static final int SAW_WORD = 3;
67
68   /** If {@code stringizingLevel > 0} then stringize rather than copy nodes.
69    * It counts the number of nested beginAttributes that are active.
70    * (In the future it should also count begun comment and
71    * processing-instruction constructors, when those support nesting.) */

72   protected int stringizingLevel;
73   /** Value of {@code nesting} just before outermost beginGroup
74    * while {@code stringizingLevel > 0}.
75    * I.e. if we're nested inside a group nested inside an attribute
76    * then {@code stringizingElementNesting >= 0},
77    * otherwise {@code stringizingElementNesting == -1}. */

78   protected int stringizingElementNesting = -1;
79   /** Postive if all output should be ignored.
80    * This happens if we're inside an attribute value inside an element which
81    * is stringized because it is in turm inside an outer attribute. Phew.
82    * If gets increment by nested attributes so we can tell when to stop. */

83   protected int ignoringLevel;
84
85   // List of indexes in tlist.data of begin of attribute.
86
int[] startIndexes = null;
87
88   /** The number of attributes.
89    * Zero means we've seen an element start tag.
90    * Gets reset to -1 when we no longer in the element header. */

91   int attrCount = -1;
92
93   boolean inStartTag;
94
95   /** The local name if currently processing an attribute value. */
96   String JavaDoc attrLocalName;
97   String JavaDoc attrPrefix;
98
99   /** Non-null if we're processing a namespace declaration attribute.
100    * In that case it is the prefix we're defining,
101    * or {@code ""} in the case of a default namespace. */

102   String JavaDoc currentNamespacePrefix;
103
104   /** True if namespace declarations should be passed through as attributes.
105    * Like SAX2's http://xml.org/features/namespace-prefixes. */

106   public boolean namespacePrefixes = false;
107
108   /** Map either lexical-QName or expanded-QName to a MappingInfo.
109    * This is conceptually three hash tables merged into a single data structure.
110    * (1) when we first see a tag (a QName as a lexical form before namespace
111    * resolution), we map the tag String to an preliminary info entry that
112    * has a null qname field.
113    * (2) After see the namespace declaration, we use the same table and keys,
114    * but name the uri and qtype.namespaceNodes also have to match.
115    * (3) Used for hash-consing NamespaceBindings.
116    */

117   MappingInfo[] mappingTable = new MappingInfo[128];
118   int mappingTableMask = mappingTable.length - 1;
119
120   boolean mismatchReported;
121
122   public void setLocator (SourceLocator locator)
123   {
124     this.locator = locator;
125   }
126
127   /** Functionally equivalent to
128    * {@code new NamespaceBinding(prefix, uri, oldBindings},
129    * but uses "hash consing".
130    */

131   public NamespaceBinding
132   findNamespaceBinding (String JavaDoc prefix, String JavaDoc uri, NamespaceBinding oldBindings)
133   {
134     int hash = uri == null ? 0 : uri.hashCode();
135     if (prefix != null)
136       hash ^= prefix.hashCode();
137     int bucket = hash & mappingTableMask;
138     MappingInfo info = mappingTable[bucket];
139     for (;; info = info.nextInBucket)
140       {
141         if (info == null)
142           {
143             info = new MappingInfo();
144             info.nextInBucket = mappingTable[bucket];
145             mappingTable[bucket] = info;
146             info.tagHash = hash;
147             info.prefix = prefix;
148             info.local = uri;
149             info.uri = uri;
150             if (uri == "")
151               uri = null;
152             NamespaceBinding namespaces
153               = new NamespaceBinding(prefix, uri, oldBindings);
154             info.namespaces = namespaces;
155             return info.namespaces;
156           }
157         NamespaceBinding namespaces;
158         if (info.tagHash == hash
159             && info.prefix == prefix
160             && (namespaces = info.namespaces) != null
161             && namespaces.getNext() == namespaceBindings
162             && namespaces.getPrefix() == prefix
163             && info.uri == uri)
164           {
165             return info.namespaces;
166           }
167       }
168   }
169
170   /** Return a MappingInfo containing a match namespaces.
171    * Specifically, return a {@code MappingInfo info} is such that
172    * {@code info.namespaces} is equal to
173    * {@code new NamespaceBinding(prefix, uri, oldBindings)}, where {@code uri}
174    * is {@code new String(uriChars, uriStart, uriLength).intern())}.
175    */

176   public MappingInfo lookupNamespaceBinding (String JavaDoc prefix,
177                                              char[] uriChars,
178                                              int uriStart, int uriLength,
179                                              int uriHash,
180                                              NamespaceBinding oldBindings)
181   {
182     int hash = prefix == null ? uriHash : prefix.hashCode() ^ uriHash;
183     // Search for a matching already-seen NamespaceBinding list.
184
// We hash these to so we can share lists that are equal but
185
// appear multiple times in the same XML file, as sometimes happens.
186
// This not only saves memory, but keeps hash bucket chains short,
187
// which is important since we don't resize the table.
188
int bucket = hash & mappingTableMask;
189     MappingInfo info = mappingTable[bucket];
190     for (;; info = info.nextInBucket)
191       {
192         if (info == null)
193           {
194             info = new MappingInfo();
195             info.nextInBucket = mappingTable[bucket];
196             mappingTable[bucket] = info;
197             String JavaDoc uri = new String JavaDoc(uriChars, uriStart, uriLength).intern();
198             // We re-use the same MappingInfo table that is mainly used
199
// for tag lookup, but re-interpreting the meaning of the
200
// various fields. Since MappingInfo hashes on prefix^local,
201
// we must do the same here.
202
info.tagHash = hash;
203             info.prefix = prefix;
204             info.local = uri;
205             info.uri = uri;
206             if (uri == "")
207               uri = null;
208             NamespaceBinding namespaces
209               = new NamespaceBinding(prefix, uri, oldBindings);
210             info.namespaces = namespaces;
211             return info;
212           }
213         NamespaceBinding namespaces;
214         if (info.tagHash == hash
215             && info.prefix == prefix
216             && (namespaces = info.namespaces) != null
217             && namespaces.getNext() == namespaceBindings
218             && namespaces.getPrefix() == prefix
219             && MappingInfo.equals(info.uri, uriChars, uriStart, uriLength))
220           {
221             return info;
222           }
223       }
224   }
225
226   public void endAttribute()
227   {
228     if (attrLocalName == null)
229       return;
230     if (previous == SAW_KEYWORD)
231       {
232         previous = 0;
233         return;
234       }
235     if (stringizingElementNesting >= 0)
236       ignoringLevel--;
237     if (--stringizingLevel == 0)
238       {
239         if (attrLocalName == "id" && attrPrefix == "xml")
240           {
241             // Need to normalize xml:id attributes.
242
int valStart
243               = startIndexes[attrCount-1] + TreeList.BEGIN_ATTRIBUTE_LONG_SIZE;
244             int valEnd = tlist.gapStart;
245             char[] data = tlist.data;
246             for (int i = valStart; ; )
247               {
248                 if (i >= valEnd)
249                   {
250                     // It's normalized. Nothing to do.
251
break;
252                   }
253                 char datum = data[i++];
254                 if (((datum & 0xFFFF) > TreeList.MAX_CHAR_SHORT)
255                     || datum == '\t' || datum == '\r' || datum == '\n'
256                     || (datum == ' ' && (i == valEnd || data[i] == ' ')))
257                   {
258                     // It's either not normalized, or the value contains
259
// chars values above MAX_CHAR_SHORT or non-chars.
260
// We could try to normalize in place but why bother?
261
// I'm assuming xml:id are going be normalized already.
262
// The exception is characters above MAX_CHAR_SHORT, but
263
// let's defer that until TreeList gets re-written.
264
StringBuffer JavaDoc sbuf = new StringBuffer JavaDoc();
265                     tlist.stringValue(valStart, valEnd, sbuf);
266                     tlist.gapStart = valStart;
267                     tlist.write(TextUtils
268                                 .replaceWhitespace(sbuf.toString(), true));
269                     break;
270                   }
271               }
272           }
273
274         attrLocalName = null;
275         attrPrefix = null;
276         if (currentNamespacePrefix == null || namespacePrefixes)
277           tlist.endAttribute();
278         if (currentNamespacePrefix != null)
279           {
280             // Handle raw namespace attribute from parser.
281
int attrStart = startIndexes[attrCount-1];
282             int uriStart = attrStart;
283             int uriEnd = tlist.gapStart;
284             int uriLength = uriEnd - uriStart;
285             char[] data = tlist.data;
286
287             // Check that the namespace attribute value is plain characters
288
// so we an use the in-place buffer.
289
// Calculate hash while we're at it.
290
int uriHash = 0;
291             for (int i = uriStart; i < uriEnd; i++)
292               {
293                 char datum = data[i];
294                 if ((datum & 0xFFFF) > TreeList.MAX_CHAR_SHORT)
295                   {
296                     StringBuffer JavaDoc sbuf = new StringBuffer JavaDoc();
297                     tlist.stringValue(uriStart, uriEnd, sbuf);
298                     uriHash = sbuf.hashCode();
299                     uriStart = 0;
300                     uriEnd = uriLength = sbuf.length();
301                     data = new char[sbuf.length()];
302                     sbuf.getChars(0, uriEnd, data, 0);
303                     break;
304                   }
305                 uriHash = 31 * uriHash + datum;
306               }
307             tlist.gapStart = attrStart;
308
309             String JavaDoc prefix = currentNamespacePrefix == "" ? null
310               : currentNamespacePrefix;
311             MappingInfo info
312               = lookupNamespaceBinding(prefix, data, uriStart, uriLength,
313                                        uriHash, namespaceBindings);
314             namespaceBindings = info.namespaces;
315
316             currentNamespacePrefix = null;
317           }
318       }
319   }
320
321   private String JavaDoc resolve(String JavaDoc prefix, boolean isAttribute)
322   {
323     if (isAttribute && prefix == null)
324       return "";
325     String JavaDoc uri = namespaceBindings.resolve(prefix);
326     if (uri != null)
327       return uri;
328     if (prefix != null)
329       {
330         error('e', "unknown namespace prefix '" + prefix + '\'');
331       }
332     return "";
333   }
334
335   void closeStartTag ()
336   {
337     if (attrCount < 0 || stringizingLevel > 0)
338       return;
339     inStartTag = false;
340     previous = 0;
341
342     if (attrLocalName != null) // Should only happen on erroneous input.
343
endAttribute();
344     NamespaceBinding outer = nesting == 0 ? NamespaceBinding.predefinedXML
345       : (NamespaceBinding) workStack[nesting-2];
346       
347     NamespaceBinding bindings = namespaceBindings;
348
349     // This first pass is to check that there are namespace declarations for
350
// each Symbol.
351
for (int i = 0; i <= attrCount; i++)
352       {
353         Object JavaDoc saved = workStack[nesting+i-1];
354         if (saved instanceof Symbol)
355           {
356             Symbol sym = (Symbol) saved;
357             String JavaDoc prefix = sym.getPrefix();
358             if (prefix == "")
359               prefix = null;
360             String JavaDoc uri = sym.getNamespaceURI();
361             if (uri == "")
362               uri = null;
363             if (i > 0 && prefix == null && uri == null)
364               continue;
365             boolean isOuter = false;
366             for (NamespaceBinding ns = bindings; ; ns = ns.next)
367               {
368                 if (ns == outer)
369                   isOuter = true;
370                 if (ns == null)
371                   {
372                     if (prefix != null || uri != null)
373                       bindings = findNamespaceBinding(prefix, uri, bindings);
374                     break;
375                   }
376                 if (ns.prefix == prefix)
377                   {
378                     if (ns.uri != uri)
379                       {
380                         if (isOuter)
381                           bindings = findNamespaceBinding(prefix, uri, bindings);
382                         else
383                           {
384                             // Try to find an alternative existing prefix:
385
String JavaDoc nprefix;
386                             for (NamespaceBinding ns2 = bindings;
387                                  ; ns2 = ns2.next)
388                               {
389                                 if (ns2 == null)
390                                   {
391                                     // We have to generate a new prefix.
392
for (int j = 1; ; j++)
393                                       {
394                                         nprefix = ("_ns_"+j).intern();
395                                         if (bindings.resolve(nprefix) == null)
396                                           break;
397                                       }
398                                     break;
399                                   }
400                                 if (ns2.uri == uri)
401                                   {
402                                     nprefix = ns2.prefix;
403                                     if (bindings.resolve(nprefix) == uri)
404                                       break;
405                                   }
406                               }
407                             bindings = findNamespaceBinding(nprefix, uri, bindings);
408                             String JavaDoc local = sym.getLocalName();
409                             if (uri == null)
410                               uri = "";
411                             workStack[nesting+i-1]
412                               = Symbol.make(uri, local, nprefix);
413                           }
414                       }
415                     break;
416                   }
417               }
418           }
419
420       }
421
422     for (int i = 0; i <= attrCount; i++)
423       {
424         Object JavaDoc saved = workStack[nesting+i-1];
425         MappingInfo info;
426         boolean isNsNode = false;
427         String JavaDoc prefix, uri, local;
428         if (saved instanceof MappingInfo || out == tlist)
429           {
430             if (saved instanceof MappingInfo)
431               {
432                 info = (MappingInfo) saved;
433                 prefix = info.prefix;
434                 local = info.local;
435                 if (i > 0
436                     && ((prefix == null && local == "xmlns")
437                         || prefix == "xmlns"))
438                   {
439                     isNsNode = true;
440                     uri = "(namespace-node)";
441                   }
442                 else
443                   uri = resolve(prefix, i > 0);
444               }
445             else
446               {
447                 Symbol symbol = (Symbol) saved;
448                 info = lookupTag(symbol);
449                 prefix = info.prefix;
450                 local = info.local;
451                 uri = symbol.getNamespaceURI();
452               }
453             int hash = info.tagHash;
454             int bucket = hash & mappingTableMask;
455
456             info = mappingTable[bucket];
457             MappingInfo tagMatch = null;
458             Object JavaDoc type;
459             for (;; info = info.nextInBucket)
460               {
461                 if (info == null)
462                   {
463                     info = tagMatch;
464                     info = new MappingInfo();
465                     info.tagHash = hash;
466                     info.prefix = prefix;
467                     info.local = local;
468                     info.nextInBucket = mappingTable[bucket];
469                     mappingTable[bucket] = info;
470                     info.uri = uri;
471                     info.qname = Symbol.make(uri, local, prefix);
472                     if (i == 0)
473                       {
474                         XName xname = new XName(info.qname, bindings);
475                         type = xname;
476                         info.type = xname;
477                         info.namespaces = bindings;
478                       }
479                     break;
480                   }
481                 if (info.tagHash == hash
482                     && info.local == local
483                     && info.prefix == prefix)
484                   {
485                     if (info.uri == null)
486                       {
487                         info.uri = uri;
488                         info.qname = Symbol.make(uri, local, prefix);
489                       }
490                     else if (info.uri != uri)
491                       continue;
492                     else if (info.qname == null)
493                       info.qname = Symbol.make(uri, local, prefix);
494                     if (i == 0)
495                       {
496                         if (info.namespaces == bindings
497                             || info.namespaces == null)
498                           {
499                             type = info.type;
500                             info.namespaces = bindings;
501                             if (type == null)
502                               {
503                                 XName xname = new XName(info.qname, bindings);
504                                 type = xname;
505                                 info.type = xname;
506                               }
507                             break;
508                           }
509                       }
510                     else
511                       {
512                         type = info.qname;
513                         break;
514                       }
515                   }
516               }
517             workStack[nesting+i-1] = info;
518           }
519         else
520           {
521             Symbol sym = (Symbol) saved;
522             uri = sym.getNamespaceURI();
523             local = sym.getLocalName();
524             info = null;
525           }
526
527         // Check for duplicated attribute names.
528
for (int j = 1; j < i; j++)
529           {
530             Object JavaDoc other = workStack[nesting+j-1];
531             Symbol osym;
532             if (other instanceof Symbol)
533               osym = (Symbol) other;
534             else if (other instanceof MappingInfo)
535               osym = ((MappingInfo) other).qname;
536             else
537               continue;
538             if (local == osym.getLocalPart()
539                 && uri == osym.getNamespaceURI())
540               {
541                 Object JavaDoc tag = workStack[nesting-1];
542                 if (tag instanceof MappingInfo)
543                   tag = ((MappingInfo) tag).qname;
544                 error('e', XMLFilter.duplicateAttributeMessage(osym, tag));
545               }
546           }
547
548     if (out == tlist)
549       {
550             Object JavaDoc type = i == 0 ? info.type : info.qname;
551         int index = info.index;
552         if (index <= 0
553         || tlist.objects[index] != type)
554           {
555         index = tlist.find(type);
556         info.index = index;
557           }
558         if (i == 0)
559               tlist.setGroupName(tlist.gapEnd, index);
560         else if (! isNsNode || namespacePrefixes)
561               tlist.setAttributeName(startIndexes[i-1], index);
562       }
563     else
564       {
565             Object JavaDoc type = info == null ? saved
566               : i == 0 ? info.type : info.qname;
567         if (i == 0)
568           out.beginGroup(type);
569         else if (! isNsNode || namespacePrefixes)
570           {
571         out.beginAttribute(type);
572         int start = startIndexes[i-1];
573         int end = i < attrCount ? startIndexes[i] : tlist.gapStart;
574         tlist.consumeIRange(start + TreeList.BEGIN_ATTRIBUTE_LONG_SIZE,
575                                     end - TreeList.END_ATTRIBUTE_SIZE,
576                                     out);
577         out.endAttribute();
578           }
579       }
580       }
581     for (int i = 1; i <= attrCount; i++)
582       workStack[nesting+i-1] = null; // For GC.
583
if (out != tlist)
584       {
585     base = out;
586     // Remove temporarily stored attributes.
587
tlist.clear();
588       }
589     attrCount = -1;
590   }
591
592   protected boolean checkWriteAtomic ()
593   {
594     previous = 0;
595     if (ignoringLevel > 0)
596       return false;
597     closeStartTag();
598     return true;
599  }
600
601   public void write (int v)
602   {
603     if (checkWriteAtomic())
604       base.write(v);
605   }
606
607   public void writeBoolean (boolean v)
608   {
609     if (checkWriteAtomic())
610       base.writeBoolean(v);
611   }
612
613   public void writeFloat (float v)
614   {
615     if (checkWriteAtomic())
616       base.writeFloat(v);
617   }
618
619   public void writeDouble (double v)
620   {
621     if (checkWriteAtomic())
622       base.writeDouble(v);
623   }
624
625   public void writeInt(int v)
626   {
627     if (checkWriteAtomic())
628       base.writeInt(v);
629   }
630
631   public void writeLong (long v)
632   {
633     if (checkWriteAtomic())
634       base.writeLong(v);
635   }
636
637   public void writeDocumentUri (Object JavaDoc uri)
638   {
639     if (nesting == 2 && base instanceof TreeList)
640       ((TreeList) base).writeDocumentUri(uri);
641   }
642
643   public void consume (SeqPosition position)
644   {
645     writePosition(position.sequence, position.ipos);
646   }
647
648   public void writePosition(AbstractSequence seq, int ipos)
649   {
650     if (ignoringLevel > 0)
651       return;
652     if (stringizingLevel > 0 && previous == SAW_WORD)
653       {
654         if (stringizingElementNesting < 0)
655           write(' ');
656         previous = 0;
657       }
658     seq.consumeNext(ipos, this);
659     if (stringizingLevel > 0 && stringizingElementNesting < 0)
660       previous = SAW_WORD;
661   }
662
663   /** If v is a node, make a copy of it. */
664   public void writeObject(Object JavaDoc v)
665   {
666     if (ignoringLevel > 0)
667       return;
668     if (v instanceof SeqPosition)
669       {
670     SeqPosition pos = (SeqPosition) v;
671     writePosition(pos.sequence, pos.getPos());
672       }
673     else if (v instanceof TreeList)
674       ((TreeList) v).consume(this);
675     else if (v instanceof Keyword)
676       {
677         Keyword k = (Keyword) v;
678         beginAttribute(k.asSymbol());
679         previous = SAW_KEYWORD;
680       }
681     else
682       {
683         closeStartTag();
684         if (v instanceof UnescapedData)
685           {
686             base.writeObject(v);
687             previous = 0;
688           }
689         else
690           {
691             if (previous == SAW_WORD)
692               write(' ');
693             TextUtils.textValue(v, this); // Atomize.
694
previous = SAW_WORD;
695           }
696       }
697   }
698
699   public XMLFilter (Consumer out)
700   {
701     this.base = out;
702     this.out = out;
703     if (out instanceof NodeTree)
704       this.tlist = (NodeTree) out;
705     else
706       tlist = new TreeList(); // just for temporary storage
707

708     namespaceBindings = NamespaceBinding.predefinedXML;
709   }
710
711   /** Process raw text. */
712   public void write (char[] data, int start, int length)
713   {
714     if (length == 0)
715       writeJoiner();
716     else if (checkWriteAtomic())
717       base.write(data, start, length);
718   }
719
720   public void write(String JavaDoc str)
721   {
722     write(str, 0, str.length());
723   }
724
725   /* #ifdef use:java.lang.CharSequence */
726   public void write(CharSequence JavaDoc str, int start, int length)
727   /* #else */
728   // public void write(String str, int start, int length)
729
/* #endif */
730   {
731     if (length == 0)
732       writeJoiner();
733     else if (checkWriteAtomic())
734       base.write(str, start, length);
735   }
736
737   public void textFromParser (char[] data, int start, int length)
738   {
739     // Skip whitespace not in an element.
740
// This works semi-accidentally, since XMLParser doesn't call beginDocument
741
// which otherwise would increment nesting. Perhaps shipping toplevel
742
// whitespace should be handled internally in XMLParser. FIXME.
743
if (nesting == 0)
744       {
745         for (int i = 0; ; i++)
746           {
747             if (i == length)
748               return;
749             if (! Character.isWhitespace(data[start+i]))
750               break;
751           }
752       }
753     else if (length > 0)
754       {
755         if (previous == SAW_CR)
756           {
757             char ch = data[start];
758             previous = 0;
759             if (ch == '\n' || ch == 0x85)
760               {
761                 start++;
762                 length--;
763               }
764           }
765         if (! checkWriteAtomic())
766           return;
767
768         // The complication here is line-end normalization,
769
// with minimal overhead.
770
int limit = start + length;
771         TreeList blist = base instanceof TreeList ? (TreeList) base : null;
772       outerLoop:
773         for (int i = start; ; i++)
774           {
775             char ch;
776             // We optimize the case that base instanceof TreeList.
777
if (blist != null)
778               {
779                 blist.ensureSpace(limit-i);
780                 char[] bdata = blist.data;
781                 int gapStart = blist.gapStart;
782                 for (;; i++)
783                   {
784                     if (i >= limit)
785                       {
786                         blist.gapStart = gapStart;
787                         break outerLoop;
788                       }
789                     ch = data[i];
790                     if (ch != '\r' && ch < 0x85) // Quick trest first.
791
bdata[gapStart++] = ch;
792                     else if (ch > TreeList.MAX_CHAR_SHORT)
793                       {
794                         blist.gapStart = gapStart;
795                         blist.write(ch);
796                         continue outerLoop;
797                       }
798                     else if (ch == '\r' || ch == 0x85 || ch == 0x2028)
799                       {
800                         blist.gapStart = gapStart;
801                         start = i+1;
802                         break;
803                       }
804                     else
805                       bdata[gapStart++] = ch;
806                   }
807               }
808             else if (i >= limit)
809               ch = 0;
810             else
811               {
812                 ch = data[i];
813                 if (ch >= ' ' && ch < 0x85) // Quick(er) test.
814
continue;
815                 if (ch != '\r' && ch != 0x85 && ch != 0x2028)
816                   continue;
817               }
818             if (i > start)
819               {
820                 base.write(data, start, i-start);
821               }
822             if (i >= limit)
823               break;
824             start = i+1;
825             if (ch == '\r')
826               {
827                 if (start < limit)
828                   {
829                     ch = data[i+1];
830                     if (ch == '\n')
831                       continue; // Handled next iteration.
832
if (ch == 0x85)
833                       i++;
834                   }
835                 else
836                   previous = SAW_CR;
837               }
838             base.write('\n');
839           }
840       }
841   }
842
843   public void write (String JavaDoc str, int start, int length)
844   {
845     if (length == 0)
846       writeJoiner();
847     else if (checkWriteAtomic())
848       base.write(str, start, length);
849   }
850
851   protected void writeJoiner ()
852   {
853     previous = 0;
854     if (ignoringLevel == 0)
855       ((TreeList) base).writeJoiner();
856   }
857
858   /** Process a CDATA section.
859    * The data (starting at start for length char).
860    * Does not include the delimiters (i.e. {@code "<![CDATA["}
861    * and {@code "]]>"} are excluded). */

862   public void writeCDATA(char[] data, int start, int length)
863   {
864     if (checkWriteAtomic())
865       {
866         if (base instanceof XConsumer)
867           ((XConsumer) base).writeCDATA(data, start, length);
868         else
869           write(data, start, length);
870       }
871   }
872
873   protected void beginGroupCommon ()
874   {
875     closeStartTag();
876     if (stringizingLevel == 0)
877       {
878         ensureSpaceInWorkStack(nesting);
879         workStack[nesting] = namespaceBindings;
880         tlist.beginGroup(0);
881         base = tlist;
882         attrCount = 0;
883       }
884     else
885       {
886         if (previous == SAW_WORD && stringizingElementNesting < 0)
887           write(' ');
888         previous = 0;
889         if (stringizingElementNesting < 0)
890           stringizingElementNesting = nesting;
891       }
892     nesting += 2;
893   }
894
895   /** Process a start tag, with the given element name. */
896   public void emitBeginElement(char[] data, int start, int count)
897   {
898     closeStartTag();
899     MappingInfo info = lookupTag(data, start, count);
900     beginGroupCommon();
901     ensureSpaceInWorkStack(nesting-1);
902     workStack[nesting-1] = info;
903   }
904
905   public void beginGroup(Object JavaDoc type)
906   {
907     beginGroupCommon();
908     if (stringizingLevel == 0)
909       {
910         ensureSpaceInWorkStack(nesting-1);
911         workStack[nesting-1] = type;
912         if (copyNamespacesMode == 0)
913           namespaceBindings = NamespaceBinding.predefinedXML;
914         else if (copyNamespacesMode == COPY_NAMESPACES_PRESERVE
915                  || nesting == 2)
916           namespaceBindings
917             = (type instanceof XName ? ((XName) type).getNamespaceNodes()
918                : NamespaceBinding.predefinedXML);
919         else
920           {
921             NamespaceBinding inherited;
922             // Start at 2, since workStack[0] just saves the predefinedXML.
923
for (int i = 2; ; i += 2)
924               {
925                 if (i == nesting)
926                   {
927                     inherited = null;
928                     break;
929                   }
930                 if (workStack[i+1] != null)
931                   { // Found an element, as opposed to a document.
932
inherited = (NamespaceBinding) workStack[i];
933                     break;
934                   }
935               }
936             if (inherited == null)
937               {
938                 // This is the outer-most group.
939
namespaceBindings
940                   = (type instanceof XName ? ((XName) type).getNamespaceNodes()
941                      : NamespaceBinding.predefinedXML);
942               }
943             else if (copyNamespacesMode == COPY_NAMESPACES_INHERIT)
944               namespaceBindings = inherited;
945             else if (type instanceof XName)
946               {
947                 NamespaceBinding preserved = ((XName) type).getNamespaceNodes();
948                 NamespaceBinding join = NamespaceBinding.commonAncestor(inherited, preserved);
949                 if (join == inherited)
950                   namespaceBindings = preserved;
951                 else
952                   namespaceBindings = mergeHelper(inherited, preserved);
953               }
954             else
955               namespaceBindings = inherited;
956           }
957       }
958   }
959
960   private NamespaceBinding mergeHelper (NamespaceBinding list,
961                                         NamespaceBinding node)
962   {
963     if (node == NamespaceBinding.predefinedXML)
964       return list;
965     list = mergeHelper(list, node.next);
966     String JavaDoc uri = node.uri;
967     if (list == null)
968       {
969     if (uri == null)
970       return list;
971     list = NamespaceBinding.predefinedXML;
972       }
973     String JavaDoc prefix = node.prefix;
974     String JavaDoc found = list.resolve(prefix);
975     if (found == null ? uri == null : found.equals(uri))
976       return list;
977     return findNamespaceBinding(prefix, uri, list);
978   }
979
980   private boolean beginAttributeCommon()
981   {
982     if (stringizingElementNesting >= 0)
983       ignoringLevel++;
984     if (stringizingLevel++ > 0)
985       return false;
986
987     if (attrCount < 0) // A disembodied attribute.
988
attrCount = 0;
989     ensureSpaceInWorkStack(nesting+attrCount);
990     ensureSpaceInStartIndexes(attrCount);
991     startIndexes[attrCount] = tlist.gapStart;
992     attrCount++;
993     return true;
994   }
995
996   public void beginAttribute (Object JavaDoc attrType)
997   {
998     previous = 0;
999     if (attrType instanceof Symbol)
1000      {
1001        Symbol sym = (Symbol) attrType;
1002        String JavaDoc local = sym.getLocalPart();
1003        attrLocalName = local;
1004        attrPrefix = sym.getPrefix();
1005        String JavaDoc uri = sym.getNamespaceURI();
1006        if (uri == "http://www.w3.org/2000/xmlns/"
1007            || (uri == "" && local == "xmlns"))
1008          error('e', "arttribute name cannot be 'xmlns' or in xmlns namespace");
1009      }
1010    if (nesting == 2 && workStack[1] == null)
1011      error('e', "attribute not allowed at document level");
1012    if (attrCount < 0 && nesting > 0)
1013      error('e', "attribute '"+attrType+"' follows non-attribute content");
1014    if (! beginAttributeCommon())
1015      return;
1016    workStack[nesting+attrCount-1] = attrType;
1017    if (nesting == 0)
1018      base.beginAttribute(attrType);
1019    else
1020      tlist.beginAttribute(0);
1021  }
1022
1023  /** Process an attribute, with the given attribute name.
1024   * The attribute value is given using {@code write}.
1025   * The value is terminated by either another emitBeginAttribute
1026   * or an emitEndAttributes.
1027   */

1028  public void emitBeginAttribute(char[] data, int start, int count)
1029  {
1030    if (attrLocalName != null)
1031      endAttribute();
1032    if (! beginAttributeCommon())
1033      return;
1034
1035    MappingInfo info = lookupTag(data, start, count);
1036    workStack[nesting+attrCount-1] = info;
1037    String JavaDoc prefix = info.prefix;
1038    String JavaDoc local = info.local;
1039    attrLocalName = local;
1040    attrPrefix = prefix;
1041    if (prefix != null)
1042      {
1043    if (prefix == "xmlns")
1044      {
1045            currentNamespacePrefix = local;
1046      }
1047      }
1048    else
1049      {
1050    if (local == "xmlns" && prefix == null)
1051      {
1052            currentNamespacePrefix = "";
1053      }
1054      }
1055    if (currentNamespacePrefix == null || namespacePrefixes)
1056      tlist.beginAttribute(0);
1057  }
1058
1059  /** Process the end of a start tag.
1060   * There are no more attributes. */

1061  public void emitEndAttributes()
1062  {
1063    if (attrLocalName != null)
1064      endAttribute();
1065  }
1066
1067  /** Process an end tag.
1068   * An abbreviated tag (such as {@code '<br/>'}) has a name==null.
1069   */

1070  public void emitEndElement(char[] data, int start, int length)
1071  {
1072    if (attrLocalName != null)
1073      {
1074    error('e', "unclosed attribute"); // FIXME
1075
endAttribute();
1076      }
1077    if (nesting == 0)
1078      {
1079    error('e', "unmatched end element"); // FIXME
1080
return;
1081      }
1082    if (data != null)
1083      {
1084        MappingInfo info = lookupTag(data, start, length);
1085        Object JavaDoc old = workStack[nesting-1];
1086        if (old instanceof MappingInfo && ! mismatchReported)
1087          {
1088            MappingInfo mold = (MappingInfo) old;
1089            if (info.local != mold.local || info.prefix != mold.prefix)
1090              {
1091                StringBuffer JavaDoc sbuf = new StringBuffer JavaDoc("</");
1092                sbuf.append(data, start, length);
1093                sbuf.append("> matching <");
1094                String JavaDoc oldPrefix = mold.prefix;
1095                if (oldPrefix != null)
1096                  {
1097                    sbuf.append(oldPrefix);
1098                    sbuf.append(':');
1099                  }
1100                sbuf.append(mold.local);
1101                sbuf.append('>');
1102                error('e', sbuf.toString());
1103                mismatchReported = true;
1104              }
1105          }
1106      }
1107    closeStartTag();
1108    if (nesting <= 0)
1109      return; // Only if error.
1110
endGroup();
1111  }
1112
1113  public void endGroup ()
1114  {
1115    closeStartTag();
1116    nesting -= 2;
1117    previous = 0;
1118    if (stringizingLevel == 0)
1119      {
1120        namespaceBindings = (NamespaceBinding) workStack[nesting];
1121        workStack[nesting] = null;
1122        workStack[nesting+1] = null;
1123        base.endGroup();
1124      }
1125    else if (stringizingElementNesting == nesting)
1126      {
1127        stringizingElementNesting = -1;
1128        previous = SAW_WORD;
1129      }
1130    /*
1131    if (nesting == 0)
1132      {
1133        workStack = null;
1134        attrIndexes = null;
1135      }
1136    */

1137  }
1138
1139  /** Process an entity reference.
1140   * The entity name is given.
1141   * This handles the predefined entities, such as "&lt;" and "&quot;".
1142   */

1143  public void emitEntityReference(char[] name, int start, int length)
1144  {
1145    char c0 = name[start];
1146    char ch = '?';
1147    if (length == 2 && name[start+1] == 't')
1148      {
1149    
1150    if (c0 == 'l')
1151      ch = '<';
1152    else if (c0 == 'g')
1153      ch = '>';
1154      }
1155    else if (length == 3)
1156      {
1157    if (c0 == 'a' && name[start+1] == 'm' && name[start+2] == 'p')
1158      ch = '&';
1159      }
1160    else if (length == 4)
1161      {
1162    char c1 = name[start+1];
1163    char c2 = name[start+2];
1164    char c3 = name[start+3];
1165    if (c0 == 'q' && c1 == 'u' && c2 == 'o' && c3 == 't')
1166      ch = '"';
1167    else if (c0 == 'a' && c1 == 'p' && c2 == 'o' && c3 == 's')
1168      ch = '\'';
1169      }
1170    write(ch);
1171  }
1172
1173  /** Process a character entity reference.
1174   * The string encoding of the character (e.g. "xFF" or "255") is given,
1175   * as well as the character value. */

1176  public void emitCharacterReference(int value, char[] name, int start, int length)
1177  {
1178    if (value >= 0x10000)
1179      Char.print(value, this);
1180    else
1181      write(value);
1182  }
1183
1184  protected void checkValidComment (char[] chars, int offset, int length)
1185  {
1186    int i = length;
1187    boolean sawHyphen = true;
1188    while (--i >= 0)
1189      {
1190        boolean curHyphen = chars[offset+i] == '-';
1191        if (sawHyphen && curHyphen)
1192          {
1193            error('e', "consecutive or final hyphen in XML comment");
1194            break;
1195          }
1196        sawHyphen = curHyphen;
1197      }
1198  }
1199
1200  /** Process a comment.
1201   * The data (starting at start for length chars).
1202   * Does not include the delimiters (i.e. "<!--" and "-->" are excluded). */

1203  public void writeComment (char[] chars, int start, int length)
1204  {
1205    checkValidComment(chars, start, length);
1206    commentFromParser(chars, start, length);
1207  }
1208
1209  /** Process a comment, when called from an XML parser.
1210   * The data (starting at start for length chars).
1211   * Does not include the delimiters (i.e. "<!--" and "-->" are excluded). */

1212  public void commentFromParser (char[] chars, int start, int length)
1213  {
1214    if (stringizingLevel == 0)
1215      {
1216        closeStartTag();
1217        if (base instanceof XConsumer)
1218          ((XConsumer) base).writeComment(chars, start, length);
1219      }
1220    else if (stringizingElementNesting < 0)
1221      base.write(chars, start, length);
1222  }
1223
1224  public void writeProcessingInstruction(String JavaDoc target, char[] content,
1225                     int offset, int length)
1226  {
1227    for (int i = offset+length; --i >= offset; )
1228      {
1229        char ch = content[i];
1230        while (ch == '>' && --i >= offset)
1231          {
1232            ch = content[i];
1233            if (ch == '?')
1234              {
1235                error('e', "'?>' is not allowed in a processing-instruction");
1236                break;
1237              }
1238          }
1239      }
1240
1241    if ("xml".equalsIgnoreCase(target))
1242      error('e',
1243            "processing-instruction target may not be 'xml' (ignoring case)");
1244    if (! XName.isName(target, true))
1245      error('e',
1246            "processing-instruction target '"+target+"' is not a valid Name");
1247
1248    processingInstructionCommon(target, content, offset, length);
1249  }
1250
1251  void processingInstructionCommon (String JavaDoc target, char[] content,
1252                                    int offset, int length)
1253  {
1254    if (stringizingLevel == 0)
1255      {
1256        closeStartTag();
1257        if (base instanceof XConsumer)
1258          ((XConsumer) base)
1259            .writeProcessingInstruction(target, content, offset, length);
1260      }
1261    else if (stringizingElementNesting < 0)
1262      base.write(content, offset, length);
1263  }
1264
1265  /** Process a processing instruction. */
1266  public void processingInstructionFromParser(char[] buffer,
1267                                        int tstart, int tlength,
1268                                        int dstart, int dlength)
1269  {
1270    // Skip XML declaration.
1271
if (nesting == 0 && tlength == 3
1272        && buffer[tstart] == 'x'
1273        && buffer[tstart+1] == 'm'
1274        && buffer[tstart+2] == 'l')
1275      return;
1276    String JavaDoc target = new String JavaDoc(buffer, tstart, tlength);
1277    processingInstructionCommon(target, buffer, dstart, dlength);
1278  }
1279
1280  public void beginDocument()
1281  {
1282    closeStartTag();
1283    if (stringizingLevel > 0)
1284      writeJoiner();
1285    // We need to increment nesting so that endDocument can decrement it.
1286
else
1287      {
1288        if (nesting == 0)
1289          base.beginDocument();
1290        else
1291          writeJoiner();
1292        ensureSpaceInWorkStack(nesting);
1293        workStack[nesting] = namespaceBindings;
1294        // The following should be redundant, but just in case ...
1295
// Having workStack[nesting+1] be null identifies that nesting
1296
// level as being a document rather than an element.
1297
workStack[nesting+1] = null;
1298        nesting += 2;
1299      }
1300  }
1301
1302  public void endDocument ()
1303  {
1304    if (stringizingLevel > 0)
1305      {
1306        writeJoiner();
1307        return;
1308      }
1309    nesting -= 2;
1310    namespaceBindings = (NamespaceBinding) workStack[nesting];
1311    workStack[nesting] = null;
1312    workStack[nesting+1] = null;
1313    if (nesting == 0)
1314      base.endDocument();
1315    else
1316      writeJoiner();
1317    /*
1318    if (nesting == 0)
1319      {
1320        workStack = null;
1321        attrIndexes = null;
1322      }
1323    */

1324  }
1325
1326  /** Process a DOCTYPE declaration. */
1327  public void emitDoctypeDecl(char[] buffer,
1328                              int target, int tlength,
1329                              int data, int dlength)
1330  {
1331    // FIXME?
1332
}
1333
1334  public void beginEntity (Object JavaDoc baseUri)
1335  {
1336    if (base instanceof XConsumer)
1337      ((XConsumer) base).beginEntity(baseUri);
1338  }
1339
1340  public void endEntity ()
1341  {
1342    if (base instanceof XConsumer)
1343      ((XConsumer) base).endEntity();
1344  }
1345
1346  /* #ifdef JAVA5 */
1347  // public XMLFilter append (char c)
1348
// {
1349
// write(c);
1350
// return this;
1351
// }
1352

1353  // public XMLFilter append (CharSequence csq)
1354
// {
1355
// if (csq == null)
1356
// csq = "null";
1357
// append(csq, 0, csq.length());
1358
// return this;
1359
// }
1360

1361  // public XMLFilter append (CharSequence csq, int start, int end)
1362
// {
1363
// if (csq == null)
1364
// csq = "null";
1365
// write(csq, start, end-start);
1366
// return this;
1367
// }
1368
/* #endif */
1369
1370  MappingInfo lookupTag (Symbol qname)
1371  {
1372    String JavaDoc local = qname.getLocalPart();
1373    String JavaDoc prefix = qname.getPrefix();
1374    if (prefix == "")
1375      prefix = null;
1376    String JavaDoc uri = qname.getNamespaceURI();
1377    int hash = MappingInfo.hash(prefix, local);
1378    int index = hash & mappingTableMask;
1379    MappingInfo first = mappingTable[index];
1380    MappingInfo info = first;
1381    for (;;)
1382      {
1383    if (info == null)
1384      {
1385        // No match found - create a new MappingInfo and Strings.
1386
info = new MappingInfo();
1387            info.qname = qname;
1388            info.prefix = prefix;
1389            info.uri = uri;
1390            info.local = local;
1391        info.tagHash = hash;
1392        info.nextInBucket = first;
1393        mappingTable[index] = first;
1394        return info;
1395      }
1396        if (qname == info.qname)
1397          return info;
1398        if (local == info.local && info.qname == null
1399            && (uri == info.uri || info.uri == null)
1400            && prefix == info.prefix)
1401          {
1402            info.uri = uri;
1403            info.qname = qname;
1404            return info;
1405          }
1406    info = info.nextInBucket;
1407      }
1408  }
1409
1410  /** Look up an attribute/element tag (a QName as a lexical string
1411   * before namespace resolution), and return a MappingInfo with the
1412   * tagHash, prefix, and local fields set.
1413   * The trick is to avoid allocating a new String for each element or
1414   * attribute node we see, but only allocate a new String when we see a
1415   * tag we haven't seen. So we calculate the hash code using the
1416   * characters in the array, rather than using String's hashCode.
1417   */

1418  MappingInfo lookupTag (char[] data, int start, int length)
1419  {
1420    // Calculate hash code. Also note presence+position of ':'.
1421
int hash = 0;
1422    int prefixHash = 0;
1423    int colon = -1;
1424    for (int i = 0; i < length; i++)
1425      {
1426        char ch = data[start+i];
1427        if (ch == ':' && colon < 0)
1428          {
1429            colon = i;
1430            prefixHash = hash;
1431            hash = 0;
1432          }
1433        else
1434          hash = 31 * hash + ch;
1435      }
1436    hash = prefixHash ^ hash;
1437    int index = hash & mappingTableMask;
1438    MappingInfo first = mappingTable[index];
1439    MappingInfo info = first;
1440    for (;;)
1441      {
1442    if (info == null)
1443      {
1444        // No match found - create a new MappingInfo and Strings.
1445
info = new MappingInfo();
1446        info.tagHash = hash;
1447        if (colon >= 0)
1448          {
1449        info.prefix = new String JavaDoc(data, start, colon).intern();
1450                colon++;
1451                int lstart = start+colon;
1452        info.local = new String JavaDoc(data, lstart, length-colon).intern();
1453          }
1454        else
1455          {
1456        info.prefix = null;
1457        info.local = new String JavaDoc(data, start, length).intern();
1458          }
1459        info.nextInBucket = first;
1460        mappingTable[index] = first;
1461        return info;
1462      }
1463    if (hash == info.tagHash
1464        && info.match(data, start, length))
1465      return info;
1466    info = info.nextInBucket;
1467      }
1468  }
1469
1470  private void ensureSpaceInWorkStack (int oldSize)
1471  {
1472    if (workStack == null)
1473      {
1474        workStack = new Object JavaDoc[20];
1475      }
1476    else if (oldSize >= workStack.length)
1477      {
1478        Object JavaDoc[] tmpn = new Object JavaDoc[2*workStack.length];
1479        System.arraycopy(workStack, 0, tmpn, 0, oldSize);
1480        workStack = tmpn;
1481      }
1482  }
1483
1484  private void ensureSpaceInStartIndexes (int oldSize)
1485  {
1486    if (startIndexes == null)
1487      {
1488        startIndexes = new int[20];
1489      }
1490    else if (oldSize >= startIndexes.length)
1491      {
1492        int[] tmpn = new int[2*startIndexes.length];
1493        System.arraycopy(startIndexes, 0, tmpn, 0, oldSize);
1494        startIndexes = tmpn;
1495      }
1496  }
1497
1498  public static String JavaDoc
1499  duplicateAttributeMessage (Symbol attrSymbol, Object JavaDoc groupName)
1500  {
1501    StringBuffer JavaDoc sbuf = new StringBuffer JavaDoc("duplicate attribute: ");
1502    String JavaDoc uri = attrSymbol.getNamespaceURI();
1503    if (uri != null && uri.length() > 0)
1504      {
1505        sbuf.append('{');
1506        sbuf.append('}');
1507        sbuf.append(uri);
1508      }
1509    sbuf.append(attrSymbol.getLocalPart());
1510    if (groupName != null)
1511      {
1512        sbuf.append(" in <");
1513        sbuf.append(groupName);
1514        sbuf.append('>');
1515      }
1516    return sbuf.toString();
1517  }
1518
1519  public void error(char severity, String JavaDoc message)
1520  {
1521    if (messages == null)
1522      throw new RuntimeException JavaDoc(message);
1523    else if (locator != null)
1524      messages.error(severity, locator, message);
1525    else
1526      messages.error(severity, message);
1527  }
1528
1529  public boolean ignoring()
1530  {
1531    return ignoringLevel > 0;
1532  }
1533}
1534
1535final class MappingInfo
1536{
1537  /** Next in same hash bucket. */
1538  MappingInfo nextInBucket;
1539
1540  // maybe future: MappingInfo prevInBucket;
1541
// maybe future: MappingInfo nextForPrefix;
1542

1543  /** The cached value of {@code hash(prefix, local)}. */
1544  int tagHash;
1545
1546  /** The prefix part of tag: - the part before the colon.
1547   * It is null if there is no colon in tag. Otherwise it is interned. */

1548  String JavaDoc prefix;
1549
1550  /** The local name part of tag: - the part after the colon.
1551   * It is interned. */

1552  String JavaDoc local;
1553
1554  /** The namespace URI.
1555   * The value null means "unknown". */

1556  String JavaDoc uri;
1557
1558  /** The Symbol for the resolved QName.
1559   * If non-null, it must be the case that {@code uri!= null}, and
1560   * {@code qname==Symbol.make(uri, local, prefix==null?"":prefix)}.
1561   */

1562  Symbol qname;
1563
1564  NamespaceBinding namespaces;
1565
1566  /** An XName matching the other fields.
1567   * If non-null, we must have {@code qname!=null}, {@code namespaces!=null},
1568   * {@code type.namespaceNodes == namespaces}, and
1569   * {@code type.equals(qname)}. */

1570  XName type;
1571
1572  /** If non-negative: An index into a TreeList objects array. */
1573  int index = -1;
1574
1575  static int hash (String JavaDoc prefix, String JavaDoc local)
1576  {
1577    int hash = local.hashCode();
1578    if (prefix != null)
1579      hash ^= prefix.hashCode();
1580    return hash;
1581  }
1582
1583  /** Hash a QName, handling an optional prefix+colon. */
1584  static int hash (char[] data, int start, int length)
1585  {
1586    int hash = 0;
1587    int prefixHash = 0;
1588    int colonPos = -1;
1589    for (int i = 0; i < length; i++)
1590      {
1591        char ch = data[start+i];
1592        if (ch == ':' && colonPos < 0)
1593          {
1594            colonPos = i;
1595            prefixHash = hash;
1596            hash = 0;
1597          }
1598        else
1599          hash = 31 * hash + ch;
1600      }
1601    return prefixHash ^ hash;
1602  }
1603
1604  /** Match {@code "[prefix:]length"} against {@code new String(data, start, next)}. */
1605  boolean match (char[] data, int start, int length)
1606  {
1607    if (prefix != null)
1608      {
1609        int localLength = local.length();
1610        int prefixLength = prefix.length();
1611        return length == prefixLength + 1 + localLength
1612          && data[prefixLength] == ':'
1613          && equals(prefix, data, start, prefixLength)
1614          && equals(local, data, start+prefixLength+1, localLength);
1615      }
1616    else
1617      return equals(local, data, start, length);
1618  }
1619
1620  /** An optimization of {@code sbuf.toString().equals(tag)}.
1621  */

1622  static boolean equals (String JavaDoc tag, StringBuffer JavaDoc sbuf)
1623  {
1624    int length = sbuf.length();
1625    if (tag.length () != length)
1626      return false;
1627    for (int i = 0; i < length; i++)
1628      if (sbuf.charAt(i) != tag.charAt(i))
1629    return false;
1630    return true;
1631  }
1632
1633  static boolean equals (String JavaDoc tag, char[] data, int start, int length)
1634  {
1635    if (tag.length () != length)
1636      return false;
1637    for (int i = 0; i < length; i++)
1638      if (data[start+i] != tag.charAt(i))
1639    return false;
1640    return true;
1641  }
1642}
1643
Popular Tags