Package org.eclipse.sapphire

Source Code of org.eclipse.sapphire.LocalizableText

/******************************************************************************
* Copyright (c) 2014 Oracle and Other Contributors
* 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
*    IBM - implementation inspired by org.eclipse.osgi.util.NLS
******************************************************************************/

package org.eclipse.sapphire;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;

import org.eclipse.sapphire.internal.ValueSnapshot;
import org.eclipse.sapphire.services.ValueLabelService;

/**
* Represents a string that can be localized by substituting a translated version based on
* the current locale. Instances of LocalizableText are held as static fields in a class and must be
* initialized by calling the init method on the class.
*
* <p>The text lookup searches resource files from most specific to most generic, based on the
* current locale. When looking at the resource files, the key is the field name. If a corresponding
* resource is not found, the text specified by the @Text annotation is used.</p>
*
* <p>In the following example, two LocalizableText fields are used to implement a simple validator.</p>
*
* <pre><code> public class Validator
* {
*     @Text( "Value must be specified." )
*     private static LocalizableText mustBeSpecifiedMessage;
*    
*     @Text( "Value must not be larger than {0}." )
*     private static LocalizableText mustNotBeLargerThanMessage;
*    
*     static
*     {
*         LocalizableText.init( ExampleValidationService.class );
*     }
*    
*     public String validate( Integer value, Integer max )
*     {
*         if( value == null )
*         {
*             return mustBeSpecifiedMessage.text();
*         }
*         else if( max != null && value.intValue() > max.intValue() )
*         {
*             return mustNobeLargerThanMessage.format( max );
*         }
*        
*         return null;
*     }
* }</code></pre>
*
* <p>If the locale is set to en_US, the search order will be as follows:</p>
*
* <ol>
*   <li>org/eclipse/example/Validator_en_US.properties</li>
*   <li>org/eclipse/example/Validator_en.properties</li>
*   <li>org/eclipse/example/Validator.properties</li>
*   <li>@Text annotation on the field</li>
* </ol>
*
* @author <a href="mailto:konstantin.komissarchik@oracle.com">Konstantin Komissarchik</a>
*/

public final class LocalizableText
{
    private static final String EXTENSION = ".properties";
    private static String[] suffixes;

    private final String text;
   
    /**
     * Initializes all LocalizableText static fields present in the specified class. The semantics of this
     * method are equivalent to calling <code>init( clazz, null )</code>.
     *
     * @param clazz the class that contains static LocalizableText fields to initialize
     * @throws IllegalArgumentException if clazz is null
     */
   

    public static void init( final Class<?> clazz )
    {
        init( clazz, null );
    }

    /**
     * Initializes all LocalizableText static fields present in the specified class.
     *
     * @param clazz the class that contains static LocalizableText fields to initialize
     * @param resource the qualified base name of a resource file; defaults to class name if not specified
     * @throws IllegalArgumentException if clazz is null
     */
   
    public static void init( final Class<?> clazz, final String resource )
    {
        if( clazz == null )
        {
            throw new IllegalArgumentException();
        }
       
        final String resourceBaseName = ( resource == null ? clazz.getName() : resource );
       
        if( System.getSecurityManager() == null )
        {
            load( clazz, resourceBaseName );
        }
        else
        {
            AccessController.doPrivileged
            (
                new PrivilegedAction<Object>()
                {
                    public Object run()
                    {
                        load( clazz, resourceBaseName );
                        return null;
                    }
                }
            );
        }
    }
   
    private static void load( final Class<?> clazz, final String resource )
    {
        final Map<String,Field> fields = new HashMap<String,Field>();
       
        for( final Field field : clazz.getDeclaredFields() )
        {
            if( ( field.getModifiers() & Modifier.STATIC ) != 0 && field.getType() == LocalizableText.class )
            {
                field.setAccessible( true );
                fields.put( field.getName(), field );
            }
        }
       
        ClassLoader loader = clazz.getClassLoader();
       
        if( loader == null )
        {
            loader = ClassLoader.getSystemClassLoader();
        }
       
        if( suffixes == null )
        {
            // Build an array of the relevant property file suffixes in the order from most specific to most generic.
            // For instance, in FR_fr locale, the array will contain "_FR_fr.properties", then "_fr.properties" and
            // finally ".properties".
           
            String nl = Locale.getDefault().toString();
            final List<String> result = new ArrayList<String>( 4 );
           
            while( true )
            {
                result.add( '_' + nl + EXTENSION );
               
                final int lastSeparator = nl.lastIndexOf( '_' );
               
                if( lastSeparator == -1 )
                {
                    break;
                }
               
                nl = nl.substring(0, lastSeparator);
            }
           
            result.add( EXTENSION );
           
            suffixes = result.toArray( new String[ result.size() ] );
        }
       
        final String root = resource.replace( '.', '/' );
   
        for( final String suffix : suffixes )
        {
            final String variant = root + suffix;
            final InputStream input = loader.getResourceAsStream( variant );
           
            if( input != null )
            {
                Properties properties = null;
               
                try
                {
                    properties = new Properties();
                    properties.load( input );
                }
                catch( IOException e )
                {
                    System.err.println( "Error loading " + variant );
                    e.printStackTrace();
                }
                finally
                {
                    try
                    {
                        input.close();
                    }
                    catch( IOException e ) {}
                }
               
                if( properties != null )
                {
                    for( final Map.Entry<Object,Object> entry : properties.entrySet() )
                    {
                        final String key = (String) entry.getKey();
                        final Field field = fields.get( key );
                       
                        if( field == null )
                        {
                            final String msg = "Unused message: " + key + " in: " + resource;
                            System.err.println( msg );
                        }
                        else
                        {
                            try
                            {
                                if( field.get( null ) == null )
                                {
                                    // Extra care is taken to be sure we create a String with its own backing char[] (bug 287183)
                                    // This is to ensure we do not keep the key chars in memory.
                                   
                                    field.set( null, new LocalizableText( new String( ( (String) entry.getValue() ).toCharArray() ) ) );
                                }
                            }
                            catch( Exception e )
                            {
                                System.err.println( "Exception setting field " + field.getName() );
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        }
       
        for( final Field field : fields.values() )
        {
            try
            {
                if( field.get( null ) == null )
                {
                    final Text annotation = field.getAnnotation( Text.class );
                    final String value;

                    if( annotation != null )
                    {
                        value = annotation.value();
                    }
                    else
                    {
                        value = "Missing message: " + field.getName() + " in: " + resource;
                    }
                   
                    field.set( null, new LocalizableText( value ) );
                }
            }
            catch( Exception e )
            {
                System.err.println( "Exception setting field " + field.getName() );
                e.printStackTrace();
            }
        }
    }
   
    /**
     * The constructor must not be called directly. Instances are created by calling {@link #init(Class)}.
     */

    private LocalizableText( final String text )
    {
        this.text = text;
    }
   
    /**
     * Returns the raw text as it was found during initialization.
     *
     * @return the raw text
     */
   
    public String text()
    {
        return this.text;
    }
   
    /**
     * Formats a message using this text as a template. The semantics of this method are equivalent
     * to {@link MessageFormat#format( String template, Object... bindings )}.
     *
     * @param bindings the bindings to substitute into the template
     * @return the formatted message
     */
   
    public String format( final Object... bindings )
    {
        if( bindings == null || bindings.length == 0 )
        {
            return this.text;
        }
       
        boolean foundValueInBindings = false;
       
        for( final Object binding : bindings )
        {
            if( binding instanceof Value || binding instanceof ValueSnapshot )
            {
                foundValueInBindings = true;
                break;
            }
        }
       
        final Object[] objects;
       
        if( foundValueInBindings )
        {
            objects = new Object[ bindings.length ];
           
            for( int i = 0; i < bindings.length; i++ )
            {
                final Object binding = bindings[ i ];
               
                if( binding instanceof Value )
                {
                    final Value<?> value = (Value<?>) binding;
                    objects[ i ] = format( value.definition(), value.text() );
                }
                else if( binding instanceof ValueSnapshot )
                {
                    final ValueSnapshot snapshot = (ValueSnapshot) binding;
                    objects[ i ] = format( snapshot.property(), snapshot.text() );
                }
                else
                {
                    objects[ i ] = binding;
                }
            }
        }
        else
        {
            objects = bindings;
        }
       
        return MessageFormat.format( this.text, objects );
    }
   
    private static String format( final ValueProperty property, final String text )
    {
        String formatted = property.service( ValueLabelService.class ).provide( text );
       
        if( ! ( property.isOfType( Byte.class ) ||
                property.isOfType( Short.class ) ||
                property.isOfType( Integer.class ) ||
                property.isOfType( Long.class ) ||
                property.isOfType( Float.class ) ||
                property.isOfType( Double.class ) ||
                property.isOfType( BigInteger.class ) ||
                property.isOfType( BigDecimal.class ) ||
                property.isOfType( Boolean.class ) ) )
        {
            formatted = "\"" + formatted + "\"";
        }
       
        return formatted;
    }
   
    /**
     * Returns the raw text as it was found during initialization. The semantics of this method
     * are equivalent to {@link #text()}.
     *
     * @return the raw text
     */
   
    @Override
    public String toString()
    {
        return this.text;
    }
   
}
TOP

Related Classes of org.eclipse.sapphire.LocalizableText

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.