/****************************************************************************** * 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; import static org.eclipse.sapphire.modeling.util.MiscUtil.equal; import org.eclipse.sapphire.modeling.CapitalizationType; import org.eclipse.sapphire.modeling.annotations.Derived; import org.eclipse.sapphire.modeling.localization.LocalizationService; import org.eclipse.sapphire.services.ValueNormalizationService; /** * @author <a href="mailto:konstantin.komissarchik@oracle.com">Konstantin Komissarchik</a> */ public class Value<T> extends Property { private static final int DEFAULT_CONTENT_INITIALIZED = 1 << 4; private String text; private T content; private String defaultText; private T defaultContent; private boolean writing; public Value( final Element element, final ValueProperty property ) { super( element, property ); } /** * Returns a reference to Value.class that is parameterized with the given type. * * <p>Example:</p> * * <p><code>Class<Value<Integer>> cl = Value.of( Integer.class );</code></p> * * @param type the type * @return a reference to Value.class that is parameterized with the given type */ @SuppressWarnings( { "unchecked", "rawtypes" } ) public static <TX> Class<Value<TX>> of( final Class<TX> type ) { return (Class) Value.class; } @Override public final void refresh() { refresh( false ); } private void refresh( final boolean refactor ) { synchronized( root() ) { if( ! this.writing ) { init(); refreshContent( false, refactor ); refreshDefaultContent( false ); refreshEnablement( false ); refreshValidation( false ); } } } private void refreshContent( final boolean onlyIfNotInitialized ) { refreshContent( onlyIfNotInitialized, false ); } private void refreshContent( final boolean onlyIfNotInitialized, final boolean refactor ) { boolean initialized; synchronized( this ) { initialized = ( ( this.initialization & CONTENT_INITIALIZED ) != 0 ); } if( ! initialized || ! onlyIfNotInitialized ) { final ValueProperty p = definition(); String afterText; if( p.hasAnnotation( Derived.class ) ) { final DerivedValueService derivedValueService = service( DerivedValueService.class ); if( ! initialized ) { final Listener listener = new Listener() { @Override public void handle( final Event event ) { refreshContent( false ); } }; derivedValueService.attach( listener ); } afterText = derivedValueService.value(); } else { afterText = binding().read(); } afterText = normalize( service( ValueNormalizationService.class ).normalize( p.encodeKeywords( afterText ) ) ); final boolean proceed; synchronized( this ) { initialized = ( ( this.initialization & CONTENT_INITIALIZED ) != 0 ); proceed = ( ! initialized || ! equal( this.text, afterText ) ); } if( proceed ) { final T afterContent = parse( afterText ); PropertyContentEvent event = null; synchronized( this ) { final String beforeText = this.text; this.text = afterText; this.content = afterContent; if( initialized ) { event = new ValuePropertyContentEvent( this, beforeText, afterText, refactor ); } else { this.initialization |= CONTENT_INITIALIZED; } } broadcast( event ); } } } private void refreshDefaultContent( final boolean onlyIfNotInitialized ) { boolean initialized; synchronized( this ) { initialized = ( ( this.initialization & DEFAULT_CONTENT_INITIALIZED ) != 0 ); } if( ! initialized || ! onlyIfNotInitialized ) { final ValueProperty p = definition(); String afterText = null; final DefaultValueService defaultValueService = service( DefaultValueService.class ); if( defaultValueService != null ) { if( ! initialized ) { final Listener listener = new Listener() { @Override public void handle( final Event event ) { refreshDefaultContent( false ); } }; defaultValueService.attach( listener ); } afterText = defaultValueService.value(); if( afterText != null ) { afterText = normalize( service( ValueNormalizationService.class ).normalize( p.encodeKeywords( afterText ) ) ); } } final boolean proceed; synchronized( this ) { initialized = ( ( this.initialization & DEFAULT_CONTENT_INITIALIZED ) != 0 ); proceed = ( ! initialized || ! equal( this.defaultText, afterText ) ); } if( proceed ) { final T afterContent = parse( afterText ); PropertyDefaultEvent event = null; synchronized( this ) { this.defaultText = afterText; this.defaultContent = afterContent; if( initialized ) { event = new PropertyDefaultEvent( this ); } else { this.initialization |= DEFAULT_CONTENT_INITIALIZED; } } broadcast( event ); } } } @Override public final ValueProperty definition() { return (ValueProperty) super.definition(); } @Override protected final ValuePropertyBinding binding() { return (ValuePropertyBinding) super.binding(); } @Override public final boolean empty() { synchronized( root() ) { init(); refreshContent( true ); return ( this.text == null ); } } public final String text() { return text( true ); } public final String text( final boolean useDefaultValue ) { init(); refreshContent( true ); synchronized( this ) { if( this.text != null ) { return this.text; } } if( useDefaultValue ) { refreshDefaultContent( true ); synchronized( this ) { return this.defaultText; } } return null; } public final T content() { return content( true ); } public final T content( final boolean useDefaultValue ) { init(); refreshContent( true ); synchronized( this ) { if( this.content != null ) { return this.content; } } if( useDefaultValue ) { refreshDefaultContent( true ); synchronized( this ) { return this.defaultContent; } } return null; } public final String localized() { return localized( true ); } public final String localized( final boolean useDefaultValue ) { return localized( useDefaultValue, CapitalizationType.NO_CAPS, true ); } public final String localized( final CapitalizationType capitalizationType, final boolean includeMnemonic ) { return localized( true, capitalizationType, includeMnemonic ); } public final String localized( final boolean useDefaultValue, final CapitalizationType capitalizationType, final boolean includeMnemonic ) { final String sourceLangText = text( useDefaultValue ); if( sourceLangText != null ) { return element().adapt( LocalizationService.class ).text( sourceLangText, capitalizationType, includeMnemonic ); } return null; } /** * Returns parsed property content after running the actual property text through the localization service. * * <p>Equivalent to localizedContent( true )</p> * * @since 8.3 and 9.1 */ public final T localizedContent() { return localizedContent( true ); } /** * Returns parsed property content after running the actual property text through the localization service. * * @param useDefaultValue indicates whether the default value should be used if property content is null * @since 8.3 and 9.1 */ public final T localizedContent( final boolean useDefaultValue ) { return parse( localized( useDefaultValue ) ); } public final T getDefaultContent() { init(); refreshDefaultContent( true ); return this.defaultContent; } public final String getDefaultText() { init(); refreshDefaultContent( true ); return this.defaultText; } public final boolean malformed() { init(); refreshContent( true ); synchronized( this ) { if( this.text != null ) { return ( this.content == null ); } } refreshDefaultContent( true ); synchronized( this ) { return ( this.defaultText != null && this.defaultContent == null ); } } /** * Updates the value of the property. This method variant does not allow further model refactoring. * * @param content the new value for the property, either in typed form or as an equivalent string; null is allowed */ public final void write( final Object content ) { write( content, false ); } /** * Updates the value of the property. * * @param content the new value for the property, either in typed form or as an equivalent string; null is allowed * @param refactor indicates whether refactoring actions can be taken as the result of the property change * @throws UnsupportedOperationException is the property is not modifiable */ public final void write( final Object content, final boolean refactor ) { init(); if( definition().isReadOnly() ) { throw new UnsupportedOperationException(); } final ValueProperty p = definition(); String text = null; if( content != null ) { text = convertToText( content ); } text = normalize( service( ValueNormalizationService.class ).normalize( p.decodeKeywords( text ) ) ); if( ! equal( text( false ), text ) ) { synchronized( root() ) { this.writing = true; try { binding().write( text ); } finally { this.writing = false; } refresh( refactor ); } } } protected String convertToText( final Object content ) { final String text; if( content instanceof String ) { text = (String) content; } else { text = service( MasterConversionService.class ).convert( content, String.class ); if( text == null ) { throw new IllegalArgumentException(); } } return text; } @Override public final void clear() { write( null ); } @Override public final void copy( final Element source ) { if( source == null ) { throw new IllegalArgumentException(); } if( definition().isReadOnly() ) { throw new UnsupportedOperationException(); } final Property p = source.property( (PropertyDef) definition() ); if( p instanceof Value<?> ) { write( ( (Value<?>) p ).text( false ) ); } else { clear(); } } @Override public final void copy( final ElementData source ) { if( source == null ) { throw new IllegalArgumentException(); } if( definition().isReadOnly() ) { throw new UnsupportedOperationException(); } final Object content = source.read( name() ); if( content != null ) { write( content.toString() ); } else { clear(); } } @Override public boolean holds( final Element element ) { if( element == null ) { throw new IllegalArgumentException(); } return false; } @Override public boolean holds( final Property property ) { if( property == null ) { throw new IllegalArgumentException(); } return ( this == property ); } @Override public final String toString() { final String text = text( false ); return ( text == null ? "<null>" : text ); } @SuppressWarnings( "unchecked" ) private T parse( final String str ) { if( str == null ) { return null; } else { final ValueProperty p = definition(); return (T) service( MasterConversionService.class ).convert( p.decodeKeywords( str ), p.getTypeClass() ); } } private static String normalize( String str ) { if( str != null && str.length() == 0 ) { str = null; } return str; } }