/*
* soapUI, copyright (C) 2004-2011 eviware.com
*
* soapUI is free software; you can redistribute it and/or modify it under the
* terms of version 2.1 of the GNU Lesser General Public License as published by
* the Free Software Foundation.
*
* soapUI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details at gnu.org.
*/
package com.eviware.soapui.support.xml;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.TreePath;
import javax.xml.namespace.QName;
import org.apache.log4j.Logger;
import org.apache.xmlbeans.SchemaGlobalElement;
import org.apache.xmlbeans.SchemaParticle;
import org.apache.xmlbeans.SchemaProperty;
import org.apache.xmlbeans.SchemaType;
import org.apache.xmlbeans.SchemaTypeSystem;
import org.apache.xmlbeans.XmlBeans;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlCursor.TokenType;
import org.apache.xmlbeans.XmlLineNumber;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.impl.values.XmlAnyTypeImpl;
import org.jdesktop.swingx.treetable.TreeTableModel;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import com.eviware.soapui.impl.wsdl.support.xsd.SchemaUtils;
import com.eviware.soapui.support.types.StringToStringMap;
public class XmlObjectTreeModel implements TreeTableModel
{
private XmlObject xmlObject;
private Set<TreeModelListener> listeners = new HashSet<TreeModelListener>();
private XmlCursor cursor;
private Map<XmlObject, XmlTreeNode> treeNodeMap = new HashMap<XmlObject, XmlTreeNode>();
public final static Class<?> hierarchicalColumnClass = TreeTableModel.class;
private SchemaTypeSystem typeSystem;
private RootXmlTreeNode root;
@SuppressWarnings( "unused" )
private final static Logger log = Logger.getLogger( XmlObjectTreeModel.class );
public XmlObjectTreeModel( XmlObject xmlObject )
{
this( XmlBeans.getBuiltinTypeSystem(), xmlObject );
}
public XmlObjectTreeModel()
{
this( XmlObject.Factory.newInstance() );
}
public XmlObjectTreeModel( SchemaTypeSystem typeSystem, XmlObject xmlObject )
{
if( typeSystem == null )
typeSystem = XmlBeans.getBuiltinTypeSystem();
this.typeSystem = typeSystem;
this.xmlObject = xmlObject;
init();
}
public XmlObjectTreeModel( SchemaTypeSystem typeSystem )
{
this( typeSystem, XmlObject.Factory.newInstance() );
}
private void init()
{
cursor = null;
if( xmlObject != null )
{
cursor = xmlObject.newCursor();
cursor.toStartDoc();
}
root = new RootXmlTreeNode( cursor );
}
public SchemaTypeSystem getTypeSystem()
{
return typeSystem;
}
public void setTypeSystem( SchemaTypeSystem typeSystem )
{
if( typeSystem == null )
typeSystem = XmlBeans.getBuiltinTypeSystem();
this.typeSystem = typeSystem;
}
public XmlObject getXmlObject()
{
return xmlObject;
}
public void setXmlObject( XmlObject xmlObject )
{
if( cursor != null )
cursor.dispose();
this.xmlObject = xmlObject;
init();
XmlTreeNode xmlTreeNode = ( ( XmlTreeNode )getRoot() );
fireTreeStructureChanged( xmlTreeNode );
}
protected void fireTreeStructureChanged( XmlTreeNode rootNode )
{
for( TreeModelListener listener : listeners )
{
listener.treeStructureChanged( new XmlTreeTableModelEvent( this, rootNode.getTreePath(), -1 ) );
}
}
public Class<?> getColumnClass( int arg0 )
{
return arg0 == 0 ? hierarchicalColumnClass : XmlTreeNode.class;
}
public int getColumnCount()
{
return 3;
}
public String getColumnName( int arg0 )
{
return null;
}
public Object getValueAt( Object arg0, int arg1 )
{
return arg0; // ((XmlTreeNode)arg0).getValue( arg1 );
}
public boolean isCellEditable( Object arg0, int arg1 )
{
return ( ( XmlTreeNode )arg0 ).isEditable( arg1 );
}
public void setValueAt( Object arg0, Object arg1, int arg2 )
{
XmlTreeNode treeNode = ( XmlTreeNode )arg1;
if( treeNode.setValue( arg2, arg0 ) )
{
fireTreeNodeChanged( treeNode, arg2 );
}
}
protected void fireTreeNodeChanged( XmlTreeNode treeNode, int column )
{
for( TreeModelListener listener : listeners )
{
listener.treeNodesChanged( new XmlTreeTableModelEvent( this, treeNode.getTreePath(), column ) );
}
}
public void addTreeModelListener( TreeModelListener l )
{
listeners.add( l );
}
public Object getChild( Object parent, int index )
{
return ( ( XmlTreeNode )parent ).getChild( index );
}
public int getChildCount( Object parent )
{
return ( ( XmlTreeNode )parent ).getChildCount();
}
public int getIndexOfChild( Object parent, Object child )
{
return ( ( XmlTreeNode )parent ).getIndexOfChild( ( XmlTreeNode )child );
}
public Object getRoot()
{
return getRootNode();
}
public RootXmlTreeNode getRootNode()
{
return root;
}
public boolean isLeaf( Object node )
{
return ( ( XmlTreeNode )node ).isLeaf();
}
public void removeTreeModelListener( TreeModelListener l )
{
listeners.remove( l );
}
public void valueForPathChanged( TreePath path, Object newValue )
{
}
private class TreeBookmark extends XmlCursor.XmlBookmark
{
}
public interface XmlTreeNode
{
public int getChildCount();
public XmlTreeNode getChild( int ix );
public int getIndexOfChild( XmlTreeNode childNode );
public String getNodeName();
public String getNodeText();
public boolean isEditable( int column );
public boolean isLeaf();
public boolean setValue( int column, Object value );
public XmlLineNumber getNodeLineNumber();
public XmlLineNumber getValueLineNumber();
public XmlObject getXmlObject();
public Node getDomNode();
public TreePath getTreePath();
public XmlTreeNode getParent();
public SchemaType getSchemaType();
public String getDocumentation();
}
private abstract class AbstractXmlTreeNode implements XmlTreeNode
{
protected Node node;
protected TreeBookmark bm;
private final XmlTreeNode parent;
private XmlLineNumber lineNumber;
protected SchemaType schemaType;
protected String documentation;
@SuppressWarnings( "unchecked" )
protected AbstractXmlTreeNode( XmlCursor cursor, XmlTreeNode parent )
{
this.parent = parent;
if( cursor != null )
{
node = cursor.getDomNode();
ArrayList list = new ArrayList();
cursor.getAllBookmarkRefs( list );
for( Object o : list )
if( o instanceof XmlLineNumber )
lineNumber = ( XmlLineNumber )o;
bm = new TreeBookmark();
cursor.setBookmark( bm );
treeNodeMap.put( cursor.getObject(), this );
}
}
protected SchemaType findSchemaType()
{
if( cursor == null )
return null;
positionCursor( cursor );
SchemaType resultType = null;
XmlObject xo = cursor.getObject();
if( xo != null )
{
Node domNode = xo.getDomNode();
// check for xsi:type
if( domNode.getNodeType() == Node.ELEMENT_NODE )
{
Element elm = ( Element )domNode;
String xsiType = elm.getAttributeNS( "http://www.w3.org/2001/XMLSchema-instance", "type" );
if( xsiType != null && xsiType.length() > 0 )
{
resultType = findXsiType( xsiType );
}
}
if( resultType == null )
resultType = typeSystem.findType( xo.schemaType().getName() );
if( resultType == null )
resultType = xo.schemaType();
if( resultType.isNoType() )
{
QName nm = cursor.getName();
if( parent != null && parent.getSchemaType() != null )
{
SchemaType parentSchemaType = parent.getSchemaType();
SchemaParticle contentModel = parentSchemaType.getContentModel();
if( contentModel != null )
{
SchemaParticle[] children = contentModel.getParticleChildren();
for( int c = 0; children != null && c < children.length; c++ )
{
if( nm.equals( children[c].getName() ) )
{
resultType = children[c].getType();
documentation = SchemaUtils.getDocumentation( resultType );
break;
}
}
if( resultType.isNoType() && nm.equals( contentModel.getName() ) )
resultType = contentModel.getType();
if( resultType.isNoType() )
{
SchemaType[] anonymousTypes = parentSchemaType.getAnonymousTypes();
for( int c = 0; anonymousTypes != null && c < anonymousTypes.length; c++ )
{
QName name = anonymousTypes[c].getName();
if( name != null && name.equals( nm ) )
{
resultType = anonymousTypes[c];
break;
}
else if( anonymousTypes[c].getContainerField().getName().equals( nm ) )
{
resultType = anonymousTypes[c].getContainerField().getType();
break;
}
}
}
}
}
if( resultType.isNoType() )
{
SchemaGlobalElement elm = typeSystem.findElement( nm );
if( elm != null )
{
resultType = elm.getType();
}
else if( typeSystem.findDocumentType( nm ) != null )
{
resultType = typeSystem.findDocumentType( nm );
}
}
}
}
if( resultType == null )
resultType = XmlAnyTypeImpl.type;
if( documentation == null )
documentation = SchemaUtils.getDocumentation( resultType );
return resultType;
}
@SuppressWarnings( "unused" )
protected String getUserInfo( SchemaType schemaType )
{
if( schemaType.getAnnotation() != null )
{
XmlObject[] userInformation = schemaType.getAnnotation().getUserInformation();
if( userInformation != null && userInformation.length > 0 )
{
return userInformation[0].toString(); // XmlUtils.getElementText(
// ( Element )
// userInformation[0].getDomNode());
}
}
return null;
}
public String getDocumentation()
{
return documentation;
}
private SchemaType findXsiType( String xsiType )
{
SchemaType resultType;
int ix = xsiType.indexOf( ':' );
QName name = null;
if( ix == -1 )
{
name = new QName( xsiType );
resultType = typeSystem.findType( name );
}
else
{
StringToStringMap map = new StringToStringMap();
cursor.getAllNamespaces( map );
name = new QName( map.get( xsiType.substring( 0, ix ) ), xsiType.substring( ix + 1 ) );
resultType = typeSystem.findType( name );
}
return resultType;
}
public XmlTreeNode getParent()
{
return parent;
}
protected void positionCursor( XmlCursor cursor )
{
cursor.toBookmark( bm );
}
public XmlTreeNode getChild( int ix )
{
return null;
}
public int getChildCount()
{
return 0;
}
public int getIndexOfChild( XmlTreeNode childNode )
{
return -1;
}
@SuppressWarnings( "unused" )
public Object getValue( int column )
{
if( column == 0 )
return getNodeName();
else if( column == 1 )
return getNodeText();
return null;
}
public Node getDomNode()
{
return node;
}
public String getNodeName()
{
return node == null ? null : node.getNodeName();
}
public String getNodeText()
{
if( node == null )
return null;
String nodeValue = node.getNodeValue();
return nodeValue == null ? null : nodeValue.trim();
}
public boolean isEditable( int column )
{
return false;
}
public boolean isLeaf()
{
return getChildCount() == 0;
}
public boolean setValue( int column, Object value )
{
return false;
}
public String toString()
{
return getNodeName();
}
public boolean equals( Object obj )
{
if( obj == this )
return true;
if( obj instanceof AbstractXmlTreeNode )
return ( ( AbstractXmlTreeNode )obj ).node == this.node;
else
return super.equals( obj );
}
public XmlLineNumber getNodeLineNumber()
{
return lineNumber;
}
public XmlLineNumber getValueLineNumber()
{
return lineNumber;
}
public XmlObject getXmlObject()
{
if( cursor != null && cursor.toBookmark( bm ) )
{
XmlObject object = cursor.getObject();
if( object != null )
return object;
else if( parent != null )
return parent.getXmlObject();
}
return null;
}
public TreePath getTreePath()
{
List<XmlTreeNode> nodes = new ArrayList<XmlTreeNode>();
nodes.add( this );
XmlTreeNode node = this;
while( node.getParent() != null )
{
nodes.add( 0, node.getParent() );
node = node.getParent();
}
return new TreePath( nodes.toArray() );
}
public SchemaType getSchemaType()
{
if( schemaType == null )
schemaType = findSchemaType();
return schemaType;
}
}
public class RootXmlTreeNode extends AbstractXmlTreeNode
{
private ElementXmlTreeNode rootNode;
protected RootXmlTreeNode( XmlCursor cursor )
{
super( cursor, null );
if( cursor != null )
{
cursor.toFirstContentToken();
rootNode = new ElementXmlTreeNode( cursor, this );
}
}
public XmlTreeNode getChild( int ix )
{
return ix == 0 ? rootNode : null;
}
public int getChildCount()
{
return rootNode == null ? 0 : 1;
}
public int getIndexOfChild( XmlTreeNode childNode )
{
return childNode == rootNode ? 0 : -1;
}
}
public class ElementXmlTreeNode extends AbstractXmlTreeNode
{
private LinkedList<XmlTreeNode> elements = new LinkedList<XmlTreeNode>();
private TextXmlTreeNode textTreeNode;
private int attrCount;
protected ElementXmlTreeNode( XmlCursor cursor, XmlTreeNode parent )
{
super( cursor, parent );
TokenType token = cursor.toNextToken();
while( token == TokenType.ATTR || token == TokenType.NAMESPACE )
{
if( token == TokenType.ATTR )
{
elements.add( new AttributeXmlTreeNode( cursor, this ) );
}
token = cursor.toNextToken();
}
attrCount = elements.size();
positionCursor( cursor );
cursor.toFirstContentToken();
while( true )
{
while( cursor.isComment() || cursor.isProcinst() )
cursor.toNextToken();
if( cursor.isContainer() )
{
elements.add( new ElementXmlTreeNode( cursor, this ) );
cursor.toEndToken();
cursor.toNextToken();
}
if( cursor.isText() )
{
elements.add( new TextXmlTreeNode( cursor, this ) );
cursor.toNextToken();
}
if( cursor.isEnd() || cursor.isEnddoc() )
break;
}
if( elements.size() == attrCount + 1 && ( elements.get( attrCount ) instanceof TextXmlTreeNode ) )
{
textTreeNode = ( TextXmlTreeNode )elements.remove( attrCount );
}
else
{
for( int c = attrCount; c < elements.size(); c++ )
{
if( elements.get( c ) instanceof TextXmlTreeNode )
{
TextXmlTreeNode treeNode = ( TextXmlTreeNode )elements.get( c );
String text = treeNode.getNodeText().trim();
if( text.length() == 0 )
{
elements.remove( c );
c-- ;
}
}
}
}
positionCursor( cursor );
}
public XmlTreeNode getChild( int ix )
{
return elements.get( ix );
}
public boolean isEditable( int column )
{
return column == 1 && elements.size() == attrCount;
}
public boolean setValue( int column, Object value )
{
if( column == 1 )
{
if( textTreeNode != null )
{
textTreeNode.setValue( 1, value );
}
else
{
positionCursor( cursor );
cursor.toEndToken();
cursor.insertChars( value.toString() );
positionCursor( cursor );
cursor.toFirstContentToken();
textTreeNode = new TextXmlTreeNode( cursor, this );
}
}
return column == 1;
}
public int getChildCount()
{
return elements.size();
}
public int getIndexOfChild( XmlTreeNode childNode )
{
return elements.indexOf( childNode );
}
public String getNodeText()
{
return textTreeNode == null ? "" : textTreeNode.getNodeText();
}
public XmlLineNumber getValueLineNumber()
{
return textTreeNode == null ? super.getValueLineNumber() : textTreeNode.getValueLineNumber();
}
}
public class AttributeXmlTreeNode extends AbstractXmlTreeNode
{
private boolean checkedType;
protected AttributeXmlTreeNode( XmlCursor cursor, ElementXmlTreeNode parent )
{
super( cursor, parent );
}
public String getNodeName()
{
return "@" + super.getNodeName();
}
public XmlLineNumber getNodeLineNumber()
{
return getParent().getNodeLineNumber();
}
public boolean isEditable( int column )
{
return column == 1;
}
public boolean setValue( int column, Object value )
{
if( column == 1 )
node.setNodeValue( value.toString() );
return column == 1;
}
public SchemaType getSchemaType()
{
if( schemaType == null && !checkedType )
{
SchemaType parentSchemaType = getParent().getSchemaType();
if( parentSchemaType != null )
{
positionCursor( cursor );
SchemaProperty attributeProperty = parentSchemaType.getAttributeProperty( cursor.getName() );
if( attributeProperty != null )
{
schemaType = attributeProperty.getType();
documentation = SchemaUtils.getDocumentation( schemaType );
// SchemaAnnotation annotation = schemaType.getAnnotation();
// if( annotation != null )
// {
// XmlObject[] userInformation =
// annotation.getUserInformation();
// if( userInformation != null && userInformation.length > 0 )
// {
// //userInformation[0].toString(); //XmlUtils.getElementText(
// ( Element ) userInformation[0].getDomNode());
// }
// }
}
}
checkedType = true;
}
return schemaType;
}
}
public class TextXmlTreeNode extends AbstractXmlTreeNode
{
protected TextXmlTreeNode( XmlCursor cursor, ElementXmlTreeNode parent )
{
super( cursor, parent );
}
public boolean isEditable( int column )
{
return column == 1;
}
public boolean setValue( int column, Object value )
{
if( column == 1 && node != null )
node.setNodeValue( value.toString() );
return column == 1;
}
public TreePath getTreePath()
{
return super.getTreePath().getParentPath();
}
}
public TreePath findXmlTreeNode( int line, int column )
{
line++ ;
XmlTreeNode treeNode = findXmlTreeNode( root, line, column );
if( treeNode instanceof AttributeXmlTreeNode )
return treeNode.getParent().getTreePath();
else if( treeNode != null )
return treeNode.getTreePath();
return null;
}
private XmlTreeNode findXmlTreeNode( XmlTreeNode treeNode, int line, int column )
{
for( int c = 0; c < treeNode.getChildCount(); c++ )
{
XmlTreeNode child = treeNode.getChild( c );
XmlLineNumber ln = child.getNodeLineNumber();
if( ln != null && ( line < ln.getLine() || ( line == ln.getLine() && column <= ln.getColumn() ) ) )
{
if( c == 0 )
return treeNode;
else
return findXmlTreeNode( treeNode.getChild( c - 1 ), line, column );
}
}
if( treeNode.getChildCount() > 0 )
{
return findXmlTreeNode( treeNode.getChild( treeNode.getChildCount() - 1 ), line, column );
}
return treeNode;
}
public class XmlTreeTableModelEvent extends TreeModelEvent
{
private final int column;
public XmlTreeTableModelEvent( Object source, Object[] path, int[] childIndices, Object[] children, int column )
{
super( source, path, childIndices, children );
this.column = column;
}
public XmlTreeTableModelEvent( Object source, Object[] path, int column )
{
super( source, path );
this.column = column;
}
public XmlTreeTableModelEvent( Object source, TreePath path, int[] childIndices, Object[] children, int column )
{
super( source, path, childIndices, children );
this.column = column;
}
public XmlTreeTableModelEvent( Object source, TreePath path, int column )
{
super( source, path );
this.column = column;
}
public int getColumn()
{
return column;
}
}
public XmlTreeNode getXmlTreeNode( XmlObject object )
{
return treeNodeMap.get( object );
}
public XmlTreeNode[] selectTreeNodes( String xpath )
{
XmlObject[] nodes = xmlObject.selectPath( xpath );
List<XmlTreeNode> result = new ArrayList<XmlTreeNode>();
for( XmlObject xmlObject : nodes )
{
XmlTreeNode tn = getXmlTreeNode( xmlObject );
if( tn != null )
result.add( tn );
}
return result.toArray( new XmlTreeNode[result.size()] );
}
public void release()
{
typeSystem = null;
treeNodeMap.clear();
listeners.clear();
}
public int getHierarchicalColumn()
{
return 0;
}
}