/****************************************************************************** * 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; import java.util.Set; import org.eclipse.sapphire.internal.NonSuspendableListener; import org.eclipse.sapphire.modeling.ElementPropertyBinding; import org.eclipse.sapphire.modeling.ModelPath; import org.eclipse.sapphire.modeling.ModelPath.AllDescendentsSegment; import org.eclipse.sapphire.modeling.ModelPath.PropertySegment; import org.eclipse.sapphire.modeling.ModelPath.TypeFilterSegment; /** * @author <a href="mailto:konstantin.komissarchik@oracle.com">Konstantin Komissarchik</a> */ public final class ElementHandle<T extends Element> extends Property { private T content; public ElementHandle( final Element element, final ElementProperty property ) { super( element, property ); } /** * Returns a reference to ElementHandle.class that is parameterized with the given type. * * <p>Example:</p> * * <p><code>Class<ElementHandle<Item>> cl = ElementHandle.of( Item.class );</code></p> * * @param type the type * @return a reference to ElementHandle.class that is parameterized with the given type */ @SuppressWarnings( { "unchecked", "rawtypes" } ) public static <TX extends Element> Class<ElementHandle<TX>> of( final Class<TX> type ) { return (Class) ElementHandle.class; } @Override public void refresh() { synchronized( root() ) { init(); refreshContent( false ); if( this.content != null ) { this.content.refresh(); } refreshEnablement( false ); refreshValidation( false ); } } private void refreshContent( final boolean onlyIfNotInitialized ) { boolean initialized; synchronized( this ) { initialized = ( ( this.initialization & CONTENT_INITIALIZED ) != 0 ); } if( ! initialized || ! onlyIfNotInitialized ) { final ElementPropertyBinding binding = binding(); final Resource resourceAfter = binding.read(); final boolean proceed; synchronized( this ) { final Resource resourceBefore = ( this.content == null ? null : this.content.resource() ); initialized = ( ( this.initialization & CONTENT_INITIALIZED ) != 0 ); proceed = ( ! initialized || resourceBefore != resourceAfter ); } if( proceed ) { T contentBefore; T contentAfter = null; if( resourceAfter != null ) { final ElementType type = binding.type( resourceAfter ); contentAfter = type.instantiate( this, resourceAfter ); } PropertyContentEvent event = null; synchronized( this ) { contentBefore = this.content; this.content = contentAfter; if( initialized ) { event = new PropertyContentEvent( this ); } else { this.initialization |= CONTENT_INITIALIZED; } } if( contentBefore != null ) { try { contentBefore.dispose(); } catch( Exception e ) { Sapphire.service( LoggingService.class ).log( e ); } } broadcast( event ); } } } @Override public ElementProperty definition() { return (ElementProperty) super.definition(); } @Override protected ElementPropertyBinding binding() { return (ElementPropertyBinding) super.binding(); } @Override public void attach( final Listener listener, final ModelPath path ) { if( listener == null ) { throw new IllegalArgumentException(); } if( path == null ) { throw new IllegalArgumentException(); } synchronized( root() ) { assertNotDisposed(); if( path.length() > 0 ) { final ModelPath.Segment head = path.head(); if( head instanceof AllDescendentsSegment || head instanceof PropertySegment || head instanceof TypeFilterSegment ) { attach( listener ); if( ! ( definition() instanceof ImpliedElementProperty ) ) { attach( new PropagationListener( listener, path ) ); } final Element element = content(); if( element != null ) { element.attach( listener, path ); } return; } } super.attach( listener, path ); } } @Override public void detach( final Listener listener, final ModelPath path ) { if( listener == null ) { throw new IllegalArgumentException(); } if( path == null ) { throw new IllegalArgumentException(); } synchronized( root() ) { if( path.length() > 0 ) { final ModelPath.Segment head = path.head(); if( head instanceof AllDescendentsSegment || head instanceof PropertySegment || head instanceof TypeFilterSegment ) { detach( listener ); if( ! ( definition() instanceof ImpliedElementProperty ) ) { detach( new PropagationListener( listener, path ) ); } final Element element = content(); if( element != null ) { element.detach( listener, path ); } return; } } super.detach( listener, path ); } } /** * Returns the element contained by this property. * * @return the element contained by this property or null */ public T content() { return content( false ); } /** * Returns the element contained by this property, creating it if necessary. * * @param force controls whether the element should be created if the property is empty * @return the element contained by this property or null * @throws IllegalArgumentException if the set of possible types is greater than one */ public T content( final boolean force ) { return content( force, (ElementType) null ); } /** * Returns the element contained by this property, creating it if necessary. * * @param force controls whether the element should be created if the property is empty or it contains the wrong element type * @param type the desired element type * @return the element contained by this property or null * @throws IllegalArgumentException if the type is not among possible types * @throws IllegalArgumentException if a type is not provided and the set of possible types is greater than one * @throws IllegalArgumentException if force flag is set to false and the contained type differs from the desired type */ public T content( final boolean force, final ElementType type ) { init(); refreshContent( true ); final Set<ElementType> possible = service( PossibleTypesService.class ).types(); if( type != null && ! possible.contains( type ) ) { throw new IllegalArgumentException(); } if( force ) { ElementType t = type; if( t == null ) { if( possible.size() > 1 ) { throw new IllegalArgumentException(); } t = possible.iterator().next(); } final boolean create; synchronized( this ) { create = ( this.content == null || this.content.type() != t ); } if( create ) { binding().create( t ); refresh(); t.instantiate(); } } else { synchronized( this ) { if( this.content != null && type != null && this.content.type() != type ) { throw new IllegalArgumentException(); } } } return this.content; } /** * Returns the element contained by this property, creating it if necessary. * * @param force controls whether the element should be created if the property is empty or it contains the wrong element type * @param cl the class of the desired element type * @return the element contained by this property or null * @throws IllegalArgumentException if the class does not contain an element type * @throws IllegalArgumentException if the type is not among possible types * @throws IllegalArgumentException if a type is not provided and the set of possible types is greater than one * @throws IllegalArgumentException if force flag is set to false and the contained type differs from the desired type */ @SuppressWarnings( "unchecked" ) public <C extends Element> C content( final boolean force, final Class<C> cl ) { ElementType type = null; if( cl != null ) { type = ElementType.read( cl ); if( type == null ) { throw new IllegalArgumentException(); } } return (C) content( force, type ); } @Override public boolean empty() { synchronized( root() ) { init(); refreshContent( true ); if( definition() instanceof ImpliedElementProperty ) { for( Property property : this.content.properties() ) { if( ! property.empty() ) { return false; } } return true; } else { return ( this.content == null ); } } } @Override public void clear() { init(); refreshContent( true ); if( definition() instanceof ImpliedElementProperty ) { content().clear(); } else { binding().remove(); refresh(); } } @Override public void copy( final Element source ) { init(); refreshContent( true ); if( source == null ) { throw new IllegalArgumentException(); } if( definition().isReadOnly() ) { throw new UnsupportedOperationException(); } final Property p = source.property( (PropertyDef) definition() ); if( definition().getClass() == p.definition().getClass() ) { if( definition() instanceof ImpliedElementProperty ) { content().copy( ( (ElementHandle<?>) p ).content() ); } else { final Element sourceChildElement = ( (ElementHandle<?>) p ).content(); if( sourceChildElement == null ) { clear(); } else { final ElementType sourceChildElementType = sourceChildElement.type(); if( service( PossibleTypesService.class ).types().contains( sourceChildElementType ) ) { content( true, sourceChildElementType ).copy( sourceChildElement ); } } } } } @Override public void copy( final ElementData source ) { init(); refreshContent( true ); if( source == null ) { throw new IllegalArgumentException(); } if( definition().isReadOnly() ) { throw new UnsupportedOperationException(); } final Object content = source.read( name() ); boolean copied = false; if( content instanceof ElementData ) { final ElementData sourceChildElementData = (ElementData) content; final ElementType sourceChildElementType = sourceChildElementData.type(); if( service( PossibleTypesService.class ).types().contains( sourceChildElementType ) ) { if( definition() instanceof ImpliedElementProperty ) { content().copy( sourceChildElementData ); } else { content( true, sourceChildElementType ).copy( sourceChildElementData ); } copied = true; } } if( ! copied ) { clear(); } } @Override public String toString() { final T content = content(); return ( content == null ? "<null>" : content.toString() ); } @Override protected void disposeOther() { if( this.content != null ) { this.content.dispose(); this.content = null; } } private static final class PropagationListener extends FilteredListener<PropertyContentEvent> implements NonSuspendableListener { private final Listener listener; private final ModelPath path; public PropagationListener( final Listener listener, final ModelPath path ) { this.listener = listener; this.path = path; } @Override public boolean equals( final Object obj ) { if( obj instanceof PropagationListener ) { final PropagationListener pl = (PropagationListener) obj; return this.listener.equals( pl.listener ) && this.path.equals( pl.path ); } return false; } @Override public int hashCode() { return this.listener.hashCode() ^ this.path.hashCode(); } @Override protected void handleTypedEvent( final PropertyContentEvent event ) { final Element element = ( (ElementHandle<?>) event.property() ).content(); if( element != null ) { element.attach( this.listener, this.path ); } } } }