/******************************************************************************
* Copyright (c) 2016 Oracle
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Konstantin Komissarchik - initial implementation and ongoing maintenance
******************************************************************************/
package org.eclipse.sapphire.modeling.xml;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.eclipse.sapphire.LoggingService;
import org.eclipse.sapphire.Sapphire;
import org.eclipse.sapphire.modeling.ValidateEditException;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
/**
* @author <a href="mailto:konstantin.komissarchik@oracle.com">Konstantin Komissarchik</a>
*/
public abstract class XmlNode
{
private final XmlResourceStore store;
private final XmlElement parent;
private final Node domNode;
private List<Listener> listeners;
public XmlNode( final XmlResourceStore store,
final XmlElement parent,
final Node domNode )
{
if( store == null )
{
throw new IllegalArgumentException();
}
if( domNode == null )
{
throw new IllegalArgumentException();
}
this.store = store;
this.parent = parent;
this.domNode = domNode;
}
public final XmlResourceStore getResourceStore()
{
return this.store;
}
public final XmlElement getParent()
{
return this.parent;
}
public Node getDomNode()
{
return this.domNode;
}
public final void validateEdit()
throws ValidateEditException
{
this.store.validateEdit();
}
@Override
public final boolean equals( final Object obj )
{
if( obj instanceof XmlNode )
{
return ( this.domNode == ( (XmlNode) obj ).domNode );
}
else
{
return false;
}
}
@Override
public final int hashCode()
{
return this.domNode.hashCode();
}
public abstract String getText();
public abstract void setText( String text );
@Override
public final String toString()
{
try
{
final StringWriter sw = new StringWriter();
final DOMSource source = new DOMSource( this.domNode );
final StreamResult result = new StreamResult( sw );
final TransformerFactory factory = TransformerFactory.newInstance();
final Transformer transformer = factory.newTransformer();
transformer.setOutputProperty( OutputKeys.OMIT_XML_DECLARATION, "yes" );
transformer.transform( source, result );
return sw.toString();
}
catch( Exception e )
{
e.printStackTrace();
return super.toString();
}
}
public final void format()
{
validateEdit();
removeFormatting();
int depth = 0;
if( this.domNode.getParentNode() != null )
{
for( Node n = this.domNode.getParentNode(); n.getNodeType() == Node.ELEMENT_NODE; n = n.getParentNode() )
{
depth++;
}
}
format( depth );
}
private void format( final int depth )
{
final StringBuilder buf = new StringBuilder();
buf.append( '\n' );
for( int i = 0; i < depth; i++ )
{
buf.append( " " );
}
final String formatting = buf.toString();
final Document document = this.domNode.getOwnerDocument();
final Node parent = this.domNode.getParentNode();
if( parent.getNodeType() == Node.ELEMENT_NODE ||
( parent.getNodeType() == Node.DOCUMENT_NODE && this.domNode.getPreviousSibling() != null ) )
{
final Text textBeforeOpeningTag = document.createTextNode( formatting );
parent.insertBefore( textBeforeOpeningTag, this.domNode );
}
if( this.domNode.getNodeType() == Node.ELEMENT_NODE && this.domNode.getChildNodes().getLength() > 0 )
{
final Text textBeforeClosingTag = document.createTextNode( formatting );
this.domNode.appendChild( textBeforeClosingTag );
}
if( this instanceof XmlElement )
{
final int depthPlusOne = depth + 1;
final XmlElement element = (XmlElement) this;
for( XmlNode child : element.getChildElements() )
{
child.format( depthPlusOne );
}
for( XmlNode child : element.getComments() )
{
child.format( depthPlusOne );
}
}
}
public final void removeFormatting()
{
validateEdit();
final NodeList nodes = this.domNode.getChildNodes();
final List<Node> textNodesToRemove = new ArrayList<Node>();
for( int i = 0, n = nodes.getLength(); i < n; i++ )
{
final Node child = nodes.item( i );
if( child.getNodeType() == Node.TEXT_NODE )
{
if( child.getNodeValue().trim().length() == 0 )
{
textNodesToRemove.add( child );
}
}
}
for( Node n : textNodesToRemove )
{
this.domNode.removeChild( n );
}
if( this instanceof XmlElement )
{
final XmlElement element = (XmlElement) this;
for( XmlNode child : element.getChildElements() )
{
child.removeFormatting();
}
for( XmlNode child : element.getComments() )
{
child.removeFormatting();
}
}
final Node prevSibling = this.domNode.getPreviousSibling();
if( prevSibling != null && prevSibling.getNodeType() == Node.TEXT_NODE )
{
if( prevSibling.getNodeValue().trim().length() == 0 )
{
this.domNode.getParentNode().removeChild( prevSibling );
}
}
}
public abstract void remove();
public final void addListener( final Listener listener )
{
if( listener == null )
{
throw new IllegalArgumentException();
}
final List<Listener> listeners = new ArrayList<Listener>();
if( this.listeners != null )
{
listeners.addAll( this.listeners );
}
listeners.add( listener );
this.listeners = listeners;
}
public final void removeListener( final Listener listener )
{
if( this.listeners != null && this.listeners.contains( listener ) )
{
if( this.listeners.size() == 1 )
{
this.listeners = null;
}
else
{
final List<Listener> listeners = new ArrayList<Listener>();
for( Listener x : this.listeners )
{
if( ! x.equals( listener ) )
{
listeners.add( x );
}
}
this.listeners = listeners;
}
}
}
protected final void notifyListeners( final Event event )
{
if( this.listeners != null )
{
for( Listener listener : this.listeners )
{
try
{
listener.handle( event );
}
catch( Exception e )
{
Sapphire.service( LoggingService.class ).log( e );
}
}
}
}
public enum EventType
{
PRE_CHILD_ELEMENT_ADD,
POST_CHILD_ELEMENT_ADD,
PRE_CHILD_ELEMENT_REMOVE,
POST_CHILD_ELEMENT_REMOVE
}
public static class Event
{
private final EventType type;
private final XmlNode node;
public Event( final EventType type,
final XmlNode node )
{
this.type = type;
this.node = node;
}
public EventType getType()
{
return this.type;
}
public XmlNode getNode()
{
return this.node;
}
}
public static abstract class Listener
{
public abstract void handle( Event event );
}
}