/*---------------- FILE HEADER ------------------------------------------
This file is part of deegree.
Copyright (C) 2001 by:
EXSE, Department of Geography, University of Bonn
http://www.giub.uni-bonn.de/exse/
lat/lon GmbH
http://www.lat-lon.de
It has been implemented within SEAGIS - An OpenSource implementation of OpenGIS specification
(C) 2001, Institut de Recherche pour le D�veloppement (http://sourceforge.net/projects/seagis/)
SEAGIS Contacts: Surveillance de l'Environnement Assist�e par Satellite
Institut de Recherche pour le D�veloppement / US-Espace
mailto:seasnet@teledetection.fr
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Contact:
Andreas Poth
lat/lon GmbH
Aennchenstr. 19
53115 Bonn
Germany
E-Mail: poth@lat-lon.de
Klaus Greve
Department of Geography
University of Bonn
Meckenheimer Allee 166
53115 Bonn
Germany
E-Mail: klaus.greve@uni-bonn.de
---------------------------------------------------------------------------*/
package org.deegree.model.csct.resources;
// Utilities
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.text.MessageFormat;
import java.util.Enumeration;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.NoSuchElementException;
/**
* {link java.util.ResourceBundle} implementation using integers instead of strings for
* resource keys. Because it doesn't use strings, this implementation avoid adding all
* those string constants to <code>.class</code> files and runtime images. Developpers
* still have meaningful labels in their code (e.g. <code>DIMENSION_MISMATCH</code>)
* through a set of constants defined in interfaces. This approach furthermore give the
* benefict of compile-time safety. Because integer constants are inlined right into
* class files at compile time, the declarative interface is never loaded at run time.
* <br><br>
* This class also provides facilities for string formatting using {@link MessageFormat}.
*
* @version 1.0
* @author Martin Desruisseaux
*/
public class ResourceBundle extends java.util.ResourceBundle {
/**
* Maximal string length for text inserted into an other text. This parameter
* is used by {@link #summarize}. Resource strings are never cut to this length.
* However, text replacing "{0}" in a string like "Parameter name is {0}." will
* be cut to this length.
*/
private static final int MAX_STRING_LENGTH = 80;
/**
* The resource name of the binary file containing resources.
* It is usually a file name, but may also be the name of an
* entry in a JAR file.
*/
private final String filename;
/**
* The array of resources. Keys are array index. For example the value for
* key "14" is <code>values[14]</code>. This array will be loaded only when
* first needed. We should not load it at construction time, because some
* <code>ResourceBundle</code> objects will never ask for values. This is
* case especially for ancestor classes of <code>Resources_fr_CA</code>,
* <code>Resources_en</code>, <code>Resources_de</code>, etc., which will
* be used only if a key has not been found in the subclasse.
*/
protected String[] values;
/**
* The object to use for formatting messages. This
* object will be constructed only when first needed.
*/
private transient MessageFormat format;
/**
* The key of the last resource requested. If the same resource is requested
* many consecutive time, knowing this fact allows to avoid invoking the costly
* {@link MessageFormat#applyPattern} method.
*/
private transient int lastKey;
/**
* Construct a new resource bundle.
*
* @param filename The resource name containing resources. It is
* usually a filename, but may also be an entry in a JAR file.
*/
protected ResourceBundle( final String filename ) {
this.filename = filename;
}
/**
* Returns the name of the logger to use. Default
* implementation returns the package name.
*/
protected String getLoggerName() {
final String name = getClass().getName();
final int index = name.lastIndexOf( '.' );
return ( index >= 0 ) ? name.substring( 0, index ) : "org.deegree.model";
}
/**
* List resources to the specified stream. If a resource has
* more than one line, only the first line will be written.
* This method is used mostly for debugging purpose.
*
* @param out The destination stream.
* @throws IOException if an output operation failed.
*/
public final synchronized void list( final Writer out )
throws IOException {
ensureLoaded( null );
list( out, 0, values.length );
}
/**
* List resources to the specified stream. If a resource has
* more than one line, only the first line will be written.
* This method is used mostly for debugging purpose.
*
* @param out The destination stream.
* @param lower The beginning index (inclusive).
* @param upper The ending index (exclusive).
* @throws IOException if an output operation failed.
*/
private void list( final Writer out, int lower, int upper )
throws IOException {
final String lineSeparator = System.getProperty( "line.separator", "\n" );
for ( int i = lower; i < upper; i++ ) {
String value = values[i];
if ( value == null )
continue;
int indexCR = value.indexOf( '\r' );
if ( indexCR < 0 )
indexCR = value.length();
int indexLF = value.indexOf( '\n' );
if ( indexLF < 0 )
indexLF = value.length();
final String number = String.valueOf( i );
out.write( Utilities.spaces( 5 - number.length() ) );
out.write( number );
out.write( ":\t" );
out.write( value.substring( 0, Math.min( indexCR, indexLF ) ) );
out.write( lineSeparator );
}
}
/**
* Ensure that resource values are loaded.
* If they are not, load them immediately.
*
* @param key Key for the requested resource, or <code>null</code>
* if all resources are requested. This key is used mostly
* for constructing messages.
* @throws MissingResourceException if this method failed to load resources.
*/
private void ensureLoaded( final String key )
throws MissingResourceException {
if ( values != null ) {
return;
}
/*
* Prepare a log record. We will wait for succesfull loading before to post this
* record. If loading fail, the record will be changed into an error record.
*/
try {
final InputStream in = getClass().getClassLoader().getResourceAsStream( filename );
if ( in == null ) {
throw new FileNotFoundException( filename );
}
final DataInputStream input = new DataInputStream( new BufferedInputStream( in ) );
values = new String[input.readInt()];
for ( int i = 0; i < values.length; i++ ) {
values[i] = input.readUTF();
if ( values[i].length() == 0 )
values[i] = null;
}
input.close();
} catch ( IOException exception ) {
final MissingResourceException error = new MissingResourceException(
exception.getLocalizedMessage(),
getClass().getName(),
key );
throw error;
}
}
/**
* Returns an enumeration of the keys.
*/
public final synchronized Enumeration getKeys() {
ensureLoaded( null );
return new Enumeration() {
private int i = 0;
public boolean hasMoreElements() {
while ( true ) {
if ( i >= values.length )
return false;
if ( values[i] != null )
return true;
i++;
}
}
public Object nextElement() {
while ( true ) {
if ( i >= values.length )
throw new NoSuchElementException();
if ( values[i] != null )
return String.valueOf( i++ );
i++;
}
}
};
}
/**
* Gets an object for the given key from this resource bundle.
* Returns null if this resource bundle does not contain an
* object for the given key.
*
* @param key the key for the desired object
* @exception NullPointerException if <code>key</code> is <code>null</code>
* @return the object for the given key, or null
*/
protected final synchronized Object handleGetObject( final String key ) {
ensureLoaded( key );
final int keyID;
try {
keyID = Integer.parseInt( key );
} catch ( NumberFormatException exception ) {
return null;
}
return ( keyID >= 0 && keyID < values.length ) ? values[keyID] : null;
}
/**
* Make sure that the <code>text</code> string is no longer than <code>maxLength</code>
* characters. If <code>text</code> is not longer, it is returned unchanged (except for
* trailing blancks, which are removed). If <code>text</code> is longer, it will be cut
* somewhere in the middle. This method try to cut between two words and replace the
* missing words with "(...)". For example, the following string:
*
* <blockquote>
* "This sentence given as an example is way too long to be included in a message."
* </blockquote>
*
* May be "summarized" by something like this:
*
* <blockquote>
* "This sentence given (...) included in a message."
* </blockquote>
*
* @param text The sentence to summarize if it is too long.
* @param maxLength The maximal length allowed for <code>text</code>.
* If <code>text</code> is longer, it will summarized.
* @return A sentence not longer than <code>text</code>.
*/
private static String summarize( String text, int maxLength ) {
text = text.trim();
final int length = text.length();
if ( length <= maxLength )
return text;
/*
* Compute maximum length for one half of the string. Take in
* account the space needed for inserting the " (...) " string.
*/
maxLength = ( maxLength - 7 ) >> 1;
if ( maxLength <= 0 )
return text;
/*
* We will remove characters from 'break1' to 'break2', both exclusive.
* We try to adjust 'break1' and 'break2' in such a way that first and
* last removed characters will be spaces or punctuation characters.
* Constants 'lower' and 'upper' are limit values. If we don't find values
* for 'break1' and 'break2' inside those limits, we will give it up.
*/
int break1 = maxLength;
int break2 = length - maxLength;
for ( final int lower = ( maxLength >> 1 ); break1 >= lower; break1-- ) {
if ( !Character.isUnicodeIdentifierPart( text.charAt( break1 ) ) ) {
while ( --break1 >= lower
&& !Character.isUnicodeIdentifierPart( text.charAt( break1 ) ) )
;
break;
}
}
for ( final int upper = length - ( maxLength >> 1 ); break2 < upper; break2++ ) {
if ( !Character.isUnicodeIdentifierPart( text.charAt( break2 ) ) ) {
while ( ++break2 < upper
&& !Character.isUnicodeIdentifierPart( text.charAt( break2 ) ) )
;
break;
}
}
return ( text.substring( 0, break1 + 1 ) + " (...) " + text.substring( break2 ) ).trim();
}
/**
* Returns <code>arguments</code> as an array. If <code>arguments</code> is already an
* array, this array or a copy of this array will be returned. If <code>arguments</code>
* is not an array, it will be wrapped in an array of length 1. In all case, all array's
* elements will be checked for {@link String} objects. Any strings of length greater than
* {@link #MAX_STRING_LENGTH} will be reduced using the {@link #summarize} method.
*
* @param arguments The object to check.
* @return <code>arguments</code> as an array.
*/
private static Object[] toArray( final Object arguments ) {
Object[] array;
if ( arguments instanceof Object[] ) {
array = (Object[]) arguments;
} else {
array = new Object[] { arguments };
}
for ( int i = 0; i < array.length; i++ ) {
{
final String s0 = array[i].toString();
final String s1 = summarize( s0, MAX_STRING_LENGTH );
if ( s0 != s1 && !s0.equals( s1 ) ) {
if ( array == arguments ) {
array = new Object[array.length];
System.arraycopy( arguments, 0, array, 0, array.length );
}
array[i] = s1;
}
}
}
return array;
}
/**
* Gets a string for the given key and append "..." to it.
* This is method is typically used for creating menu label.
*
* @param keyID The key for the desired string.
* @return The string for the given key.
* @throws MissingResourceException If no object for the given key can be found.
*/
public final String getMenuLabel( final int keyID )
throws MissingResourceException {
return getString( keyID ) + "...";
}
/**
* Gets a string for the given key and append ": " to it.
* This is method is typically used for creating menu label.
*
* @param keyID The key for the desired string.
* @return The string for the given key.
* @throws MissingResourceException If no object for the given key can be found.
*/
public final String getLabel( final int keyID )
throws MissingResourceException {
return getString( keyID ) + ": ";
}
/**
* Gets a string for the given key from this resource bundle or one of its parents.
*
* @param keyID The key for the desired string.
* @return The string for the given key.
* @throws MissingResourceException If no object for the given key can be found.
*/
public final String getString( final int keyID )
throws MissingResourceException {
return getString( String.valueOf( keyID ) );
}
/**
* Gets a string for the given key and format it with the specified argument.
* The message if formatted using {@link MessageFormat}. Calling his method is
* approximatively equivalent to calling:
*
* <blockquote><pre>
* String pattern = getString(key);
* Format f = new MessageFormat(pattern);
* return f.format(arg0);
* </pre></blockquote>
*
* If <code>arg0</code> is not already an array, it will be wrapped into an array
* of length 1. Using {@link MessageFormat}, all occurence of "{0}", "{1}", "{2}"
* in the resource string will be replaced by <code>arg0[0]</code>, <code>arg0[1]</code>,
* <code>arg0[2]</code>, etc.
*
* @param keyID The key for the desired string.
* @param arg0 A single object or an array of objects to be formatted and substituted.
* @return The string for the given key.
* @throws MissingResourceException If no object for the given key can be found.
*
* @see MessageFormat
*/
public final synchronized String getString( final int keyID, final Object arg0 )
throws MissingResourceException {
final Object object = getObject( String.valueOf( keyID ) );
final Object[] arguments = toArray( arg0 );
if ( format == null ) {
/*
* Construct a new {@link MessageFormat} for formatting the arguments. There is two
* possible {@link Locale} we could use: default locale or resource bundle locale.
* If the default locale use the same language than this <code>ResourceBundle</code>
* locale, then we will use the default locale. This allow formatting dates and numbers
* with user conventions (e.g. French Canada) even if the <code>ResourceBundle</code>
* locale is different (e.g. standard French). However, if languages don't match, then
* we will use <code>ResourceBundle</code> locale for better coherence.
*/
Locale locale = Locale.getDefault();
final Locale resourceLocale = getLocale();
if ( !locale.getLanguage().equalsIgnoreCase( resourceLocale.getLanguage() ) ) {
locale = resourceLocale;
}
} else if ( keyID != lastKey ) {
/*
* Method {@link MessageFormat#applyPattern} is costly! We will avoid
* calling it again if {@link #format} already has the right pattern.
*/
format.applyPattern( object.toString() );
lastKey = keyID;
}
return format.format( arguments );
}
/**
* Gets a string for the given key are replace all occurence of "{0}",
* "{1}", with values of <code>arg0</code>, <code>arg1</code>, etc.
*
* @param keyID The key for the desired string.
* @param arg0 Value to substitute to "{0}".
* @param arg1 Value to substitute to "{1}".
* @return The formatted string for the given key.
* @throws MissingResourceException If no object for the given key can be found.
*/
public final String getString( final int keyID, final Object arg0, final Object arg1 )
throws MissingResourceException {
return getString( keyID, new Object[] { arg0, arg1 } );
}
/**
* Gets a string for the given key are replace all occurence of "{0}",
* "{1}", with values of <code>arg0</code>, <code>arg1</code>, etc.
*
* @param keyID The key for the desired string.
* @param arg0 Value to substitute to "{0}".
* @param arg1 Value to substitute to "{1}".
* @param arg2 Value to substitute to "{2}".
* @return The formatted string for the given key.
* @throws MissingResourceException If no object for the given key can be found.
*/
public final String getString( final int keyID, final Object arg0, final Object arg1,
final Object arg2 )
throws MissingResourceException {
return getString( keyID, new Object[] { arg0, arg1, arg2 } );
}
/**
* Gets a string for the given key are replace all occurence of "{0}",
* "{1}", with values of <code>arg0</code>, <code>arg1</code>, etc.
*
* @param keyID The key for the desired string.
* @param arg0 Value to substitute to "{0}".
* @param arg1 Value to substitute to "{1}".
* @param arg2 Value to substitute to "{2}".
* @param arg3 Value to substitute to "{3}".
* @return The formatted string for the given key.
* @throws MissingResourceException If no object for the given key can be found.
*/
public final String getString( final int keyID, final Object arg0, final Object arg1,
final Object arg2, final Object arg3 )
throws MissingResourceException {
return getString( keyID, new Object[] { arg0, arg1, arg2, arg3 } );
}
/**
* Gets a string for the given key are replace all occurence of "{0}",
* "{1}", with values of <code>arg0</code>, <code>arg1</code>, etc.
*
* @param keyID The key for the desired string.
* @param arg0 Value to substitute to "{0}".
* @param arg1 Value to substitute to "{1}".
* @param arg2 Value to substitute to "{2}".
* @param arg3 Value to substitute to "{3}".
* @param arg4 Value to substitute to "{4}".
* @return The formatted string for the given key.
* @throws MissingResourceException If no object for the given key can be found.
*/
public final String getString( final int keyID, final Object arg0, final Object arg1,
final Object arg2, final Object arg3, final Object arg4 )
throws MissingResourceException {
return getString( keyID, new Object[] { arg0, arg1, arg2, arg3, arg4 } );
}
/**
* Returns a string representation of this object.
* This method is for debugging purpose only.
*/
public synchronized String toString() {
final StringBuffer buffer = new StringBuffer( Utilities.getShortClassName( this ) );
buffer.append( '[' );
if ( values != null ) {
int count = 0;
for ( int i = 0; i < values.length; i++ )
if ( values[i] != null )
count++;
buffer.append( count );
buffer.append( " values" );
}
buffer.append( ']' );
return buffer.toString();
}
}