/*
* Copyright (c) 2012, 2013, Credit Suisse (Anatole Tresch), Werner Keil.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.javamoney.format;
import org.javamoney.format.spi.ItemFormatFactorySpi;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.money.AbstractContext;
import javax.money.AbstractContextBuilder;
import java.io.Serializable;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* This class enhances the localization mechanisms as defined by {@link Locale}.
* It is used to configure formatting/parsing ( {@link ItemFormat} instances )
* with arbitrary parameters, thus also supporting very complex formatting
* scenarios. The configuration parameters possible are either determined
* <ul>
* <li>by the several {@link StyleableItemFormatToken} added in sequence, and the
* {@link ParseResultFactory}, when using an {@link ItemFormat} instance created with
* the {@link ItemFormatBuilder}.
* <li>by the preoconfigured and provided {@link ItemFormat} instance, provided
* by an implementation of the {@link ItemFormatFactorySpi}.
* <p>
* Further more when parsing amounts, it is often desirable to control the
* checks for the required decimals of the given target currency (aka lenient
* fraction parsing). In even more advanced use cases, also additional
* configuration attributes may be necessary to be passed to a formatter/parser
* instance.
* <p>
* Finally instances of {@link LocalizationContext} can be registered to the
* internal style cache, which allows to share the according styles, by
* accessing them using {@link #of(Class)} of {@link #of(Class, String)}.
* <p>
* This class is thread safe, immutable and {@link Serializable}. The containing
* {@link Builder} class however is NOT thread-safe.
*
* @author Anatole Tresch
*/
public final class LocalizationContext extends AbstractContext implements Serializable{
/**
* serialVersionUID.
*/
private static final long serialVersionUID = 8612440355369457473L;
private static final Logger LOG = LoggerFactory.getLogger(LocalizationContext.class);
/**
* the default id.
*/
public static final String DEFAULT_ID = "default";
/**
* The style's name, by default ({@link #DEFAULT_ID}.
*/
private String id = DEFAULT_ID;
/**
* The style's target type.
*/
private Class<?> targetType;
/**
* The shared map of LocalizationStyle instances.
*/
private static final Map<String,LocalizationContext> STYLE_MAP =
new ConcurrentHashMap<String,LocalizationContext>();
/**
* Access a cached <i>default</i> style for a type. This equals to
* {@link #of(Class, String)}, hereby passing
* {@link LocalizationContext#DEFAULT_ID} as {@code styleId}.
*
* @param targetType The target type, not {@code null}.
* @param styleId The style's id, not {@code null}.
* @return the according style, if a corresponding style is cached, or
* {@code null].
*/
public static final LocalizationContext of(Class<?> targetType, String styleId){
return STYLE_MAP.get(getKey(targetType, styleId));
}
/**
* Access a cached <i>default</i> style for a type. This equals to
* {@link #of(Class, String)}, hereby passing
* {@link LocalizationContext#DEFAULT_ID} as {@code styleId}.
*
* @param targetType The target type, not {@code null}.
* @return the according style, if a corresponding style is cached, or
* {@code null].
*/
public static final LocalizationContext of(Class<?> targetType){
return of(targetType, LocalizationContext.DEFAULT_ID);
}
/**
* Collects all styles currently registered within the style cache for the
* given type.
*
* @param targetType the target type, not {@code null}.
* @return a set of style identifiers for the given type, never null.
*/
public static Collection<String> getSupportedStyleIds(Class<?> targetType){
Set<String> result = new HashSet<String>();
String className = targetType.getName();
for(String key : STYLE_MAP.keySet()){
int index = key.indexOf('_');
if(className.equals(key.substring(0, index))){
result.add(key.substring(index + 1));
}
}
return result;
}
/**
* Access a cached style for a type.
*
* @param targetType The target type, not {@code null}.
* @param styleId The style's id, not {@code null}.
* @return the according style, if a corresponding style is cached, or
* {@code null].
*/
private static String getKey(Class<?> targetType, String styleId){
return targetType.getName() + "_" + (styleId != null ? styleId : "default");
}
/**
* Creates a new instance of a style.
*
* @param builder The style's builder (not null).
*/
private LocalizationContext(Builder builder){
super(builder);
this.id = builder.id;
this.targetType = builder.targetType;
}
/**
* Get the style's identifier, not null.
*
* @return the style's id.
*/
public String getId(){
return id;
}
/**
* Get the style's target type used.
*
* @return the translation (default) locale
*/
public final Class<?> getTargetType(){
return this.targetType;
}
/**
* Access the ItemFormat class that should be instantiated by default for formatting this style.
*
* @return the default item format class, or null.
*/
public final Class<? extends ItemFormat<?>> getDefaultItemFormatClass(){
String defaultItemFormatClassName = getText("defaultItemFormatClassName");
if(defaultItemFormatClassName != null){
try{
return (Class<? extends ItemFormat<?>>) Class.forName(defaultItemFormatClassName);
}
catch(Exception e){
LOG.error("Failed to load ItemFormat class: " + defaultItemFormatClassName, e);
}
}
return getAny("defaultItemFormatClass", Class.class);
}
/**
* Method allows to check, if a given style is a default style, which is
* equivalent to a style id equal to {@link #DEFAULT_ID}.
*
* @return true, if the instance is a default style.
*/
public boolean isDefaultStyle(){
return DEFAULT_ID.equals(getId());
}
/**
* Builder to of new instances of {@link LocalizationContext}.
* <p>
* This class is not thread-safe and should not be used in multiple threads.
* However {@link LocalizationContext} instances created can securely shared
* among threads.
*
* @author Anatole Tresch
*/
public static final class Builder extends AbstractContextBuilder<Builder,LocalizationContext>{
/**
* The style's id.
*/
private String id = DEFAULT_ID;
/**
* The formated type.
*/
private Class<?> targetType;
/**
* The default class of ItemFormat to be used.
*/
private Class<? extends ItemFormat<?>> defaultItemFormatClass;
/**
* Constructor.
*
* @param targetType the target type, not null.
*/
public Builder(Class<?> targetType){
this.targetType = targetType;
setId(DEFAULT_ID);
}
/**
* Constructor.
*
* @param styleId The style's id.
* @param targetType The target TYPE
* @return the {@link LocalizationContext} created.
*/
public Builder(Class<?> targetType, String styleId){
setId(styleId);
this.targetType = targetType;
}
/**
* 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 baseContext The style to be used as a base style.
*/
public Builder(LocalizationContext baseContext){
importContext(baseContext);
this.id = baseContext.getId();
this.targetType = baseContext.getTargetType();
}
/**
* Creates a new instance of {@link LocalizationContext}.
*
* @return a new instance of {@link LocalizationContext}, never
* {@code null}
* @throws IllegalStateException if this builder can not of a new instance.
*/
public LocalizationContext build(){
return build(false);
}
/**
* Creates a new instance of {@link LocalizationContext}.
*
* @param register flag for registering the style into the global cache.
* @return a new instance of {@link LocalizationContext}, never
* {@code null}
* @throws IllegalStateException if this builder can not of a new instance.
*/
public LocalizationContext build(boolean register){
LocalizationContext style = new LocalizationContext(this);
if(register){
STYLE_MAP.put(getKey(this.targetType, this.id), style);
}
return style;
}
/**
* Constructor for a <i>default</i> style.
*/
public Builder(){
}
/**
* Method allows to check, if a given style is a default style, which is
* equivalent to a style {@code id} equal to {@link #DEFAULT_ID}.
*
* @return {@code true}, if the instance is a <i>default</i> style.
*/
public boolean isDefaultStyle(){
return DEFAULT_ID.equals(getId());
}
/**
* Sets the style's id.
*
* @param id the style's id, not {@code null}.
* @return this instance, for chaining.
*/
public Builder setId(String id){
Objects.requireNonNull(id, "style id required.");
this.id = id;
return this;
}
/**
* Sets the given targetType.
*
* @param targetType The instance's targetType, not {@code null}.
* @return The Builder instance for chaining.
*/
public <T> Builder setTargetType(Class<?> targetType){
Objects.requireNonNull(targetType, "targetType required.");
this.targetType = targetType;
return this;
}
/**
* Sets the default formatter to be used by this style.
*
* @param itemFormatClass the default formatter class, not null.
* @param <T> the target type
* @return The Builder instance for chaining.
*/
public <T> Builder setDefaultItemFormat(Class<? extends ItemFormat<?>> itemFormatClass){
Objects.requireNonNull(itemFormatClass);
this.defaultItemFormatClass = itemFormatClass;
return this;
}
/**
* Get the style's identifier, not {@code null}.
*
* @return the style's id.
*/
public String getId(){
return id;
}
}
}