Monday, April 22, 2013

Overview JSR 354 (Money and Currency) Core API

What is JSR 354?

JSR 354 is the upcoming standard, how money and currencies should be modelled and handled in Java. You can find more details here:

Why this blog?

Within JSR 354 we had a lot of discussions. I wanted to show with some examples how this API feels. And finally, if someone has input or ideas, we want to be aware of, so we finally build the right API !


Dealing with Currencies

Basic Design Decisions

The JSR basically models many of the key artifacts using interfaces. Nevertheless we provide concrete value types implementing the interfaces which in concrete code are referenced. The interfaces typically are for interoperability:
  • CurrencyUnit and MonetaryAmout are modelled by interfaces for interoperability.
  • The classes MoneyCurrency and Money implement these interfaces.
  • In the case of MoneyCurrency, also an according MoneyCurrency.Builder is defined.
This will be shown in more detail within the following sections.

Interface javax.money.CurrencyUnit

Basically the interface directly models the same aspects as available on the existing java.util.Currency class, but adds some additional methods to support additional aspects not covered by java.util.Currency. Some of the considerations are:
  • not renaming existing methods of java.util.Currency enables maximal backward compatibility and makes it more easy to let implement java.util.Currency the new interface for interoperability. This would also allow existing code to remain as is, if none of the extended features are required.
  • the other methods added should be the minimum required and be basically easily implementable, If feasible, also undefined values should be possible as method results. 
  • In the area of currencies there are a couple of complexities that were not obvious at a first glance, but can not be neglected, if currency should model the reality at least to some extent. For example think on the following aspects:
    • currencies come and go during human history. Even during the last years there were quite important changes that requires that historic and current currencies can be distinguished.
    • ISO-4217 currency codes also have some imminent aspects to be considered:
      • ISO codes (the identifiers!) are not guaranteed over time, the can be reused after some defined time.
      • Rounding modes and fraction digits can change during time, even when the currency is still the same.
      • Historic currencies are not mapped by ISO at all.
      • ISO also maps things that are not effectively currencies, like precious metals (e.g. AUG), no currency (XXX) or testing codes.
      • ISO also is ambiguous  e.g. CFA is a code that basically is backed up by two different bank notes.
  • the biggest change is that we introduced an additional namespace on top of the currency code, because:
    • namespaces can be used to separate concerns. This makes sense since ISO currencies are real currencies, whereas Social or Video Game Currencies are completely virtual. BitCoin even is more special, since it started as a virtual currency but lately is accepted more and more as real currency.
    • namespaces also allow to manage legacy currency schemes as they are in use by all financial organizations that must deal with money in a time range and scope that is longer than 10 years.
    • One might argue, that we could simply extend the existing currency code. But this also has some severe drawbacks:
      • the existing currency code of the JDK class can not be adapted correspondingly  since this would break behavioral compatibility. But if the code can not be extended, we would have currencies with a namespace prefix (all non ISO), and ones without (ISO, as before).
      • Additionally the JSR should definitely not impose anything on how currency codes will be defined in the future. But when extending the existing currency code with some optional namespace, an according separation criteria must be defined. Obviously this could easily clash with future namespace or code identifiers.
So summarizing a currency must implement the following interface:

public interface public CurrencyUnit{
  public String getNamespace(); // new
  public String getCurrencyCode();
  public int getNumericCode();
  public int getDefaultFractionDigits();
  public boolean isVirtual(); // new
  public boolean isLegalTender(); // new
  public Long getValidFrom(); // new
  public Long getValidUntil(); // new
}


There are a couple of remarks:
  • the numeric code, if not defined, should be -1.
  • the timestamps are modelled as Long due to the following reasons:
    • the JSR wants to be backward compatible with SE 7 (which will also cover SE6).
    • SE 8 is not yet final.
    • Long can be null, which means not defined. This is also the correct value for current currency instances.
    • UTC timestamps are commonly understood and well supported by all kind of time and date frameworks. They can easily converted to any other kind of objects required. Note: there is quite discussion ongoing, if the new JSR 310 APIs should be used here. We require definitively here better information, if we can go for the new date and time types, without preventing usage of the new API for years.

Class javax.money.MoneyCurrency

This class is the implementation of CurrencyUnit, also including an according Builder and an internal cache for reusing instances created. It is also where, by default, in the new API ISO currencies can be accessed:

MoneyCurrency currency = MoneyCurrency.of("USD");

For ISO currencies this will work out of the box, since the ISO currencies are implicitly backed up by java.util.Currency. Also the call above does implicitly add 'ISO-4217' as namespace, so basically the call below is aequivalent:

MoneyCurrency currency = MoneyCurrency.of("ISO-4217", "USD");

Defining alternate MoneyCurrency instances and namespaces can be done using the MoneyCurrency.Builder:


MoneyCurrency.Builder builder = new MoneyCurrency.Builder();
builder.setNamespace("myNamespace");
builder.setCurrencyCode("myCode");
builder.setDefaultFractionDigits(4);
builder.setLegalTender(false);
builder.setVirtual(true);
builder.setAttribute("test-only", true);
MoneyCurrency unit = builder.build();
// however MoneyCurrency.of("myNamespace", "myCode"); 
// still returns null!
builder.build(true);
// no it is registered
unit = MoneyCurrency.of("myNamespace", "myCode");

Extended API: javax.money.ext.MonetaryCurrencies

This singleton class allow to access currencies in a more service like fashion as required for more advanced use cases, such as accessing historic currencies or mapping of currencies between or within namespaces. Also it is possible to access all currencies within a namespace. Extended functionality is not part of the JSR's platform part and therefore is designed as standalone module, usable in multiple usage contexts (SE, EE etc):


public final class MonetaryCurrencies {

public static boolean isNamespaceDefined(String namespace){..}
public static Collection<String> getNamespaces(){..}
public static CurrencyUnit get(String namespace,
                                 String code)
{..}
public static Collection<CurrencyUnit> getAll(
                                           String namespace){..}
public static boolean isDefined(String code){..}
public static boolean isDefined(String namespace, 
                                  String code){..}
public static Collection<CurrencyUnit> getAll(String namespace,
                                                String code)
{..}
public static Collection<CurrencyUnit> getAll(String code){..}

  // historic access
public static boolean isNamespaceDefined(String namespace
                                           Long timestamp){..}
public static Collection<String> getNamespaces(
                                           Long timestamp){..}
public static CurrencyUnit get(String namespace, String code,
                                 Long timestamp)
{..}
public static CurrencyUnit get(String code, Long timestamp){..}
public static Collection<CurrencyUnit> getAll(String namespace,
                                              Long timestamp)
{..}
public static boolean isDefined(String namespace, String code,
                                  Long timestamp)
{..}
public static boolean isDefined(String code, 
                                  Long timestamp){..}
public static Collection<CurrencyUnit> getAll(Locale locale, 
                                             Long timestamp){..}
  // mapping of currrencies
  public static CurrencyUnit map(CurrencyUnit unit, 
                                 String targetNamespace);
public static List<CurrencyUnit> mapAll(String targetNamespace, 
                                          CurrencyUnit... units);
public static CurrencyUnit map(CurrencyUnit unit, 
                                 String targetNamespace, 
                                 Long timestamp);
public static List CurrencyUnit> mapAll(String targetNamespace,  
                                          Long timestamp, 
                                          CurrencyUnit... units);
}

The singleton's implementation itself can be determined by registering an instance of MonetaryCurrencies.MonetaryCurrenciesSpi using java.util.ServiceLoader.

Monetary Amounts

Some Basic Design Decisions

Monetary amounts basically follow the same design as CurrencyUnit::
  • MonetaryAmout is modelled by interface for interoperability.
  • Aims look and feel from java.math.BigDecimal 
  • Money implements the MonetaryAmout interface, using java.math.BigDecimal for numeric representation.

Interface javax.money.MonetaryAmount

Basically the interface models similar aspects as java.math.BigDecimal, but targeting monetary amounts:

public interface MonetaryAmount{
  CurrencyUnit getCurrency()
  MonetaryAmount add(MonetaryAmount);
  MonetaryAmount subtract(MonetaryAmount);
  
MonetaryAmount multiply(MonetaryAmount);    
  MonetaryAmount multiply(Number);
  MonetaryAmount divide(MonetaryAmount);
  MonetaryAmount divide(Number);
  MonetaryAmount remainder(MonetaryAmount);
  MonetaryAmount remainder(Number);
  MonetaryAmount scaleByPowerOfTen(int);  
  MonetaryAmount abs();    
  MonetaryAmount negate();
  
  MonetaryAmount pow(int); 
  MonetaryAmount ulp(); // unit in the last place  
  boolean isGreater(MonetaryAmount); 
  boolean isGreaterOrEquals(MonetaryAmount);  
  boolean isLess(MonetaryAmount), 
  boolean isLessOrEquals(MonetaryAmount);
  ...
  boolean isEqualTo(MonetaryAmount);  
  boolean isNotEqualTo(MonetaryAmount);  
  boolean isNegative();  
  boolean isPositive();  
  boolean isZero();   
  byte byteValue();  
  short shortValue();  
  short shortValueExact();  
  int intValue();  
  int intValueExact();
  long longValue();  
  long longValueExact();  
  float floatValue();  
  double doubleValue();  
  <T> T asType(Class<T>);
  Class<?> getNumberType();  
  int getPrecision();  
  int getScale();
  int signum();  
  MonetaryAmount from(Number);
  MonetaryAmount from(CurrencyUnit, Number);
  MonetaryAmount with(MonetaryOperator);
}


Hereby the last method with, allows to combine amount instances with arbitrary external manipulation logic, implemented as MonetaryOperator. This is explained in more detail later.


Class javax.money.Money

This class is the implementation of MonetaryAmount, using java.math.BigDecimal for numeric representation. The method signature look similar to the interface, but return the according value object, e.g.:

public final class Money implements MonetaryAmount,...{
   [...]
   public Money add(MonetaryAmount amount);
   [...]
}

Creation of Money instance hereby is done using the of() factory methods, e.g.

Money amount1 = Money.of("USD", 12); // int
Money amount2 = Money.of("USD", 12.5); // float
Money amount3 = Money.of("USD", (byte)12); // int
Money amount4 = Money.of("USD", BigDecimal.valueOf(100.15d)); 
                                                   // BigDecimal

Hereby the above calls also include some convenience, since the ISO namespace is used implicitly. The first call basically is equivalent to:

Money amount1 = Money.of(MoneyCurrency.of("USD"), 12);

or to the full fledged version:

Money amount1 = 
    Money.of(
      MoneyCurrency.of(MoneyCurrency.ISO_NAMESPACE, "USD"), 12); 


Interface javax.money.MonetaryOperator

As seen before an instance of MonetaryAmount also has a with method taking a MonetaryOperator as a parameter. The type MonetaryOperator is modelled similarly to the UnaryOperator functional interface from Java 8, extending the corresponding base type MonetaryFunction:


//@FunctionalInterface
public interface MonetaryFunction<T, R> {
public R apply(T value);
}


//@FunctionalInterface
public interface MonetaryOperator 
extends MonetaryFunction<MonetaryAmount,MonetaryAmount> {

}

This looks not very spectacular, but when combined with functionalities like currency exchange, rounding and more complex operations the concepts renders to a powerful weapon against complexity:

CurrencyConversion convertToYen = ...;
Money m = Money.of("USD", 12345.25)
              .multiply(10.34563)
              .with(MajorPart.of())
              .with(convertToYen)
              .divide(7)
              .with(MoneyRounding.of());

This operational chain creates an amount in USD, multiplies it by 10.34563, takes the major part only,  converts it to YEN, divides by 7 and finally rounds according the default rounding rules defined for YEN. 
When dealing with monetary functions there are also some built-in functions provided by default (if you have some hints or ideas, what else would be useful please drop me a mail):

Collection<MonetaryAmount> amounts = ...;
Map<CurrencyUnit, MonetaryAmount> sepResult = 
                               SeparateCurrencies.apply(amounts);
// Get amounts > 100 in USD
Collection<MonetaryAmount> allUSDAmounts =  
                          sepResult.get(MoneyCurrency.of("USD"));
Collection<MonetaryAmount> bigAmounts = new  
  AmountFilter(allUSDAmounts ).apply(
    new MonetaryFunction<MonetaryAmount, Boolean>(){
     public Boolean apply(MonetaryAmount amount){
        return amount.intValue()>100;
     }
  });

Finally also rounding can be modelled similar as MonetaryOperator, thus being only a special case of operation not adding basic additional complexity on this API level. Hereby the class MoneyRounding provides access to rounding algorithms:

public final class MoneyRounding implements MonetaryOperator{
   public MonetaryAmount apply(MonetaryAmount amount){
     [.. ]
   }
}


Different Numeric Representations

Different implementations of MonetaryAmount can model the numeric representation using different numeric types. This allows to cover the varieties of use cases identified. Nevertheless it should be possible to mix up different implementations by applying some relatively simple rules:
  • The numeric representation is not part of the MonetaryAmount interface.
  • The target instance's class of an operation performed, determines the resulting instance type of an operation (which should be the same as the target type itself).
  • If an arithmetic operation exceeds the capabilities of the target instance, an ArithmeticException should be thrown.
  • Precision/Scale information should never be lost.
  • Automatic rounding is only valid, when required by the internal representation. By default no rounding should ever happen, despite internal rounding, implied by the numeric representation type.

As an example, refer to the example below:

Money mm = Money.of("USD", "1222222222222222222323232.23232323");
IntegralMoney im = IntegralMoney.of("USD", 2345);
mm.divide(im);  // should work
im.divide(mm);  // throws ArithmeticException

So hopefully, this creates some appetite for more. So go to our project page on java.net or check out our GitHub repository and try things out, and of course, if you have any questions, or even better feedback or improvement, feel free to contact me

Wednesday, February 6, 2013

Interesting Aspects on Locale Extensions

Locale Extensions

What can be done

Extensions allow to add additional information to a Locale as also described in the Java Doc:
The Locale class implements IETF BCP 47 which is composed of RFC 4647 "Matching of Language Tags" and RFC 5646 "Tags for Identifying Languages" with support for the LDML (UTS#35, "Unicode Locale Data Markup Language") BCP 47-compatible extensions for locale data exchange.
Now what you can do, is adding extension tags as follows:


b.setExtension('x', "myExt-myCal-myCur");

But there are some things to be aware of:

  • extensions are not case sensitive (The JDK Locale converts them all implicitly to lower case!)
  • there is no defined order of extension tags, so dont rely on!
  • tags can be separated by '-' or '_' (the standard requires '-', Java accepts both as input, but then translates all '_' to '-').
  • valid characters for tags are restricted to [a-z][A-Z][0-9], so there are no special characters like '?' or '=' or similar possible (Java checks this).
  • tags are a minimum of 2 characters long (this is also checked by the JDK)
  • tags are a maximum of 8 characters long (this is also checked by the JDK)
  • each extension is identified by a singleton character (not a digit)
So all the following inputs are accepted by the JDK:
b.setExtension('x', "mi");b.setExtension('a', "maxmaxma");b.setExtension('b', "de-US");
b.setExtension('d', "aa1-bb2_cc3_dd4");



Strange Behavior

Some days ago I played around with Locale extensions (JDK 7/8):
Locale.Builder b = new Locale.Builder();// b.setRegion("DE");// b.setLanguage("de");b.setExtension('x', "gr2-spPrepen-nldeDE");System.out.println("Locale: " + b.build());System.out.println("Locale's extension: " + b.build().getExtension('x'));

The outputput is a bit surprising (the extension does NOT appear in the toString-output):
Locale: Locale's extension: gr2-spprepen-nldede

At a first glue this seem to be a bug, but when reading the spec in http://tools.ietf.org/html/rfc5646#page-16, especially section 2.2.6:

An extension MUST follow at least a primary language subtag.
That is, a language tag cannot begin with an extension.

this can be a hint, why this behaves as shown above, though, if a Locale is invalid, then it should not be possible to create/build it...

Now, when setting a language in our example with:
b.setLanguage("de");

the  toString() result now seem to be correct:
Locale: de__#x-gr2-spprepen-nldedeLocale's extension: gr2-spprepen-nldede 

The same applies, when setting a region only...

> Locale: _DE_#x-gr2-spprepen-nldede> Locale's extension: gr2-spprepen-nldede


...or when setting both, a region and a language, also of the output is as expected:

> Locale: de_DE_#x-gr2-spprepen-nldede> Locale's extension: gr2-spprepen-nldede

Finally I also was trying some special inputs based on the constraints defined by the specification, and I was able to create other invalid Locale instances realtively easily:

  • b.setExtension('c', "de-DE"); will be converted to c-de-de, which is invalid since de is duplicated in the final representation (but required to be unique).
  • b.setExtension('c', "c-de"); will be converted to c-c-de-de, which is invalid since the extension singleton c is duplicated in the final representation (but required to be unique).
  • b.setExtension('c', "x-de"); will be converted to c-x-de-de, which is invalid since an extension singleton must contain some tags, which is not the case for c-x-de, which in this case is the final representation. 
So be careful, when using the Locale extension mechanism. I will also post this to the i18n colleagues at OpenJDK, I am wondering what they think...




Friday, January 18, 2013

java.util.Timezone

What happens when calling Timezone.getTimezone("blabla")?

Even seen, what happens when you access a Timezone using java.uil.Timezone.getTimezone("blabla")? I would expect an exception to be thrown, since the input is completely invalid, but nothing similar happens. The method silently returns a TimeZone initialized to GMT+0.
But there are other flaws in TimeZone. At a first look, it looks like an immutable object, but when look at it carefully, there is a setId(String) method. This allows to set the time zone ID to an arbitrary value:
TimeZone tz = Timezone.getTimezone("Europe/Zurich"); 
     // will return a zone with GMT+1 offset (DST ignored)
tz.setId("blabla");
     // now we have a time zone 'blabla' with identical offsets.
System.out.println(tz);

This results in the following output:
sun.util.calendar.ZoneInfo[id="blabla",offset=3600000,dstSavings=3600000,useDaylight=true,transitions=119,lastRule=java.util.SimpleTimeZone[id=blabla,offset=3600000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=2,startMonth=2,startDay=-1,startDayOfWeek=1,startTime=3600000,startTimeMode=2,endMode=2,endMonth=9,endDay=-1,endDayOfWeek=1,endTime=3600000,endTimeMode=2]]

Obviously this API is very dangerous. So looking forward what JSR 310 will bring...

JDK used was 1.7.09 and jdk-8-ea-bin-b74-windows-x64-24_jan_2013, but it is similarly also the case in earlier versions.



Wednesday, January 16, 2013

Complex Formatting and Parsing

Complex Formatting and Parsing

Problem

In the area of internationalization (i18n) formatting and parsing is a typical use case. Unfortunately java.util.Locale as the only parameter controlling such a process has shown not to be sufficient. Refer to the following use cases:

  • Formatting of an monetary amount contains of a numeric part, as well as a currency part. Now both can be required to be formatted using different locales, e.g. 
    • German numeric format and 
    • English currency symbol.
  • But when considering display requirements for financial applications very different formats are required
    • based on the usage scenario, e.g. numbers included into balances may be totally different than numbers formatted on a account summary (different number groups, signs and symbol placements etc).
    • based on individual user settings.
    • based on the type of formatting, e.g. for display, print out or for textual representation within legacy systems.
  • When parsing a literal representation back into a type instance similar scenarios may be possible. Additionally, especially when parsing user input, the things can get even more complex:
    • the currency symbol may be English, similar to above, but...
    • the number formats supported can be in several formats, since users do not always enter the 100% correct format (which is totally OK from a user's perspective!).
    • additionally depending on use case different number precision may ve required, that must not match the fraction digits defined by the currency entered (e.g. 2 for Swiss Francs, but 0 for Japanese Yen). In some usage scenarios one even wants lenient fraction parsing to be possible.
Summarizing a Locale with its country, language, variant scheme is not sufficient to define the scenarios above. 

Solution: Defining a LocalizationStyle

I propose to model such things as LocalizationStyle, containing the following data:
  • an identifier defining the style. Different use cases (e.g. different display scenarios, as well as technical scenarios can be separated).
  • a target type, since a style typically is bound to a specific type and should not be mixed up.
  • a leading Locale, or translation Locale.
  • an (optional) number Locale, if missing falling back to the translation Locale
  • an (optional) date Locale, if missing falling back to the translation Locale
  • an (optional) time Locale, if missing falling back to the date Locale
  • any additional optional attributes
Additionally a Locale is an immutable instance. With a LocalizationStyle this would be also be feasible:
  • by defining a LocalizationStyleFactory to create immutable instances.
  • or by setting a LocaliuationStyle to read-only, when it is completely initialized and configured.
Consequently the most simple and common variant of a LocalizationStyle is basically very similar as a Locale:
  • it's identifier is set to "default". This is also visible by the corresponding method boolean isDefault(); which returns true.
  • Its leading translation Localeis set to the required Locale.
  • Since this functionaliy is quite common, it can be provided using a static factory method:
    LocalizationStyle style = LocalizationStyle .valueOf(Locale.GERMAN);

Using the LocalizationStyle for Configuring Complex Formatting and Parsing


As a consequence according formatters and parsers only must support LocalizationStyle as valid input parameters (Locale can still be supported for convenience), e.g.
  String format(T item, LocalizationStyle style);
or
  String getLocalized(LocalizationStyle style);

But now it is possible to pass much more detailed configuration what how formatter or parser must behave. Nevertheless in mny cases such formatters/parsers are not to be implemented as big and complex single data types. It is recommended to implement something like a FormatterManager/ParserManager that is able to manage the different implementations. Nevertheless since a Formatter/Parser instance can be identified by the duplet [style-id, target-type] this is not as complex as it seems on a first look.

Detailed interface

The proposed class would be as follows:

public class LocalizationStyle implements Serializable {

/**
* serialVersionUID.
*/
private static final long serialVersionUID = 8612440355369457473L;
/** The internal key used for a time locale set. */
public static final String TIME_LOCALE = "timeLocale";
/** The internal key used for a date locale set. */
public static final String DATE_LOCALE = "dateLocale";
/** The internal key used for a number locale set. */
public static final String NUMBER_LOCALE = "numberLocale";
/** The internal key used for a translation locale set (default). */
public static final String TRANSLATION_LOCALE = "locale";
/** The internal key used for a formatting/parsing style. */
private static final String DEFAULT_ID = "default";
/** The style's name, by default ({@link #DEFAULT_ID}. */
private String id;
/** The style's generic properties. */
private Map<String, Object> attributes = Collections
.synchronizedMap(new HashMap<String, Object>());

/**
* Flag to make a localization style read only, so it can be used (and
* cached) similar to a immutable object.
*/
private boolean readOnly = false;

/**
* Creates a new instance of a style. This method will use the Locale
* returned from {@link Locale#getDefault()} as the style's default locale.
* @param id
*            The style's identifier (not null).
*/
public LocalizationStyle(String id) {
this(id, Locale.getDefault());
}

/**
* Creates a new instance of a style.
* @param id
*            The style's identifier (not null).
* @param locale
*            the default locale to be used for all locale usages.
*/
public LocalizationStyle(String id, Locale locale) {
this(id, locale, locale);
}

/**
* Creates a new instance of a style.
* @param id
*            The style's identifier (not null).
* @param translationLocale
*            the default locale (translation locale) to be used for all
*            locale usages.
* @param numberLocale
*            the locale to be used for numbers.
*/
public LocalizationStyle(String id, Locale translationLocale,
Locale numberLocale) {
if (id == null) {
throw new IllegalArgumentException("ID must not be null.");
}
this.id = id;
}

/**
* Creates a new instance of a style. This method will copy all attributes
* and properties from the given style. The style created will not be
* read-only, even when the base style is read-only.
* @param baseStyle
*            The style to be used as a base style.
*/
public LocalizationStyle(LocalizationStyle baseStyle) {
this.attributes.putAll(baseStyle.getAttributes());
this.id = baseStyle.getId();
}

/**
* Allows to evaluate if a style is a default style. A style is a default
* style, if its id equals to {@link #DEFAULT_ID}.
* <p>
* Note that nevertheless multiple default style instances may be defined
* that are not equal, since its attributes may differ.
* @return true, if this style is a default style.
*/
public boolean isDefault() {
return DEFAULT_ID.equals(getId());
}

/**
* This method allows to check, if the given style can be changed or, if it
* read only.
* @return true, if the style is read-only.
*/
public final boolean isReadOnly() {
return readOnly;
}

/**
* This method renders this style instance into an immutable instance.
* Subsequent calls to {@link #setAttribute(String, Serializable)},
* {@link #setDateLocale(Locale)}, {@link #setNumberLocale(Locale)},
* {@link #setTimeLocale(Locale)}or {@link #removeAttribute(String)} will
* throw an {@link IllegalStateException}.
*/
public void setImmutable() {
this.readOnly = true;
}

/**
* Method used to simply create a {@link IllegalStateException}, if this
* instance is read-only. This prevents duplicating the corresponding code.
*/
private void throwsExceptionIfReadonly() {
if (readOnly) {
throw new IllegalStateException(
"This instance is immutable and can not be ^changed.");
}
}

/**
* Get the style's identifier, not null.
* @return the style's id.
*/
public String getId() {
return id;
}

/**
* Get the style's (default) locale used for translation of textual values,
* and (if not specified explicitly as a fallback) for date, time and
* numbers.
* @return the translation (default) locale
*/
public final Locale getTranslationLocale() {
Locale locale = (Locale) getAttribute(TRANSLATION_LOCALE);
if (locale != null) {
return locale;
}
return Locale.getDefault();
}

/**
* Get the style's locale used for formatting/parsing of numbers.
* @return the number locale
*/
public final Locale getNumberLocale() {
Locale locale = (Locale) getAttribute(NUMBER_LOCALE);
if (locale != null) {
return locale;
}
return getTranslationLocale();
}

/**
* Get the style's locale for formatting/parsing of date instances.
* @return the date locale
*/
public final Locale getDateLocale() {
Locale locale = (Locale) getAttribute(DATE_LOCALE);
if (locale != null) {
return locale;
}
return getTranslationLocale();
}

/**
* Set the style's locale for formatting/parsing of dates.
* @param locale
*            The date locale to be used, or null for falling back to the
*            translation locale.
* @return the date locale previously set, or null.
*/
public final Locale setDateLocale(Locale locale) {
return (Locale) setAttribute(DATE_LOCALE, locale);
}

/**
* Set the style's locale for formatting/parsing of time.
* @param locale
*            The time locale to be used, or null for falling back to the
*            translation locale.
* @return the time locale previously set, or null.
*/
public final Locale setTimeLocale(Locale locale) {
return (Locale) setAttribute(TIME_LOCALE, locale);
}

/**
* Set the style's locale for formatting/parsing of numbers.
* @param locale
*            The number locale to be used, or null for falling back to the
*            number locale.
* @return the number locale previously set, or null.
*/
public final Locale setNumberLocale(Locale locale) {
return (Locale) setAttribute(NUMBER_LOCALE, locale);
}

/**
* Get the style's locale for formatting/parsing of time data.
* @return the time locale
*/
public final Locale getTimeLocale() {
Locale locale = (Locale) getAttribute(TIME_LOCALE);
if (locale != null) {
return locale;
}
return getDateLocale();
}

/**
* Get the current defined properties fo this style.
* @return the properties defined
*/
public final Map<String, Object> getAttributes() {
synchronized (attributes) {
return new HashMap<String, Object>(attributes);
}
}

/**
* Sets the given property. This method is meant for adding custom
* properties. Setting a predefined property, e.g. {@link #DATE_LOCALE} will
* throw an {@link IllegalArgumentException}.
* @param key
*            The target key
* @param value
*            The target value
* @return The object previously set, or null.
* @throws IllegalArgumentException
*             if the key passed equals to a key used for a predefined
*             property.
*/
public Object setAttribute(String key, Serializable value) {
throwsExceptionIfReadonly();
synchronized (attributes) {
return attributes.put(key, value);
}
}

/**
* Read a property from this style.
* @param key
*            The property's key
* @return the current property value, or null.
*/
public Object getAttribute(String key) {
synchronized (attributes) {
return attributes.get(key);
}
}

/**
* Removes the given property. This method is meant for removing custom
* properties. Setting a predefined property, e.g. {@link #DATE_LOCALE} will
* throw an {@link IllegalArgumentException}.
* @param key
*            The key to be removed
* @return The object previously set, or null.
* @throws IllegalArgumentException
*             if the key passed equals to a key used for a predefined
*             property.
*/
public Object removeAttribute(String key) {
throwsExceptionIfReadonly();
synchronized (attributes) {
return attributes.remove(key);
}
}

/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
synchronized (attributes) {
result = prime * result
+ ((attributes == null) ? 0 : attributes.hashCode());
}
return result;
}

/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
LocalizationStyle other = (LocalizationStyle) obj;
synchronized (attributes) {
if (attributes == null) {
if (other.attributes != null)
return false;
} else if (!attributes.equals(other.attributes))
return false;
}
return true;
}

/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
synchronized (attributes) {
return "LocalizationContext [id=" + id + ", properties="
+ attributes + "]";
}
}

/**
* Factory method to create a {@link LocalizationStyle} using a single
* {@link Locale}.
* @param locale
* @return
*/
public static LocalizationStyle of(Locale locale) {
return new LocalizationStyle(DEFAULT_ID, locale);
}

}