package fr.openwide.core.wicket.more.rendering;
import static com.google.common.base.Preconditions.checkNotNull;
import java.io.Serializable;
import java.text.DateFormat;
import java.text.Format;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.MissingResourceException;
import org.apache.commons.lang3.text.WordUtils;
import org.apache.wicket.Localizer;
import org.apache.wicket.model.AbstractReadOnlyModel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.util.convert.ConversionException;
import org.apache.wicket.util.convert.IConverter;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Joiner;
import com.google.common.base.Joiner.MapJoiner;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Range;
import fr.openwide.core.commons.util.functional.Functions2;
import fr.openwide.core.commons.util.functional.SerializableFunction;
import fr.openwide.core.commons.util.rendering.IRenderer;
import fr.openwide.core.wicket.markup.html.basic.AbstractCoreLabel;
import fr.openwide.core.wicket.markup.html.basic.CoreLabel;
import fr.openwide.core.wicket.more.model.LocaleAwareReadOnlyModel;
import fr.openwide.core.wicket.more.util.IDatePattern;
import fr.openwide.core.wicket.more.util.model.Detachables;
import fr.openwide.core.wicket.more.util.model.Models;
/**
* A one-way wicket converter: converts an object to a String.
* <p>This class implements {@link IRenderer}, which is not tied to Wicket.
*/
public abstract class Renderer<T> implements IConverter<T>, IRenderer<T> {
private static final long serialVersionUID = 4900817523619252790L;
@Override
public abstract String render(T value, Locale locale);
/**
* Utility method that can be used when a resource key value is needed in the render() implementation.
*/
protected static String getString(String key, Locale locale) {
return getString(key, locale, null);
}
/**
* Utility method that can be used when a resource key value is needed in the render() implementation.
*/
protected static String getString(String key, Locale locale, IModel<?> model) {
return Localizer.get().getString(key, null, model, locale, null, (IModel<String>) null);
}
/**
* Utility method that can be used when a resource key value is needed in the render() implementation.
* <p>Returns Optional.absent() when the key is not found.
*/
protected static Optional<String> getStringOptional(String key, Locale locale) {
return getStringOptional(key, locale, null);
}
/**
* Utility method that can be used when a resource key value is needed in the render() implementation.
* <p>Returns Optional.absent() when the key is not found.
*/
protected static Optional<String> getStringOptional(String key, Locale locale, IModel<?> model) {
String defaultValue = new String(); // NOSONAR
String result = Localizer.get().getString(key, null, model, locale, null, defaultValue);
if (result == defaultValue) { // NOSONAR
return Optional.absent();
} else {
return Optional.of(result);
}
}
/**
* @deprecated Implemented to satisfy the {@link IConverter} interface; do not use this method.
* @throws UnsupportedOperationException Always.
*/
@Override
@Deprecated
public final T convertToObject(String value, Locale locale) throws ConversionException {
throw new UnsupportedOperationException("Renderers do not implement convertToObject");
}
/**
* @deprecated Implemented to satisfy the {@link IConverter} interface; use {@link #render(Object, Locale)} instead.
*/
@Override
@Deprecated
public final String convertToString(T value, Locale locale) {
return render(value, locale);
}
public Renderer<T> nullsAsNull() {
return withDefault(Predicates.notNull(), NULL_RENDERER);
}
private static final Renderer<Object> NULL_RENDERER = new ConstantRenderer<Object>(null) {
private static final long serialVersionUID = 5711969182760960576L;
private Object readResolve() {
return NULL_RENDERER;
}
};
public Renderer<T> nullsAsBlank() {
return withDefault(Predicates.notNull(), BLANK_RENDERER);
}
private static final Renderer<Object> BLANK_RENDERER = new ConstantRenderer<Object>("") {
private static final long serialVersionUID = 5711969182760960576L;
private Object readResolve() {
return BLANK_RENDERER;
}
};
public Renderer<T> nullsAs(T defaultValue) {
return withDefault(Predicates.notNull(), defaultValue);
}
public Renderer<T> nullsAs(IRenderer<? super T> defaultRenderer) {
return withDefault(Predicates.notNull(), defaultRenderer);
}
public Renderer<T> nullsAsResourceKey(String resourceKey) {
return withDefault(Predicates.notNull(), withResourceKey(resourceKey));
}
public Renderer<T> nullsAsConstant(String defaultRendering) {
return withDefault(Predicates.notNull(), constant(defaultRendering));
}
public Renderer<T> withDefault(Predicate<? super T> validValuePredicate, T defaultValue) {
return onResultOf(Functions2.defaultValue(validValuePredicate, Functions.constant(defaultValue)));
}
public Renderer<T> withDefault(Predicate<? super T> validValuePredicate, IRenderer<? super T> defaultRenderer) {
return new DefaultingRenderer<>(validValuePredicate, this, defaultRenderer);
}
private static class DefaultingRenderer<T> extends Renderer<T> {
private static final long serialVersionUID = 3566036942853574753L;
private final Predicate<? super T> validValuePredicate;
private final IRenderer<? super T> validValueDelegate;
private final IRenderer<? super T> invalidValueDelegate;
public DefaultingRenderer(Predicate<? super T> validValuePredicate, IRenderer<? super T> validValueDelegate,
IRenderer<? super T> invalidValueDelegate) {
super();
this.validValuePredicate = checkNotNull(validValuePredicate);
this.validValueDelegate = checkNotNull(validValueDelegate);
this.invalidValueDelegate = checkNotNull(invalidValueDelegate);
}
@Override
public String render(T value, Locale locale) {
if (validValuePredicate.apply(value)) {
return validValueDelegate.render(value, locale);
} else {
return invalidValueDelegate.render(value, locale);
}
}
}
@SafeVarargs
public final Renderer<T> append(Function<? super Locale, ? extends Joiner> joinerFunction, Renderer<? super T> ... appendedRenderers) {
return new JoiningRenderer<>(joinerFunction, Lists.asList(this, appendedRenderers));
}
private static class JoiningRenderer<T> extends Renderer<T> {
private static final long serialVersionUID = 3566036942853574753L;
private final Function<? super Locale, ? extends Joiner> joinerFunction;
private final Iterable<? extends Renderer<? super T>> renderers;
public JoiningRenderer(Function<? super Locale, ? extends Joiner> joinerFunction, Iterable<? extends Renderer<? super T>> renderers) {
super();
this.joinerFunction = joinerFunction;
this.renderers = renderers;
}
@Override
public String render(final T value, final Locale locale) {
checkNotNull(locale);
Joiner joiner = joinerFunction.apply(locale);
Iterable<String> renderedItems = Iterables.transform(renderers, new Function<Renderer<? super T>, String>() {
@Override
public String apply(Renderer<? super T> input) {
return input.render(value, locale);
}
});
return joiner.join(renderedItems);
}
}
public <F> Renderer<F> onResultOf(Function<? super F, ? extends T> function) {
return new ByFunctionRenderer<F, T>(function, this);
}
private static class ByFunctionRenderer<F, T> extends Renderer<F> {
private static final long serialVersionUID = 3566036942853574753L;
private final Function<? super F, ? extends T> function;
private final Renderer<T> delegate;
public ByFunctionRenderer(Function<? super F, ? extends T> function, Renderer<T> delegate) {
super();
this.function = function;
this.delegate = delegate;
}
@Override
public String render(F value, Locale locale) {
return delegate.render(function.apply(value), locale);
}
}
/**
* Returns an empty string whenever the rendered value is null.
*/
public Renderer<T> orBlank() {
return compose(Functions2.defaultValue(""));
}
public Renderer<T> compose(Function<? super String, ? extends String> function) {
return new ComposeRenderer<T>(function, this);
}
private static class ComposeRenderer<T> extends Renderer<T> {
private static final long serialVersionUID = 3566036942853574753L;
private final Function<? super String, ? extends String> function;
private final Renderer<T> delegate;
public ComposeRenderer(Function<? super String, ? extends String> function, Renderer<T> delegate) {
super();
this.function = function;
this.delegate = delegate;
}
@Override
public String render(T value, Locale locale) {
return function.apply(delegate.render(value, locale));
}
}
public Function<T, String> asFunction(Locale locale) {
return new RendererFunction(locale);
}
private class RendererFunction implements Function<T, String>, Serializable {
private static final long serialVersionUID = -1080017215611311157L;
private final Locale locale;
public RendererFunction(Locale locale) {
super();
this.locale = checkNotNull(locale);
}
@Override
public String apply(T input) {
return Renderer.this.render(input, locale);
}
}
public IModel<String> asModel(IModel<? extends T> valueModel) {
return new RendererModel(valueModel);
}
private class RendererModel extends LocaleAwareReadOnlyModel<String> {
private static final long serialVersionUID = -1080017215611311157L;
private final IModel<? extends T> valueModel;
public RendererModel(IModel<? extends T> valueModel) {
super();
this.valueModel = checkNotNull(valueModel);
}
@Override
public String getObject(Locale locale) {
return Renderer.this.render(valueModel.getObject(), locale);
}
@Override
public void detach() {
super.detach();
Detachables.detach(valueModel);
}
}
public AbstractCoreLabel<?> asLabel(String id, IModel<? extends T> valueModel) {
return new CoreLabel(id, asModel(valueModel));
}
public Renderer<T> withResourceKey(String resourceKey) {
return new ResourceKeyWithRenderedParameterRenderer<>(resourceKey, this);
}
private static class ResourceKeyWithRenderedParameterRenderer<T> extends Renderer<T> {
private static final long serialVersionUID = 7436587266161009083L;
private final Renderer<T> parameterRenderer;
private final String resourceKey;
public ResourceKeyWithRenderedParameterRenderer(String resourceKey, Renderer<T> parameterRenderer) {
super();
this.resourceKey = resourceKey;
this.parameterRenderer = parameterRenderer;
}
@Override
public String render(final T value, Locale locale) {
IModel<?> model = parameterRenderer.asModel(new AbstractReadOnlyModel<T>() {
private static final long serialVersionUID = 1L;
@Override
public T getObject() {
return value;
}
});
return Renderer.getString(resourceKey, locale, model);
}
@Override
public String toString() {
return "fromResourceKey(" + resourceKey + ")";
}
}
/**
* @deprecated There is no reason to pass a WicketRenderer to {@code from()}. Use the renderer as-is.
*/
@Deprecated
public static <T> Renderer<T> from(Renderer<? super T> renderer) {
return from((IConverter<? super T>) renderer);
}
public static <T> Renderer<T> from(IConverter<? super T> converter) {
return new FromConverterRenderer<>(converter);
}
private static class FromConverterRenderer<T> extends Renderer<T> {
private static final long serialVersionUID = 7436587266161009083L;
private final IConverter<? super T> converter;
public FromConverterRenderer(IConverter<? super T> converter) {
super();
this.converter = converter;
}
@Override
public String render(T value, Locale locale) {
return this.converter.convertToString(value, locale);
}
@Override
public String toString() {
return converter.toString();
}
}
public static <T> Renderer<T> from(IRenderer<? super T> renderer) {
return new FromRendererWicketRenderer<>(renderer);
}
private static class FromRendererWicketRenderer<T> extends Renderer<T> {
private static final long serialVersionUID = 7436587266161009083L;
private final IRenderer<? super T> renderer;
public FromRendererWicketRenderer(IRenderer<? super T> renderer) {
super();
this.renderer = renderer;
}
@Override
public String render(T value, Locale locale) {
return this.renderer.render(value, locale);
}
@Override
public String toString() {
return renderer.toString();
}
}
public static <T> Renderer<T> constant(String value) {
return new ConstantRenderer<>(value);
}
private static class ConstantRenderer<T> extends Renderer<T> {
private static final long serialVersionUID = 7436587266161009083L;
private final String value;
public ConstantRenderer(String value) {
super();
this.value = value;
}
@Override
public String render(T value, Locale locale) {
return this.value;
}
@Override
public String toString() {
return "constant(" + value + ")";
}
}
@SuppressWarnings("unchecked") // Works for any T
public static <T> Renderer<T> stringValue() {
return (Renderer<T>) STRING_VALUE_RENDERER;
}
private static final Renderer<Object> STRING_VALUE_RENDERER = new Renderer<Object>() {
private static final long serialVersionUID = 5711969182760960576L;
@Override
public String render(Object value, Locale locale) {
return String.valueOf(value);
}
private Object readResolve() {
return STRING_VALUE_RENDERER;
}
};
public static <T extends Number> Renderer<T> fromNumberFormat(NumberFormat format) {
return fromFormat(format);
}
public static <T extends Number> Renderer<T> fromNumberFormat(Function<? super Locale, ? extends NumberFormat> formatFunction) {
return fromFormat(formatFunction);
}
public static <T extends Date> Renderer<T> fromDateFormat(DateFormat format) {
return fromFormat(format);
}
public static <T extends Date> Renderer<T> fromDateFormat(Function<? super Locale, ? extends DateFormat> formatFunction) {
return fromFormat(formatFunction);
}
protected static <T> Renderer<T> fromFormat(Format format) {
checkNotNull(format);
final Format copiedFormat = (Format) format.clone(); // Ignore changes on 'format'
return new FormatRenderer<T>(new SerializableFunction<Locale, Format>() {
private static final long serialVersionUID = 1L;
@Override
public Format apply(Locale input) {
return (Format) copiedFormat.clone();
}
@Override
public String toString() {
return copiedFormat.toString();
}
});
}
protected static <T> Renderer<T> fromFormat(Function<? super Locale, ? extends Format> formatFunction) {
return new FormatRenderer<T>(formatFunction).nullsAsBlank();
}
private static class FormatRenderer<T> extends Renderer<T> {
private static final long serialVersionUID = -2023856554950929671L;
private final Function<? super Locale, ? extends Format> formatFunction;
public FormatRenderer(Function<? super Locale, ? extends Format> formatFunction) {
super();
this.formatFunction = checkNotNull(formatFunction);
}
@Override
public String render(Object value, Locale locale) {
checkNotNull(locale);
Format format = formatFunction.apply(locale);
return format.format(value);
}
}
public static Renderer<Iterable<?>> fromJoiner(Function<? super Locale, ? extends Joiner> joinerFunction) {
return fromJoiner(joinerFunction, stringValue());
}
public static <T> Renderer<Iterable<? extends T>> fromJoiner(Function<? super Locale, ? extends Joiner> joinerFunction, Renderer<T> itemRenderer) {
return new IterableJoinerRenderer<T>(joinerFunction, itemRenderer);
}
private static class IterableJoinerRenderer<T> extends Renderer<Iterable<? extends T>> {
private static final long serialVersionUID = -3594965870562973846L;
private final Function<? super Locale, ? extends Joiner> joinerFunction;
private final Renderer<T> itemRenderer;
public IterableJoinerRenderer(Function<? super Locale, ? extends Joiner> joinerFunction, Renderer<T> itemRenderer) {
super();
this.joinerFunction = checkNotNull(joinerFunction);
this.itemRenderer = itemRenderer;
}
@Override
public String render(Iterable<? extends T> value, Locale locale) {
checkNotNull(locale);
Joiner joiner = joinerFunction.apply(locale);
Iterable<String> renderedItems = Iterables.transform(value, itemRenderer.asFunction(locale));
return joiner.join(renderedItems);
}
}
public static Renderer<Map<?, ?>> fromMapJoiner(Function<? super Locale, ? extends MapJoiner> joinerFunction) {
return new MapJoinerRenderer(joinerFunction);
}
private static class MapJoinerRenderer extends Renderer<Map<?, ?>> {
private static final long serialVersionUID = 200989454412696381L;
private final Function<? super Locale, ? extends MapJoiner> joinerFunction;
public MapJoinerRenderer(Function<? super Locale, ? extends MapJoiner> joinerFunction) {
super();
this.joinerFunction = checkNotNull(joinerFunction);
}
@Override
public String render(Map<?, ?> value, Locale locale) {
checkNotNull(locale);
MapJoiner joiner = joinerFunction.apply(locale);
return joiner.join(value);
}
}
public static <K, V> Renderer<Map<? extends K, ? extends V>> mapRenderer(
Function<? super Locale, ? extends Joiner> joinerFunction,
Function<? super Locale, ? extends Joiner> joinerKeyValueFunction,
Renderer<K> keyRenderer,
Renderer<V> valueRenderer
) {
return new MapRenderer<>(joinerFunction, joinerKeyValueFunction, keyRenderer, valueRenderer);
}
private static class MapRenderer<K, V> extends Renderer<Map<? extends K, ? extends V>> {
private static final long serialVersionUID = 200989454412696381L;
private final Function<? super Locale, ? extends Joiner> joinerFunction;
private final Function<? super Locale, ? extends Joiner> joinerKeyValueFunction;
private final Renderer<K> keyRenderer;
private final Renderer<V> valueRenderer;
public MapRenderer(
Function<? super Locale, ? extends Joiner> joinerFunction,
Function<? super Locale, ? extends Joiner> joinerKeyValueFunction,
Renderer<K> keyRenderer,
Renderer<V> valueRenderer
) {
super();
this.joinerFunction = checkNotNull(joinerFunction);
this.joinerKeyValueFunction = checkNotNull(joinerKeyValueFunction);
this.keyRenderer = checkNotNull(keyRenderer);
this.valueRenderer = checkNotNull(valueRenderer);
}
@Override
public String render(Map<? extends K, ? extends V> value, final Locale locale) {
checkNotNull(locale);
Joiner joiner = joinerFunction.apply(locale);
final Joiner joinerKeyValue = joinerKeyValueFunction.apply(locale);
Iterable<String> renderedEntries = Iterables.transform(value.entrySet(), new SerializableFunction<Entry<? extends K, ? extends V>, String>() {
private static final long serialVersionUID = 1L;
@Override
public String apply(Entry<? extends K, ? extends V> input) {
return joinerKeyValue.join(
keyRenderer.render(input.getKey(), locale),
valueRenderer.render(input.getValue(), locale)
);
}
});
return joiner.join(renderedEntries);
}
}
public static <T> Renderer<T> fromResourceKey(String resourceKey) {
return new FromResourceKeyRenderer<>(resourceKey);
}
private static class FromResourceKeyRenderer<T> extends Renderer<T> {
private static final long serialVersionUID = 7436587266161009083L;
private final String resourceKey;
public FromResourceKeyRenderer(String resourceKey) {
super();
this.resourceKey = resourceKey;
}
@Override
public String render(final Object value, Locale locale) {
IModel<?> model = Models.transientModel(value);
return Renderer.getString(resourceKey, locale, model);
}
@Override
public String toString() {
return "fromResourceKey(" + resourceKey + ")";
}
}
public static <T> Renderer<T> fromStringFormat(String stringFormat) {
return new FromStringFormatRenderer<>(stringFormat);
}
private static class FromStringFormatRenderer<T> extends Renderer<T> {
private static final long serialVersionUID = 1L;
private final String stringFormat;
public FromStringFormatRenderer(String stringFormat) {
super();
this.stringFormat = stringFormat;
}
@Override
public String render(final Object value, Locale locale) {
return String.format(locale, stringFormat, value);
}
@Override
public String toString() {
return "fromStringFormat(" + stringFormat + ")";
}
}
public static <T extends Date> Renderer<T> fromDatePattern(final IDatePattern datePattern) {
Renderer<T> renderer = fromFormat(new SerializableFunction<Locale, DateFormat>() {
private static final long serialVersionUID = 1L;
@Override
public DateFormat apply(Locale locale) {
return new SimpleDateFormat(Localizer.get().getString(datePattern.getJavaPatternKey(), null, null, locale, null, (IModel<String>) null), locale);
}
});
if (datePattern.capitalize()) {
renderer = renderer.compose(new SerializableFunction<String, String>() {
private static final long serialVersionUID = 1L;
@Override
public String apply(String input) {
return WordUtils.capitalize(input);
}
});
}
return renderer;
}
public static <T extends Number> Renderer<T> count(String resourceKeyPrefix) {
return count(resourceKeyPrefix, null);
}
public static <T extends Number> Renderer<T> count(String resourceKeyPrefix, Renderer<? super T> numberRenderer) {
return new CountRenderer<T>(resourceKeyPrefix, numberRenderer).nullsAsNull();
}
private static class CountRenderer<T extends Number> extends Renderer<T> {
private static final long serialVersionUID = 7436587266161009083L;
private final String resourceKeyPrefix;
private final Renderer<? super T> numberRenderer;
public CountRenderer(String resourceKeyPrefix, Renderer<? super T> numberRenderer) {
super();
this.resourceKeyPrefix = resourceKeyPrefix;
this.numberRenderer = numberRenderer;
}
@Override
public String render(final T value, Locale locale) {
IModel<T> model = Models.transientModel(value);
IModel<Map<String, Object>> mapModel;
if (numberRenderer != null) {
mapModel = Models.dataMap().put("count", numberRenderer.asModel(model)).build();
} else {
mapModel = Models.dataMap().put("count", value).build();
}
StringBuilder resourceKeyBuilder = new StringBuilder(resourceKeyPrefix);
double doubleValue = value.doubleValue();
if (doubleValue == 0) {
resourceKeyBuilder.append(".zero");
} else if (-1 <= doubleValue && doubleValue <= 1) {
resourceKeyBuilder.append(".one");
} else {
resourceKeyBuilder.append(".many");
}
return Renderer.getString(resourceKeyBuilder.toString(), locale, mapModel);
}
}
/**
* <p>A simplified version of the {@link #range(String, Renderer, Renderer)} function.
* <p>Returns a static renderer for {@link Range} elements in which every value is
* rendered the same way - either with or without units.
* <p>See {@link #range(String, Renderer, Renderer)} for more information regarding the syntax, the context or
* the prerequisites of {@link Range}-rendering functions.
*
* @param rangeResourceKeyPrefix
* the prefix of the translation key pointing to the range-based display text patterns
* @param renderer
* a renderer used to display the value of a given {@link Range} bound
*/
public static <C extends Comparable<?>> Renderer<Range<C>> range(String rangeResourceKeyPrefix, Renderer<C> renderer) {
return range(rangeResourceKeyPrefix, renderer, renderer);
}
/**
* <p>Returns a static renderer for {@link Range} type elements in which values may have units.
* <p>This function uses two renderers, one for the rendering of a plain value and
* another for the rendering of a value with its unit.
* <p>Knowing whether to render the first or last element with or without its unit is left to the <i>resource</i>,
* as it may differ from one interval to another and may need to be determined dynamically.
* <p><b>Note:</b> There are four different string variables recognised within a resource: <ul>
* <li><b>${start}:</b> substituted with the lower bound rendered without a unit
* <li><b>${startUnit}:</b> substituted with the lower bound rendered with its unit
* <li><b>${end}:</b> substituted with the upper bound rendered without a unit
* <li><b>${endUnit}:</b> substituted with the upper bound rendered with its unit
* </ul>
* <p><b>Note:</b> There are several resource key suffixes dynamically read: <ul>
* <li><b><key>.both:</b> read if the range possesses a lower and an upper bound
* <li><b><key>.from:</b> read if the range possesses a lower bound but no upper bound
* <li><b><key>.upto:</b> read if the range possesses an upper bound but no lower bound
* <li><b><key>.solo:</b> read if the range possesses a lower and an upper bound which have the same value
* </ul>
* <p><b>Example:</b> The resources:<ul>
* <li><b>base.key.upto=</b><i>Up to ${endUnit}</i> may be rendered as <i>Up to 1 mile</i> or <i>Up to 3 miles</i>.
* <li><b>base.key.both=</b><i>From ${start} to ${endUnit}</i> may be rendered as <i>From 10 to 15 people</i>.
* <li><b>base.key.from=</b><i>Starting at ${startUnit}</i> may be rendered as <i>Starting at $4</i>.
* <li><b>base.key.solo=</b><i>Exactly ${startUnit}</i> may be rendered as <i>Exactly 5 camels.</i>.
* <li><b>base.key.solo=</b><i>Closing at ${end}</i> may be rendered as <i>Closing at 5:00pm</i> using a specific Date Renderer.
* </ul>
*
* @param rangeResourceKey
* the prefix of the translation key pointing to the range-based display text patterns
* @param valueRenderer <br>
* a renderer used to display the plain value of a given {@link Range} bound
* @param valueAndUnitRenderer
* a renderer used to display the value and the unit associated with a given {@link Range} bound
* @see {@link #count(String)}, {@link #count(String, Renderer)}
* for rendering any countable value with its unit
* @see {@link #fromDatePattern(IDatePattern)}, {@link #fromDateFormat(DateFormat)}
* for fully rendering dates as without-unit-values without bothering with custom units
* @throws MissingResourceException
* if the resource key formed by associating the base key and one of the suffixes cannot be found
*/
public static <C extends Comparable<?>> Renderer<Range<C>> range(String rangeResourceKey, Renderer<C> withUnitValueRenderer, Renderer<C> withoutUnitValueRenderer) {
return new RangeRenderer<C>(
rangeResourceKey,
withoutUnitValueRenderer,
(withUnitValueRenderer == null) ? withoutUnitValueRenderer : withUnitValueRenderer
)
.nullsAsNull();
}
private static class RangeRenderer<C extends Comparable<?>> extends Renderer<Range<C>> {
private static final long serialVersionUID = -3069600849637042624L;
private final String resourceKeyPrefix;
private final Renderer<C> withUnitValueRenderer;
private final Renderer<C> withoutUnitValueRenderer;
public RangeRenderer(String resourceKeyPrefix, Renderer<C> withoutUnitValueRenderer, Renderer<C> withUnitValueRenderer) {
this.resourceKeyPrefix = resourceKeyPrefix;
this.withoutUnitValueRenderer = withoutUnitValueRenderer;
this.withUnitValueRenderer = withUnitValueRenderer;
}
@Override
public String render(final Range<C> value, Locale locale) {
IModel<Map<String, Object>> mapModel;
StringBuilder resourceKeyBuilder = new StringBuilder(resourceKeyPrefix);
if (!value.hasLowerBound() && !value.hasUpperBound()) {
return null;
} else if (!value.hasLowerBound()) {
resourceKeyBuilder.append(".upto");
mapModel = Models.dataMap()
.put("end", this.withoutUnitValueRenderer.asModel(Models.transientModel(value.upperEndpoint())))
.put("endUnit", this.withUnitValueRenderer.asModel(Models.transientModel(value.upperEndpoint())))
.build();
} else if (!value.hasUpperBound()) {
resourceKeyBuilder.append(".from");
mapModel = Models.dataMap()
.put("start", this.withoutUnitValueRenderer.asModel(Models.transientModel(value.lowerEndpoint())))
.put("startUnit", this.withUnitValueRenderer.asModel(Models.transientModel(value.lowerEndpoint())))
.build();
} else if (value.lowerEndpoint().equals(value.upperEndpoint())) {
resourceKeyBuilder.append(".solo");
mapModel = Models.dataMap()
.put("start", this.withoutUnitValueRenderer.asModel(Models.transientModel(value.lowerEndpoint())))
.put("startUnit", this.withUnitValueRenderer.asModel(Models.transientModel(value.lowerEndpoint())))
.put("end", this.withoutUnitValueRenderer.asModel(Models.transientModel(value.upperEndpoint())))
.put("endUnit", this.withUnitValueRenderer.asModel(Models.transientModel(value.upperEndpoint())))
.build();
} else {
resourceKeyBuilder.append(".both");
mapModel = Models.dataMap()
.put("start", this.withoutUnitValueRenderer.asModel(Models.transientModel(value.lowerEndpoint())))
.put("startUnit", this.withUnitValueRenderer.asModel(Models.transientModel(value.lowerEndpoint())))
.put("end", this.withoutUnitValueRenderer.asModel(Models.transientModel(value.upperEndpoint())))
.put("endUnit", this.withUnitValueRenderer.asModel(Models.transientModel(value.upperEndpoint())))
.build();
}
return Renderer.getString(resourceKeyBuilder.toString(), locale, mapModel);
}
}
}