1 /*
2 * JBoss, Home of Professional Open Source
3 * Copyright 2005, JBoss Inc., and individual contributors as indicated
4 * by the @authors tag. See the copyright.txt in the distribution for a
5 * full listing of individual contributors.
6 *
7 * This is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as
9 * published by the Free Software Foundation; either version 2.1 of
10 * the License, or (at your option) any later version.
11 *
12 * This software is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this software; if not, write to the Free
19 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
21 */
22 package org.jboss.mq.xml;
23
24 import java.util.Enumeration;
25 import java.util.Hashtable;
26 import java.util.Vector;
27
28 import org.xml.sax.Attributes;
29
30 /**
31 * XElement provides an interface to an XML element. An XElement represents an
32 * XML element which contains: <br>
33 *
34 * <ul>
35 * <li> Name (required)
36 * <li> Attributes (optional)
37 * <li> character data (optional)
38 * <li> other elements (optional)
39 * </ul>
40 * <p>
41 *
42 * It is important to understand the diffrence between an "field" XElement and
43 * a non "field" XElement. If an XElement does not contain any sub elements, it
44 * is considered a "field" XElement. The <code>getField(String)</code> and
45 * <code>getValue()</code> functions will throw an XElementException if they
46 * are used on non "attribute" objects. This give you a little bit type
47 * checking (You'll get an exception if you try to access the character data of
48 * an element that has sub elements). <p>
49 *
50 * If XElement is not an field, then it contains other XElements and optionaly
51 * some text. The text data can be accessed with the <code>getText()</code>
52 * method and the sub elements with the <code>iterator()</code> or with <code>
53 * getElementXXX()</code> fuctions. Since XML and thus XElements provide a tree
54 * type data structure, traversing the tree to access leaf data can be
55 * cumbersom if you have a 'deep' tree. For example, you may have to do: <code>
56 * element.getElement("tree").getElement("branch").getElement("leaf")</code>
57 * access a XElement 3 levels deep in the tree. To access deep elements easier,
58 * XElements lets you use 'reletive' names to access deep elements. Using
59 * reletive names, you could access the same element in previous example doing:
60 * <code>element.getElement("tree/branch/leaf")</code> When using relative
61 * names, keep in mind that "." will get the current XElement, and ".." will
62 * get parent XElement. Very similar to how URLs work.
63 *
64 * @author Hiram Chirino (Cojonudo14@hotmail.com)
65 * @created August 16, 2001
66 * @version $Revision: 55382 $
67 */
68 public class XElement {
69
70 private XElement parent = null;
71 private String name = null;
72 private Hashtable metadata = new Hashtable();
73 private Vector contents = new Vector();
74 private final static String nl = System.getProperty( "line.separator" );
75
76 /**
77 * Constructs an empty object.
78 *
79 * @param objectName the tag or element name that this object represents.
80 */
81 public XElement( String objectName ) {
82 if ( objectName == null ) {
83 throw new NullPointerException();
84 }
85 name = objectName;
86 contents.addElement( new StringBuffer() );
87 }
88
89 /**
90 * Constructs an XElement with it's parent and metatags set.
91 *
92 * @param objectName the tag or element name that this object represents.
93 * @param atts Description of Parameter
94 */
95 public XElement( String objectName, Attributes atts ) {
96 if ( objectName == null ) {
97 throw new NullPointerException();
98 }
99 if ( atts == null ) {
100 throw new NullPointerException();
101 }
102 name = objectName;
103 contents.addElement( new StringBuffer() );
104 for ( int i = 0; i < atts.getLength(); i++ ) {
105 metadata.put( atts.getQName( i ), atts.getValue( i ) );
106 //metadata.put( atts.getLocalName( i ), atts.getValue( i ) );
107 }
108 }
109
110 /**
111 * Sets/Adds a metatag value Only metatags whose value is not empty will
112 * display when the <code>toString()</code> methods is called.
113 *
114 * @param key the name of the metatag
115 * @param value the value to set the metatag to.
116 */
117 public void setAttribute( String key, String value ) {
118 metadata.put( key, value );
119 }
120
121 /**
122 * Sets the object name
123 *
124 * @param newName
125 */
126 public void setName( String newName ) {
127 name = newName;
128 }
129
130 /**
131 * Gets the character data that was within this object. This fuction can
132 * only be used on objects that are attributes.
133 *
134 * @param value The new Value value
135 * @returns the character data contained within this
136 * object.
137 * @throws XElementException if the object was not an attribute object
138 */
139 public void setValue( String value )
140 throws XElementException {
141 if ( !isField() ) {
142 throw new XElementException( "" + getName() + " is not an attribute object" );
143 }
144 contents.setElementAt( new StringBuffer( value ), 0 );
145 }
146
147
148 /**
149 * Sets/Adds a attribute
150 *
151 * @param key the name of the attribute element
152 * @param value the value to set the attribute to.
153 * @exception XElementException Description of Exception
154 */
155 public void setField( String key, String value )
156 throws XElementException {
157 getElement( key ).setValue( value );
158 }
159
160 /**
161 * Returns the value of a meta data value.
162 *
163 * @param key Description of Parameter
164 * @return The Attribute value
165 * @returns the value of the metadata item, or "" if the item has not
166 * been set.
167 */
168 public String getAttribute( String key ) {
169 String t = ( String )metadata.get( key );
170 if ( t == null ) {
171 return "";
172 }
173 return t;
174 }
175
176 /**
177 * Returns the element name (tag name) of this XElement
178 *
179 * @return The Name value
180 * @returns
181 */
182 public java.lang.String getName() {
183 return name;
184 }
185
186 /**
187 * Get the parent of this object, or the object the contains this one.
188 *
189 * @return The Parent value
190 * @returns null if this object is not contained by any other XElement.
191 */
192 public XElement getParent() {
193 return parent;
194 }
195
196 /**
197 * Gets the TRIMMED character data that was within this object. This differs
198 * from getValue() in that:
199 * <UL>
200 * <LI> this fuction will work on attribute and non attribute XElements
201 *
202 * <LI> it will trim both ends of the character data before returning it.
203 *
204 * </UL>
205 *
206 *
207 * @return The Text value
208 * @returns the character data contained within this object.
209 */
210 public String getText() {
211 return contents.elementAt( 0 ).toString().trim();
212 }
213
214 /**
215 * Gets the character data that was within this object. This fuction can
216 * only be used on objects that are attributes.
217 *
218 * @return The Value value
219 * @returns the character data contained within this
220 * object.
221 * @throws XElementException if the object was not an attribute object
222 */
223 public String getValue()
224 throws XElementException {
225 if ( !isField() ) {
226 throw new XElementException( "" + getName() + " is not an attribute object" );
227 }
228 return contents.elementAt( 0 ).toString();
229 }
230
231 /**
232 * Returns the first object contained in this object named relativeName.
233 *
234 * @param relativeName The name of the object to find
235 * @return The Element value
236 * @returns the XElement named relativeName
237 * @throws XElementException if the object could not be found.
238 */
239 public XElement getElement( String relativeName )
240 throws XElementException {
241 if ( relativeName == null ) {
242 throw new NullPointerException();
243 }
244
245 String names[] = {null, relativeName};
246
247 // Does the name have a "/" in it?
248 String split[] = splitFront( relativeName, "/" );
249 if ( split != null ) {
250
251 // was it an absolute name? (started with a '/')
252 if ( split[0].length() == 0 ) {
253 // we are the parent
254 if ( parent == null ) {
255 split[0] = null;
256 }
257 // Let my parent handle the request.
258 else {
259 return parent.getElement( relativeName );
260 }
261 }
262
263 // did we have a trailing / in the name?
264 if ( split[1].length() == 0 ) {
265 // For the case of "/",
266 if ( split[0].equals( null ) ) {
267 return this;
268 }
269
270 //otherwise it is an error
271 // to leave a trailing /, for example tree/leaf/
272 throw new XElementException( "Invalid name (trailing '/') : " + relativeName );
273 }
274
275 names = split;
276 }
277
278 if ( names[0] == null ) {
279 for ( int i = 1; i < contents.size(); i++ ) {
280 XElement o = ( XElement )contents.elementAt( i );
281 if ( names[1].equals( o.getName() ) ) {
282 return o;
283 }
284 }
285 } else {
286 if ( names[0].equals( "." ) ) {
287 return getElement( names[1] );
288 } else if ( names[0].equals( ".." ) ) {
289 if ( parent != null ) {
290 return parent.getElement( names[1] );
291 } else {
292 throw new XElementException( "Invalid name (" + getName() + " has no parent) : " + relativeName );
293 }
294 } else {
295 for ( int i = 1; i < contents.size(); i++ ) {
296 XElement o = ( XElement )contents.elementAt( i );
297 if ( names[0].equals( o.getName() ) ) {
298 return o.getElement( names[1] );
299 }
300 }
301 }
302 }
303
304 throw new XElementException( "Invalid name (" + getName() + " does not contain the name) : " + relativeName );
305 }
306
307
308 /**
309 * Gets the value of a contained attribute object.
310 *
311 * @param objectName The name of the attribute object.
312 * @return The Field value
313 * @returns the value of the attribute object.
314 * @throws XElementException if the object does not exist or if its not an
315 * attribute object.
316 */
317 public String getField( String objectName )
318 throws XElementException {
319 return getElement( objectName ).getValue();
320 }
321
322 /**
323 * Returns true if the object is an attribute object. An object is an
324 * attribute object if it does not contain any other objects.
325 *
326 * @return The Field value
327 * @returns true if the object is an attribute object.
328 */
329 public boolean isField() {
330 return contents.size() == 1;
331 }
332
333 /**
334 * Returns all the contained objects with the specified name.
335 *
336 * @param relativeName The name of the objects
337 * @return The ElementsNamed value
338 * @returns whose name is relativeName;
339 */
340 public java.util.Enumeration getElementsNamed( String relativeName ) {
341
342 Vector t = new Vector();
343 addElementsToVector( t, relativeName );
344 return t.elements();
345 }
346
347 /**
348 * Adds and appends string data to the objects text.
349 *
350 * @param data Description of Parameter
351 */
352 public void add( String data ) {
353 ( ( StringBuffer )contents.elementAt( 0 ) ).append( data );
354 }
355
356 /**
357 * Serializes this object into a string.
358 *
359 * @return Description of the Returned Value
360 */
361 public String toString() {
362 return toString( 0, true );
363 }
364
365 /**
366 * Adds an XElement to the set of XElements that are contained by this
367 * object.
368 *
369 * @param subObject
370 */
371 public void addElement( XElement subObject ) {
372 contents.addElement( subObject );
373 subObject.parent = this;
374 }
375
376 /**
377 * Adds an XElement to the set of XElements that are contained by this
378 * object.
379 *
380 * @param key The feature to be added to the Field attribute
381 * @param value The feature to be added to the Field attribute
382 */
383 public void addField( String key, String value ) {
384 XElement subObject = new XElement( key );
385 subObject.add( value );
386 addElement( subObject );
387 }
388
389 /**
390 * Tests to see if this object contains the specified object.
391 *
392 * @param objectName The name of the object.
393 * @return Description of the Returned Value
394 * @returns true if the object exits.
395 */
396 public boolean containsElement( String objectName ) {
397 try {
398 getElement( objectName );
399 return true;
400 } catch ( XElementException e ) {
401 return false;
402 }
403 }
404
405 /**
406 * Tests to see if this object contains the specified attribute object.
407 *
408 * @param objectName The name of the attribute object.
409 * @return Description of the Returned Value
410 * @returns true if the attribute exits.
411 */
412 public boolean containsField( String objectName ) {
413 try {
414 XElement obj = getElement( objectName );
415 return obj.isField();
416 } catch ( XElementException e ) {
417 return false;
418 }
419 }
420
421 /**
422 * Serializes this object into a string.
423 *
424 * @param nestingLevel how many tabs to prepend to output
425 * @param indent Description of Parameter
426 * @return Description of the Returned Value
427 */
428 public String toString( int nestingLevel, boolean indent ) {
429 try {
430 StringBuffer indentation = new StringBuffer();
431 StringBuffer rc = new StringBuffer();
432 if ( indent ) {
433 for ( int i = 0; i < nestingLevel; i++ ) {
434 indentation.append( '\t' );
435 }
436 }
437 rc.append( indentation.toString() );
438 rc.append( "<" );
439 rc.append( getName() );
440 Enumeration enumeration = metadata.keys();
441 while ( enumeration.hasMoreElements() ) {
442 String key = ( String )enumeration.nextElement();
443 String value = ( String )metadata.get( key );
444 rc.append( ' ' );
445 rc.append( key );
446 rc.append( "=\"" );
447 rc.append( metaValueEncode( value ) );
448 rc.append( '"' );
449 }
450 if ( isField() ) {
451 if ( getValue().length() == 0 ) {
452 rc.append( "/>" );
453 rc.append( nl );
454 } else {
455 rc.append( '>' );
456 rc.append( valueEncode( getValue() ) );
457 rc.append( "</" );
458 rc.append( getName() );
459 rc.append( '>' );
460 rc.append( nl );
461 }
462 } else {
463 rc.append( '>' );
464 rc.append( nl );
465 String text = getText();
466 if ( text.length() > 0 ) {
467 rc.append( indentation.toString() + "\t" );
468 rc.append( getText() );
469 rc.append( nl );
470 }
471 for ( int i = 1; i < contents.size(); i++ ) {
472 Object o = contents.elementAt( i );
473 rc.append( ( ( XElement )o ).toString( nestingLevel + 1, indent ) );
474 }
475 rc.append( indentation.toString() );
476 rc.append( "</" );
477 rc.append( getName() );
478 rc.append( '>' );
479 rc.append( nl );
480 }
481 return rc.toString();
482 } catch ( XElementException e ) {
483 // This should not occur!
484 e.printStackTrace();
485 System.exit( 1 );
486 return "";
487 }
488 }
489
490 /**
491 * Serializes this object into a XML document String.
492 *
493 * @param indent Description of Parameter
494 * @return Description of the Returned Value
495 */
496 public String toXML( boolean indent ) {
497 return
498 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + nl +
499 toString( 0, indent );
500 }
501
502 /**
503 * Removes this XElement from the parent.
504 *
505 * @throws XElementException if the object did not have a parent
506 */
507 public void removeFromParent()
508 throws XElementException {
509 if ( parent == null ) {
510 throw new XElementException( "" + getName() + " does not have a parent" );
511 }
512
513 parent.contents.remove( this );
514 parent = null;
515 }
516
517 /**
518 * @return Description of the Returned Value
519 * @returns an Enumeration of all the XElement conatained within this
520 * object.
521 */
522 public Enumeration elements() {
523 return getElementsNamed( "*" );
524 }
525
526 /**
527 * adds all the contains elements to the vector that match the relative
528 * name.
529 *
530 * @param t The feature to be added to the ElementsToVector
531 * attribute
532 * @param relativeName The feature to be added to the ElementsToVector
533 * attribute
534 */
535 private void addElementsToVector( Vector t, String relativeName ) {
536
537 String names[] = {null, relativeName};
538
539 // Does the name have a "/" in it?
540 String split[] = splitFront( relativeName, "/" );
541 if ( split != null ) {
542
543 // was it an absolute name? (started with a '/')
544 if ( split[0].length() == 0 ) {
545 // we are the parent
546 if ( parent == null ) {
547 split[0] = null;
548 } else {
549 // Let my parent handle the request.
550 parent.addElementsToVector( t, relativeName );
551 return;
552 }
553 }
554
555 // did we have a trailing / in the name?
556 if ( split[1].length() == 0 ) {
557 return;
558 }
559 names = split;
560 }
561
562 if ( names[0] == null ) {
563 if ( names[1].equals( "*" ) ) {
564 for ( int i = 1; i < contents.size(); i++ ) {
565 t.addElement( contents.elementAt( i ) );
566 }
567 } else {
568 for ( int i = 1; i < contents.size(); i++ ) {
569 XElement o = ( XElement )contents.elementAt( i );
570 if ( names[1].equals( o.getName() ) ) {
571 t.addElement( o );
572 }
573 }
574 }
575 } else {
576 if ( names[0].equals( "." ) ) {
577 addElementsToVector( t, names[1] );
578 return;
579 } else if ( names[0].equals( ".." ) ) {
580 if ( parent != null ) {
581 parent.addElementsToVector( t, names[1] );
582 }
583 return;
584 } else {
585 for ( int i = 1; i < contents.size(); i++ ) {
586 XElement o = ( XElement )contents.elementAt( i );
587 if ( names[0].equals( o.getName() ) ) {
588 o.addElementsToVector( t, names[1] );
589 }
590 }
591 }
592 }
593 }
594
595 /**
596 * Constructs an empty object.
597 *
598 * @param is Description of Parameter
599 * @return Description of the Returned Value
600 * @exception XElementException Description of Exception
601 * @exception java.io.IOException Description of Exception
602 */
603 public static XElement createFrom( java.io.InputStream is )
604 throws XElementException, java.io.IOException {
605 class MyRecordConsumer implements XElementConsumer {
606
607 XElement root = null;
608
609 public void documentEndEvent() {
610 }
611
612 public void documentStartEvent() {
613 }
614
615 public void recordReadEvent( XElement o ) {
616 root = o;
617 }
618 }
619
620 MyRecordConsumer consumer = new MyRecordConsumer();
621 XElementProducer producer = new XElementProducer( consumer );
622
623 try {
624 producer.parse( is );
625 if ( consumer.root == null ) {
626 throw new XElementException( "No root element" );
627 }
628 return consumer.root;
629 } catch ( java.io.IOException e ) {
630 throw e;
631 } catch ( Exception e ) {
632 throw new XElementException( "Parse Error: " + e );
633 }
634 }
635
636 /**
637 * Constructs an empty object.
638 *
639 * @param url Description of Parameter
640 * @return Description of the Returned Value
641 * @exception XElementException Description of Exception
642 * @exception java.io.IOException Description of Exception
643 */
644 public static XElement createFrom( java.net.URL url )
645 throws XElementException, java.io.IOException {
646 class MyRecordConsumer implements XElementConsumer {
647
648 XElement root = null;
649
650 public void documentEndEvent() {
651 }
652
653 public void documentStartEvent() {
654 }
655
656 public void recordReadEvent( XElement o ) {
657 root = o;
658 }
659 }
660
661 MyRecordConsumer consumer = new MyRecordConsumer();
662 XElementProducer producer = new XElementProducer( consumer );
663
664 try {
665 producer.parse( url );
666 if ( consumer.root == null ) {
667 throw new XElementException( "No root element" );
668 }
669 return consumer.root;
670 } catch ( java.io.IOException e ) {
671 throw e;
672 } catch ( Exception e ) {
673 throw new XElementException( "Parse Error: " + e );
674 }
675 }
676
677
678 private static String findAndReplace( String value, String searchStr, String replaceStr ) {
679 StringBuffer buffer = new StringBuffer( value.length() );
680 while ( value.length() > 0 ) {
681 int pos = value.indexOf( searchStr );
682 if ( pos != -1 ) {
683 buffer.append( value.substring( 0, pos ) );
684 buffer.append( replaceStr );
685 if ( pos + searchStr.length() < value.length() ) {
686 value = value.substring( pos + searchStr.length() );
687 } else {
688 value = "";
689 }
690 } else {
691 buffer.append( value );
692 value = "";
693 }
694 }
695 return buffer.toString();
696 }
697
698 private static String metaValueEncode( String value ) {
699 value = findAndReplace( value, "&", "&" );
700 value = findAndReplace( value, "\"", """ );
701 value = findAndReplace( value, "'", "'" );
702 return utf8Encode( value );
703 }
704
705
706 private static String utf8Encode( String value ) {
707 try {
708 //char buff[] = new char[value.length()];
709 //value.getChars( 0, buff.length, buff, 0 );
710 //sun.io.CharToByteUTF8 conv = new sun.io.CharToByteUTF8();
711 //byte b[] = conv.convertAll( buff );
712 return new String( value.getBytes("UTF-8") );
713 } catch ( Exception e ) {
714 return null;
715 }
716 }
717
718 private static String valueEncode( String value ) {
719 value = findAndReplace( value, "&", "&" );
720 value = findAndReplace( value, "<", "<" );
721 value = findAndReplace( value, ">", ">" );
722 return utf8Encode( value );
723 }
724
725
726 private static String[] splitFront( String string, String splitMarker ) {
727
728 if ( string == null || splitMarker == null ) {
729 throw new NullPointerException();
730 }
731
732 String front;
733 String back;
734
735 int pos = string.indexOf( splitMarker );
736 if ( pos == -1 ) {
737 return null;
738 }
739
740 int l = splitMarker.length();
741 front = string.substring( 0, pos );
742 if ( pos + l >= string.length() ) {
743 back = "";
744 } else {
745 back = string.substring( pos + l );
746 }
747
748 String rc[] = {front, back};
749 return rc;
750 }
751
752 public String getOptionalField( String field) throws XElementException {
753 if ( !containsField(field) )
754 return null;
755 return getField(field);
756 }
757
758 public void setOptionalField( String field, String value) throws XElementException {
759 if ( value == null ) {
760 if( containsField(field) )
761 getElement(field).removeFromParent();
762 return;
763 }
764 if( containsField(field) )
765 setField(field, value);
766 else
767 addField(field, value);
768 }
769
770 }