/****************************************************************************** * 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 * Ling Hao - [376531] Need ability to distinguish between switch among heterogeneous elements ******************************************************************************/ package org.eclipse.sapphire.modeling.xml; import static org.eclipse.sapphire.modeling.util.MiscUtil.indexOf; import static org.eclipse.sapphire.modeling.xml.XmlUtil.contains; import static org.eclipse.sapphire.modeling.xml.XmlUtil.createQualifiedName; import static org.eclipse.sapphire.modeling.xml.XmlUtil.equal; import java.util.Set; import javax.xml.namespace.QName; import org.eclipse.sapphire.Element; import org.eclipse.sapphire.ElementType; import org.eclipse.sapphire.Event; import org.eclipse.sapphire.Listener; import org.eclipse.sapphire.LocalizableText; import org.eclipse.sapphire.PossibleTypesService; import org.eclipse.sapphire.Property; import org.eclipse.sapphire.PropertyDef; import org.eclipse.sapphire.Resource; import org.eclipse.sapphire.Text; import org.eclipse.sapphire.modeling.LayeredElementBindingImpl; import org.eclipse.sapphire.modeling.xml.annotations.XmlBinding; import org.eclipse.sapphire.modeling.xml.annotations.XmlElementBinding; /** * @author <a href="mailto:konstantin.komissarchik@oracle.com">Konstantin Komissarchik</a> */ public class StandardXmlElementBindingImpl extends LayeredElementBindingImpl { @Text( "{0}.{1} : {2}" ) private static LocalizableText failure; @Text( "Element name must be specified in @XmlElementBinding.Mapping annotation." ) private static LocalizableText mustSpecifyElementNameMsg; static { LocalizableText.init( StandardXmlElementBindingImpl.class ); } private PossibleTypesService possibleTypesService; private Listener possibleTypesServiceListener; private XmlPath path; private QName[] xmlElementNames; private ElementType[] modelElementTypes; @Override public void init( final Property property ) { super.init( property ); this.possibleTypesService = property.service( PossibleTypesService.class ); this.possibleTypesServiceListener = new Listener() { @Override public void handle( final Event event ) { initBindingMetadata(); } }; this.possibleTypesService.attach( this.possibleTypesServiceListener ); initBindingMetadata(); } private void initBindingMetadata() { final Element element = property().element(); final PropertyDef property = property().definition(); try { final XmlElementBinding xmlElementBindingAnnotation = property.getAnnotation( XmlElementBinding.class ); final XmlNamespaceResolver xmlNamespaceResolver = ( (XmlResource) element.resource() ).getXmlNamespaceResolver(); final Set<ElementType> possible = this.possibleTypesService.types(); this.modelElementTypes = possible.toArray( new ElementType[ possible.size() ] ); if( xmlElementBindingAnnotation == null ) { final XmlBinding xmlBindingAnnotation = property.getAnnotation( XmlBinding.class ); if( xmlBindingAnnotation != null && possible.size() == 1 ) { final String path = xmlBindingAnnotation.path(); final int slashIndex = path.lastIndexOf( '/' ); if( slashIndex == -1 ) { this.xmlElementNames = new QName[] { createQualifiedName( path, xmlNamespaceResolver ) }; } else if( slashIndex > 0 && slashIndex < path.length() - 1 ) { this.path = new XmlPath( path.substring( 0, slashIndex ), xmlNamespaceResolver ); this.xmlElementNames = new QName[] { createQualifiedName( path.substring( slashIndex + 1 ), xmlNamespaceResolver ) }; } } if( this.xmlElementNames == null ) { this.path = new XmlPath( property.name(), ( (XmlResource) element.resource() ).getXmlNamespaceResolver() ); this.xmlElementNames = new QName[ this.modelElementTypes.length ]; for( int i = 0; i < this.modelElementTypes.length; i++ ) { this.xmlElementNames[ i ] = createDefaultElementName( this.modelElementTypes[ i ], xmlNamespaceResolver ); } } } else { if( xmlElementBindingAnnotation.path().length() > 0 ) { this.path = new XmlPath( xmlElementBindingAnnotation.path(), xmlNamespaceResolver ); } final XmlElementBinding.Mapping[] mappings = xmlElementBindingAnnotation.mappings(); this.xmlElementNames = new QName[ this.modelElementTypes.length ]; for( int i = 0; i < this.modelElementTypes.length; i++ ) { final ElementType type = this.modelElementTypes[ i ]; for( XmlElementBinding.Mapping mapping : mappings ) { if( mapping.type() == type.getModelElementClass() ) { final String mappingElementName = mapping.element().trim(); if( mappingElementName.length() == 0 ) { throw new RuntimeException( mustSpecifyElementNameMsg.text() ); } this.xmlElementNames[ i ] = createQualifiedName( mappingElementName, xmlNamespaceResolver ); break; } } if( this.xmlElementNames[ i ] == null ) { this.xmlElementNames[ i ] = createDefaultElementName( type, xmlNamespaceResolver ); } } } } catch( Exception e ) { final String msg = failure.format( element.type().getSimpleName(), property.name(), e.getMessage() ); throw new RuntimeException( msg, e ); } } /** * Creates the XML element name for a type that does not have an explicit mapping. This method can be * overridden to provide custom behavior. * * @param type the model element type * @param xmlNamespaceResolver the resolver of XML namespace suffixes to declared namespaces * @return the qualified XML element name for the given model element type */ protected QName createDefaultElementName( final ElementType type, final XmlNamespaceResolver xmlNamespaceResolver ) { return XmlUtil.createDefaultElementName( type ); } @Override public ElementType type( final Resource resource ) { final XmlElement xmlElement = ( (XmlResource) resource ).getXmlElement(); final QName xmlElementName = createQualifiedName( xmlElement.getDomNode() ); final String xmlElementNamespace = xmlElementName.getNamespaceURI(); for( int i = 0; i < this.xmlElementNames.length; i++ ) { if( equal( this.xmlElementNames[ i ], xmlElementName, xmlElementNamespace ) ) { return this.modelElementTypes[ i ]; } } throw new IllegalStateException(); } @Override protected Object readUnderlyingObject() { final XmlElement parent = parent( false ); if( parent != null ) { for( XmlElement element : parent.getChildElements() ) { final QName xmlElementName = createQualifiedName( element.getDomNode() ); if( contains( this.xmlElementNames, xmlElementName, xmlElementName.getNamespaceURI() ) ) { return element; } } } return null; } @Override protected Object createUnderlyingObject( final ElementType type ) { final XmlElement base = base( false ); if( base != null ) { final XmlElement parent = parent( false ); if( parent != null ) { for( XmlElement element : parent.getChildElements() ) { final QName xmlElementName = createQualifiedName( element.getDomNode() ); if( contains( this.xmlElementNames, xmlElementName, xmlElementName.getNamespaceURI() ) ) { element.remove(); } } } } final XmlElement parent = parent( true ); QName xmlElementName = this.xmlElementNames[ indexOf( this.modelElementTypes, type ) ]; if( xmlElementName.getNamespaceURI().equals( "" ) ) { xmlElementName = new QName( parent.getNamespace(), xmlElementName.getLocalPart() ); } return parent.getChildElement( xmlElementName, true ); } @Override protected Resource createResource( final Object obj ) { final XmlElement xmlElement = (XmlElement) obj; final XmlResource parentXmlResource = (XmlResource) property().element().resource(); return new ChildXmlResource( parentXmlResource, xmlElement ); } @Override public void remove() { final XmlElement base = base( false ); if( base != null ) { final XmlElement parent = parent( false ); if( parent != null ) { for( XmlElement element : parent.getChildElements() ) { final QName xmlElementName = createQualifiedName( element.getDomNode() ); if( contains( this.xmlElementNames, xmlElementName, xmlElementName.getNamespaceURI() ) ) { element.remove(); } } if( parent != base && parent.isEmpty() ) { base.removeChildNode( this.path ); } } } } protected XmlElement parent( final boolean createIfNecessary ) { XmlElement parent = base( createIfNecessary ); if( parent != null && this.path != null ) { parent = (XmlElement) parent.getChildNode( this.path, createIfNecessary ); } return parent; } protected XmlElement base( final boolean createIfNecessary ) { final XmlResource resource = (XmlResource) property().element().resource(); return resource.getXmlElement( createIfNecessary ); } @Override public void dispose() { super.dispose(); if( this.possibleTypesService != null ) { this.possibleTypesService.detach( this.possibleTypesServiceListener ); } } }