1
2
3
4
5
6
7
8
9
10
11
12
13 package com.eviware.soapui.support.xml;
14
15 import com.eviware.soapui.impl.wsdl.support.xsd.SchemaUtils;
16 import com.eviware.soapui.support.types.StringToStringMap;
17 import org.apache.log4j.Logger;
18 import org.apache.xmlbeans.*;
19 import org.apache.xmlbeans.XmlCursor.TokenType;
20 import org.apache.xmlbeans.impl.values.XmlAnyTypeImpl;
21 import org.jdesktop.swingx.treetable.TreeTableModel;
22 import org.w3c.dom.Element;
23 import org.w3c.dom.Node;
24
25 import javax.swing.event.TreeModelEvent;
26 import javax.swing.event.TreeModelListener;
27 import javax.swing.tree.TreePath;
28 import javax.xml.namespace.QName;
29 import java.util.*;
30
31 public class XmlObjectTreeModel implements TreeTableModel
32 {
33 private XmlObject xmlObject;
34 private Set<TreeModelListener> listeners = new HashSet<TreeModelListener>();
35 private XmlCursor cursor;
36 private Map<XmlObject, XmlTreeNode> treeNodeMap = new HashMap<XmlObject,XmlTreeNode>();
37
38 public final static Class<?> hierarchicalColumnClass = TreeTableModel.class;
39 private SchemaTypeSystem typeSystem;
40 private RootXmlTreeNode root;
41 @SuppressWarnings("unused")
42 private final static Logger log = Logger.getLogger(XmlObjectTreeModel.class);
43
44 public XmlObjectTreeModel(XmlObject xmlObject)
45 {
46 this( XmlBeans.getBuiltinTypeSystem(), xmlObject );
47 }
48
49 public XmlObjectTreeModel()
50 {
51 this( XmlObject.Factory.newInstance() );
52 }
53
54 public XmlObjectTreeModel(SchemaTypeSystem typeSystem, XmlObject xmlObject)
55 {
56 if( typeSystem == null )
57 typeSystem = XmlBeans.getBuiltinTypeSystem();
58
59 this.typeSystem = typeSystem;
60 this.xmlObject = xmlObject;
61 init();
62 }
63
64 public XmlObjectTreeModel(SchemaTypeSystem typeSystem)
65 {
66 this( typeSystem, XmlObject.Factory.newInstance() );
67 }
68
69 private void init()
70 {
71 cursor = null;
72
73 if( xmlObject != null )
74 {
75 cursor = xmlObject.newCursor();
76 cursor.toStartDoc();
77 }
78
79 root = new RootXmlTreeNode( cursor );
80 }
81
82 public SchemaTypeSystem getTypeSystem()
83 {
84 return typeSystem;
85 }
86
87 public void setTypeSystem(SchemaTypeSystem typeSystem)
88 {
89 if( typeSystem == null )
90 typeSystem = XmlBeans.getBuiltinTypeSystem();
91
92 this.typeSystem = typeSystem;
93 }
94
95 public XmlObject getXmlObject()
96 {
97 return xmlObject;
98 }
99
100 public void setXmlObject(XmlObject xmlObject)
101 {
102 if( cursor != null )
103 cursor.dispose();
104
105 this.xmlObject = xmlObject;
106 init();
107
108 XmlTreeNode xmlTreeNode = ((XmlTreeNode)getRoot());
109 fireTreeStructureChanged(xmlTreeNode);
110 }
111
112 protected void fireTreeStructureChanged(XmlTreeNode rootNode)
113 {
114 for( TreeModelListener listener : listeners )
115 {
116 listener.treeStructureChanged( new XmlTreeTableModelEvent( this, rootNode.getTreePath(), -1 ));
117 }
118 }
119
120 public Class<?> getColumnClass(int arg0)
121 {
122 return arg0 == 0 ? hierarchicalColumnClass : XmlTreeNode.class;
123 }
124
125 public int getColumnCount()
126 {
127 return 3;
128 }
129
130 public String getColumnName(int arg0)
131 {
132 return null;
133 }
134
135 public Object getValueAt(Object arg0, int arg1)
136 {
137 return arg0;
138 }
139
140 public boolean isCellEditable(Object arg0, int arg1)
141 {
142 return ((XmlTreeNode)arg0).isEditable( arg1 );
143 }
144
145 public void setValueAt(Object arg0, Object arg1, int arg2)
146 {
147 XmlTreeNode treeNode = (XmlTreeNode) arg1;
148 if( treeNode.setValue( arg2, arg0 ) )
149 {
150 fireTreeNodeChanged( treeNode, arg2 );
151 }
152 }
153
154 protected void fireTreeNodeChanged(XmlTreeNode treeNode, int column)
155 {
156 for( TreeModelListener listener : listeners )
157 {
158 listener.treeNodesChanged( new XmlTreeTableModelEvent( this, treeNode.getTreePath(), column ));
159 }
160 }
161
162 public void addTreeModelListener(TreeModelListener l)
163 {
164 listeners.add( l );
165 }
166
167 public Object getChild(Object parent, int index)
168 {
169 return ((XmlTreeNode)parent).getChild( index );
170 }
171
172 public int getChildCount(Object parent)
173 {
174 return ((XmlTreeNode)parent).getChildCount();
175 }
176
177 public int getIndexOfChild(Object parent, Object child)
178 {
179 return ((XmlTreeNode)parent).getIndexOfChild( (XmlTreeNode) child );
180 }
181
182 public Object getRoot()
183 {
184 return getRootNode();
185 }
186
187 public RootXmlTreeNode getRootNode()
188 {
189 return root;
190 }
191
192 public boolean isLeaf(Object node)
193 {
194 return ((XmlTreeNode)node).isLeaf();
195 }
196
197 public void removeTreeModelListener(TreeModelListener l)
198 {
199 listeners.remove( l );
200 }
201
202 public void valueForPathChanged(TreePath path, Object newValue)
203 {
204 }
205
206 private class TreeBookmark extends XmlCursor.XmlBookmark
207 {}
208
209 public interface XmlTreeNode
210 {
211 public int getChildCount();
212
213 public XmlTreeNode getChild( int ix );
214
215 public int getIndexOfChild( XmlTreeNode childNode );
216
217 public String getNodeName();
218
219 public String getNodeText();
220
221 public boolean isEditable( int column );
222
223 public boolean isLeaf();
224
225 public boolean setValue( int column, Object value );
226
227 public XmlLineNumber getNodeLineNumber();
228
229 public XmlLineNumber getValueLineNumber();
230
231 public XmlObject getXmlObject();
232
233 public Node getDomNode();
234
235 public TreePath getTreePath();
236
237 public XmlTreeNode getParent();
238
239 public SchemaType getSchemaType();
240
241 public String getDocumentation();
242 }
243
244 private abstract class AbstractXmlTreeNode implements XmlTreeNode
245 {
246 protected Node node;
247 protected TreeBookmark bm;
248 private final XmlTreeNode parent;
249 private XmlLineNumber lineNumber;
250 protected SchemaType schemaType;
251 protected String documentation;
252
253 @SuppressWarnings("unchecked")
254 protected AbstractXmlTreeNode( XmlCursor cursor, XmlTreeNode parent )
255 {
256 this.parent = parent;
257
258 if( cursor != null )
259 {
260 node = cursor.getDomNode();
261
262 ArrayList list = new ArrayList();
263 cursor.getAllBookmarkRefs( list );
264
265 for( Object o : list )
266 if( o instanceof XmlLineNumber )
267 lineNumber = (XmlLineNumber) o;
268
269 bm = new TreeBookmark();
270 cursor.setBookmark( bm );
271
272 treeNodeMap.put( cursor.getObject(), this );
273 }
274 }
275
276 protected SchemaType findSchemaType()
277 {
278 if( cursor == null )
279 return null;
280
281 positionCursor( cursor );
282
283 SchemaType resultType = null;
284 XmlObject xo = cursor.getObject();
285 if( xo != null )
286 {
287 Node domNode = xo.getDomNode();
288
289
290 if( domNode.getNodeType() == Node.ELEMENT_NODE )
291 {
292 Element elm = (Element) domNode;
293 String xsiType = elm.getAttributeNS( "http://www.w3.org/2001/XMLSchema-instance", "type" );
294 if( xsiType != null && xsiType.length() > 0 )
295 {
296 resultType = findXsiType(xsiType);
297 }
298 }
299
300 if( resultType == null )
301 resultType = typeSystem.findType( xo.schemaType().getName() );
302
303 if( resultType == null )
304 resultType = xo.schemaType();
305
306 if( resultType.isNoType() )
307 {
308 QName nm = cursor.getName();
309
310 if( parent != null && parent.getSchemaType() != null )
311 {
312 SchemaType parentSchemaType = parent.getSchemaType();
313 SchemaParticle contentModel = parentSchemaType.getContentModel();
314
315 if( contentModel != null )
316 {
317 SchemaParticle[] children = contentModel.getParticleChildren();
318
319 for( int c = 0; children != null && c < children.length; c++ )
320 {
321 if( nm.equals( children[c].getName()))
322 {
323 resultType = children[c].getType();
324 documentation = SchemaUtils.getDocumentation( resultType );
325 break;
326 }
327 }
328
329 if( resultType.isNoType() && nm.equals( contentModel.getName() ))
330 resultType = contentModel.getType();
331
332 if( resultType.isNoType() )
333 {
334 SchemaType[] anonymousTypes = parentSchemaType.getAnonymousTypes();
335 for( int c = 0; anonymousTypes != null && c < anonymousTypes.length; c++ )
336 {
337 QName name = anonymousTypes[c].getName();
338 if( name != null && name.equals( nm ))
339 {
340 resultType = anonymousTypes[c];
341 break;
342 }
343 else if( anonymousTypes[c].getContainerField().getName().equals( nm ))
344 {
345 resultType = anonymousTypes[c].getContainerField().getType();
346 break;
347 }
348 }
349 }
350 }
351 }
352
353 if( resultType.isNoType() )
354 {
355 SchemaGlobalElement elm = typeSystem.findElement( nm );
356 if( elm != null )
357 {
358 resultType = elm.getType();
359 }
360 else if( typeSystem.findDocumentType( nm ) != null )
361 {
362 resultType = typeSystem.findDocumentType( nm );
363 }
364 }
365 }
366 }
367
368 if( resultType == null )
369 resultType = XmlAnyTypeImpl.type;
370
371 if( documentation == null )
372 documentation = SchemaUtils.getDocumentation( resultType );
373
374 return resultType;
375 }
376
377 @SuppressWarnings( "unused" )
378 protected String getUserInfo( SchemaType schemaType )
379 {
380 if( schemaType.getAnnotation() != null )
381 {
382 XmlObject[] userInformation = schemaType.getAnnotation().getUserInformation();
383 if( userInformation != null && userInformation.length > 0 )
384 {
385 return userInformation[0].toString();
386 }
387 }
388
389 return null;
390 }
391
392 public String getDocumentation()
393 {
394 return documentation;
395 }
396
397 private SchemaType findXsiType(String xsiType)
398 {
399 SchemaType resultType;
400 int ix = xsiType.indexOf( ':' );
401 QName name = null;
402
403 if( ix == -1 )
404 {
405 name = new QName( xsiType );
406 resultType = typeSystem.findType( name);
407 }
408 else
409 {
410 StringToStringMap map = new StringToStringMap();
411 cursor.getAllNamespaces( map );
412
413 name = new QName( map.get( xsiType.substring( 0, ix )), xsiType.substring( ix +1 ) );
414 resultType = typeSystem.findType( name );
415 }
416
417 return resultType;
418 }
419
420 public XmlTreeNode getParent()
421 {
422 return parent;
423 }
424
425 protected void positionCursor(XmlCursor cursor)
426 {
427 cursor.toBookmark( bm );
428 }
429
430 public XmlTreeNode getChild(int ix)
431 {
432 return null;
433 }
434
435 public int getChildCount()
436 {
437 return 0;
438 }
439
440 public int getIndexOfChild(XmlTreeNode childNode)
441 {
442 return -1;
443 }
444
445 @SuppressWarnings( "unused" )
446 public Object getValue(int column)
447 {
448 if( column == 0 )
449 return getNodeName();
450 else if( column == 1 )
451 return getNodeText();
452
453 return null;
454 }
455
456 public Node getDomNode()
457 {
458 return node;
459 }
460
461 public String getNodeName()
462 {
463 return node == null ? null : node.getNodeName();
464 }
465
466 public String getNodeText()
467 {
468 if( node == null )
469 return null;
470
471 String nodeValue = node.getNodeValue();
472 return nodeValue == null ? null : nodeValue.trim();
473 }
474
475 public boolean isEditable(int column)
476 {
477 return false;
478 }
479
480 public boolean isLeaf()
481 {
482 return getChildCount() == 0;
483 }
484
485 public boolean setValue(int column, Object value)
486 {
487 return false;
488 }
489
490 public String toString()
491 {
492 return getNodeName();
493 }
494
495 public boolean equals(Object obj)
496 {
497 if( obj == this )
498 return true;
499 if( obj instanceof AbstractXmlTreeNode )
500 return ((AbstractXmlTreeNode)obj).node == this.node;
501 else
502 return super.equals(obj);
503 }
504
505 public XmlLineNumber getNodeLineNumber()
506 {
507 return lineNumber;
508 }
509
510 public XmlLineNumber getValueLineNumber()
511 {
512 return lineNumber;
513 }
514
515 public XmlObject getXmlObject()
516 {
517 if( cursor != null && cursor.toBookmark( bm ))
518 {
519 XmlObject object = cursor.getObject();
520
521 if( object != null )
522 return object;
523 else if( parent != null )
524 return parent.getXmlObject();
525 }
526
527 return null;
528 }
529
530 public TreePath getTreePath()
531 {
532 List<XmlTreeNode> nodes = new ArrayList<XmlTreeNode>();
533 nodes.add( this );
534
535 XmlTreeNode node = this;
536
537 while( node.getParent() != null )
538 {
539 nodes.add( 0, node.getParent() );
540 node = node.getParent();
541 }
542
543 return new TreePath( nodes.toArray() );
544 }
545
546 public SchemaType getSchemaType()
547 {
548 if( schemaType == null )
549 schemaType = findSchemaType();
550
551 return schemaType;
552 }
553 }
554
555 public class RootXmlTreeNode extends AbstractXmlTreeNode
556 {
557 private ElementXmlTreeNode rootNode;
558
559 protected RootXmlTreeNode( XmlCursor cursor )
560 {
561 super( cursor, null );
562
563 if( cursor != null )
564 {
565 cursor.toFirstContentToken();
566 rootNode = new ElementXmlTreeNode( cursor, this );
567 }
568 }
569
570 public XmlTreeNode getChild(int ix)
571 {
572 return ix == 0 ? rootNode : null;
573 }
574
575 public int getChildCount()
576 {
577 return rootNode == null ? 0 : 1;
578 }
579
580 public int getIndexOfChild(XmlTreeNode childNode)
581 {
582 return childNode == rootNode ? 0 : -1;
583 }
584 }
585
586 public class ElementXmlTreeNode extends AbstractXmlTreeNode
587 {
588 private LinkedList<XmlTreeNode> elements = new LinkedList<XmlTreeNode>();
589 private TextXmlTreeNode textTreeNode;
590 private int attrCount;
591
592 protected ElementXmlTreeNode( XmlCursor cursor, XmlTreeNode parent )
593 {
594 super( cursor, parent );
595
596 TokenType token = cursor.toNextToken();
597 while( token == TokenType.ATTR || token == TokenType.NAMESPACE )
598 {
599 if( token == TokenType.ATTR )
600 {
601 elements.add( new AttributeXmlTreeNode( cursor, this ));
602 }
603
604 token = cursor.toNextToken();
605 }
606
607 attrCount = elements.size();
608
609 positionCursor( cursor );
610 cursor.toFirstContentToken();
611
612 while( true )
613 {
614 while( cursor.isComment() || cursor.isProcinst() )
615 cursor.toNextToken();
616
617 if( cursor.isContainer())
618 {
619 elements.add( new ElementXmlTreeNode( cursor, this ));
620 cursor.toEndToken();
621 cursor.toNextToken();
622 }
623
624 if( cursor.isText() )
625 {
626 elements.add( new TextXmlTreeNode( cursor, this ));
627 cursor.toNextToken();
628 }
629
630 if( cursor.isEnd() || cursor.isEnddoc() )
631 break;
632 }
633
634 if( elements.size() == attrCount+1 && (elements.get( attrCount ) instanceof TextXmlTreeNode) )
635 {
636 textTreeNode = (TextXmlTreeNode) elements.remove( attrCount );
637 }
638 else
639 {
640 for( int c = attrCount; c < elements.size(); c++ )
641 {
642 if( elements.get( c ) instanceof TextXmlTreeNode )
643 {
644 TextXmlTreeNode treeNode = (TextXmlTreeNode) elements.get( c );
645 String text = treeNode.getNodeText().trim();
646 if( text.length() == 0 )
647 {
648 elements.remove( c );
649 c--;
650 }
651 }
652 }
653 }
654
655 positionCursor(cursor);
656 }
657
658 public XmlTreeNode getChild(int ix)
659 {
660 return elements.get( ix );
661 }
662
663 public boolean isEditable(int column)
664 {
665 return column == 1 && elements.size() == attrCount;
666 }
667
668 public boolean setValue(int column, Object value)
669 {
670 if( column == 1 )
671 {
672 if( textTreeNode != null )
673 {
674 textTreeNode.setValue( 1, value );
675 }
676 else
677 {
678 positionCursor( cursor );
679 cursor.toEndToken();
680 cursor.insertChars( value.toString() );
681 positionCursor( cursor );
682 cursor.toFirstContentToken();
683
684 textTreeNode = new TextXmlTreeNode( cursor, this );
685 }
686 }
687 return column == 1;
688 }
689
690 public int getChildCount()
691 {
692 return elements.size();
693 }
694
695 public int getIndexOfChild(XmlTreeNode childNode)
696 {
697 return elements.indexOf( childNode );
698 }
699
700 public String getNodeText()
701 {
702 return textTreeNode == null ? "" : textTreeNode.getNodeText();
703 }
704
705 public XmlLineNumber getValueLineNumber()
706 {
707 return textTreeNode == null ? super.getValueLineNumber() : textTreeNode.getValueLineNumber();
708 }
709 }
710
711 public class AttributeXmlTreeNode extends AbstractXmlTreeNode
712 {
713 private boolean checkedType;
714
715 protected AttributeXmlTreeNode( XmlCursor cursor, ElementXmlTreeNode parent )
716 {
717 super( cursor, parent );
718 }
719
720 public String getNodeName()
721 {
722 return "@" + super.getNodeName();
723 }
724
725 public XmlLineNumber getNodeLineNumber()
726 {
727 return getParent().getNodeLineNumber();
728 }
729
730 public boolean isEditable(int column)
731 {
732 return column == 1;
733 }
734
735 public boolean setValue(int column, Object value)
736 {
737 if( column == 1 )
738 node.setNodeValue( value.toString() );
739
740 return column == 1;
741 }
742
743 public SchemaType getSchemaType()
744 {
745 if( schemaType == null && !checkedType )
746 {
747 SchemaType parentSchemaType = getParent().getSchemaType();
748 if( parentSchemaType != null )
749 {
750 positionCursor( cursor );
751 SchemaProperty attributeProperty = parentSchemaType.getAttributeProperty( cursor.getName() );
752 if( attributeProperty != null )
753 {
754 schemaType = attributeProperty.getType();
755 documentation = SchemaUtils.getDocumentation( schemaType );
756
757
758
759
760
761
762
763
764
765
766 }
767 }
768
769 checkedType = true;
770 }
771
772 return schemaType;
773 }
774 }
775
776 public class TextXmlTreeNode extends AbstractXmlTreeNode
777 {
778 protected TextXmlTreeNode( XmlCursor cursor, ElementXmlTreeNode parent )
779 {
780 super( cursor, parent );
781 }
782
783 public boolean isEditable(int column)
784 {
785 return column == 1;
786 }
787
788 public boolean setValue(int column, Object value)
789 {
790 if( column == 1 )
791 node.setNodeValue( node == null ? null : value.toString() );
792
793 return column == 1;
794 }
795
796 public TreePath getTreePath()
797 {
798 return super.getTreePath().getParentPath();
799 }
800 }
801
802 public TreePath findXmlTreeNode(int line, int column)
803 {
804 line++;
805
806 XmlTreeNode treeNode = findXmlTreeNode( root, line, column );
807 if( treeNode instanceof AttributeXmlTreeNode )
808 return treeNode.getParent().getTreePath();
809 else if( treeNode != null )
810 return treeNode.getTreePath();
811
812 return null;
813 }
814
815 private XmlTreeNode findXmlTreeNode( XmlTreeNode treeNode, int line, int column )
816 {
817 for( int c = 0; c < treeNode.getChildCount(); c++ )
818 {
819 XmlTreeNode child = treeNode.getChild( c );
820 XmlLineNumber ln = child.getNodeLineNumber();
821 if( ln != null && (line < ln.getLine() || ( line == ln.getLine() && column <= ln.getColumn() )))
822 {
823 if( c == 0 )
824 return treeNode;
825 else
826 return findXmlTreeNode( treeNode.getChild( c-1 ), line, column );
827 }
828 }
829
830 if( treeNode.getChildCount() > 0 )
831 {
832 return findXmlTreeNode( treeNode.getChild( treeNode.getChildCount()-1 ), line, column );
833 }
834
835 return treeNode;
836 }
837
838 public class XmlTreeTableModelEvent extends TreeModelEvent
839 {
840 private final int column;
841
842 public XmlTreeTableModelEvent(Object source, Object[] path, int[] childIndices, Object[] children, int column )
843 {
844 super(source, path, childIndices, children);
845 this.column = column;
846 }
847
848 public XmlTreeTableModelEvent(Object source, Object[] path, int column)
849 {
850 super(source, path);
851 this.column = column;
852 }
853
854 public XmlTreeTableModelEvent(Object source, TreePath path, int[] childIndices, Object[] children, int column)
855 {
856 super(source, path, childIndices, children);
857 this.column = column;
858 }
859
860 public XmlTreeTableModelEvent(Object source, TreePath path, int column)
861 {
862 super(source, path);
863 this.column = column;
864 }
865
866 public int getColumn()
867 {
868 return column;
869 }
870 }
871
872 public XmlTreeNode getXmlTreeNode(XmlObject object)
873 {
874 return treeNodeMap.get( object );
875 }
876
877 public XmlTreeNode [] selectTreeNodes(String xpath)
878 {
879 XmlObject[] nodes = xmlObject.selectPath( xpath );
880 List<XmlTreeNode> result = new ArrayList<XmlTreeNode>();
881
882 for( XmlObject xmlObject : nodes )
883 {
884 XmlTreeNode tn = getXmlTreeNode( xmlObject );
885 if( tn != null )
886 result.add( tn );
887 }
888
889 return result.toArray( new XmlTreeNode[result.size()] );
890 }
891
892 public void release()
893 {
894 typeSystem = null;
895 treeNodeMap.clear();
896
897 listeners.clear();
898 }
899
900 public int getHierarchicalColumn()
901 {
902 return 0;
903 }
904 }