/****************************************************************************** * 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.el; import static org.eclipse.sapphire.util.CollectionsUtil.findIgnoringCase; import java.lang.reflect.Array; import java.util.List; import java.util.Map; import org.eclipse.sapphire.Event; import org.eclipse.sapphire.Listener; import org.eclipse.sapphire.LocalizableText; import org.eclipse.sapphire.ObservableMap; import org.eclipse.sapphire.Text; import org.eclipse.sapphire.modeling.internal.SapphireModelingExtensionSystem; import org.eclipse.sapphire.modeling.localization.LocalizationService; import org.eclipse.sapphire.modeling.localization.SourceLanguageLocalizationService; /** * @author <a href="mailto:konstantin.komissarchik@oracle.com">Konstantin Komissarchik</a> */ public class FunctionContext { @Text( "Property \"{0}\" is undefined." ) private static LocalizableText undefinedPropertyMessage; @Text( "Property \"{0}\" is undefined for {1} objects." ) private static LocalizableText undefinedPropertyMessageExt; @Text( "Cannot read properties from null object." ) private static LocalizableText cannotReadPropertiesFromNull; @Text( "Index {0} is outside the bounds of the collection." ) private static LocalizableText indexOutOfBounds; static { LocalizableText.init( FunctionContext.class ); } public FunctionResult property( final Object element, final String name ) { if( element == null ) { throw new FunctionException( cannotReadPropertiesFromNull.text() ); } else { if( element instanceof Map ) { final Map<?,?> map = (Map<?,?>) element; final Function f; if( map instanceof ObservableMap ) { final ObservableMap<?,?> observable = (ObservableMap<?,?>) map; f = new Function() { @Override public String name() { return name; } @Override public FunctionResult evaluate( final FunctionContext context ) { return new FunctionResult( this, context ) { private Listener listener; @Override protected void init() { this.listener = new Listener() { @Override public void handle( final Event event ) { refresh(); } }; observable.attach( this.listener ); } @Override protected Object evaluate() { return findIgnoringCase( observable, name ); } @Override public void dispose() { super.dispose(); observable.detach( this.listener ); } }; } }; } else { f = new Function() { @Override public String name() { return name; } @Override public FunctionResult evaluate( final FunctionContext context ) { return new FunctionResult( this, context ) { @Override protected Object evaluate() { return findIgnoringCase( map, name ); } }; } }; } f.init(); return f.evaluate( this ); } else { try { final int index = Integer.parseInt( name ); if( element instanceof List ) { final List<?> list = (List<?>) element; final Function f = new Function() { @Override public String name() { return name; } @Override public FunctionResult evaluate( final FunctionContext context ) { return new FunctionResult( this, context ) { @Override protected Object evaluate() { if( index >= 0 && index < list.size() ) { return list.get( index ); } else { throw new FunctionException( indexOutOfBounds.format( index ) ); } } }; } }; f.init(); return f.evaluate( this ); } else if( element.getClass().isArray() ) { final Function f = new Function() { @Override public String name() { return name; } @Override public FunctionResult evaluate( final FunctionContext context ) { return new FunctionResult( this, context ) { @Override protected Object evaluate() { if( index >= 0 && index < Array.getLength( element ) ) { return Array.get( element, index ); } else { throw new FunctionException( indexOutOfBounds.format( index ) ); } } }; } }; f.init(); return f.evaluate( this ); } } catch( NumberFormatException e ) { // Ignore. Non-integer property means call isn't trying to index into a collection. } } } if( element == this ) { if( ! SapphireModelingExtensionSystem.functions( name, 0 ).isEmpty() ) { final Function f = new DeferredFunction( name ); f.init( new Function[ 0 ] ); return f.evaluate( this ); } } else { if( ! SapphireModelingExtensionSystem.functions( name, 1 ).isEmpty() ) { final Function f = new DeferredFunction( name ); f.init( new Function[] { Literal.create( element ) } ); return f.evaluate( this ); } } if( element == this ) { throw new FunctionException( undefinedPropertyMessage.format( name ) ); } else { final Class<?> cl = element.getClass(); final String type; if( cl.isArray() ) { type = cl.getComponentType().getName() + "[]"; } else { type = cl.getName(); } throw new FunctionException( undefinedPropertyMessageExt.format( name, type ) ); } } public LocalizationService getLocalizationService() { return SourceLanguageLocalizationService.INSTANCE; } }