Java tutorial
/* * Copyright 2014 the original author or authors. * * 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 com.carlomicieli.jtrains.value.objects; import com.carlomicieli.jtrains.AppGlobals; import com.carlomicieli.jtrains.validation.ISOValidationUtils; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonSerializable; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.jsontype.TypeSerializer; import com.google.common.collect.ImmutableMap; import java.io.IOException; import java.util.*; import java.util.function.Supplier; import java.util.stream.Collectors; /** * It represents a container for different value for the same field, every * value is specific to a particular {@code Locale}. * * <p> * <pre> * LocalizedField<String> enLabel = LocalizedField.of("English, by default"); * String en = enLabel.get(Locale.ENGLISH).orElse(null); // yields "English, by default" * * LocalizedField<String> frLabel = LocalizedField.of(Locale.FRENCH, "Joyeaux Noel"); * String fr = frLabel.get(Locale.FRENCH).orElse(null); // yields "Joyeaux Noel" * String de = frLabel.get(Locale.GERMAN).orElse(LocalizedField.defaultSupplier()); // calls the default supplier * </pre> * </p> * <p> * {@code LocalizedField}s are immutable objects, methods that attempt to change the current {@code LocalizedField} * are actually returning new objects. * * <pre> * LocalizedField<String> enAndFrLabel = enLabel.with(Locale.FRENCH, "Joyeaux Noel"); * assertThat(enAndFrLabel) * .containsEntry(Locale.ENGLISH, "English, by default") * .containsEntry(Locale.FRENCH, "Joyeaux Noel"); * </pre> * </p> * <p> * This class can be {@code serialized} into Json. As this class is immutable is therefore thread-safe. * </p> * * @author Carlo Micieli * @since 1.0 * * @param <T> the type of the localized element */ public final class LocalizedField<T> implements JsonSerializable { private final Map<String, T> values; @JsonCreator private LocalizedField(Map<String, T> values) { this.values = validateValues(values); } /** * Returns the size of the current {@code LocalizedField}. * @return the size. */ public int size() { return this.values.size(); } /** * Checks whether the current field has default value. * @return {@code true} if contains a default value; {@code false} otherwise */ public boolean containsDefault() { return this.values.containsKey(AppGlobals.DEFAULT_LANGUAGE); } /** * Checks whether the current field has a value for the provided {@code Locale}. * @param locale the {@code Locale} * @return {@code true} if contains a value; {@code false} otherwise */ public boolean contains(Locale locale) { return this.values.containsKey(keyFromLocale(locale)); } /** * Returns the localized value for the provided {@code Locale}. * @param lang the {@code Locale} * @return the value to which the specified {@code Locale} is mapped; * or {@code Optional#empty()} if no value is mapped with the {@code Locale} */ public Optional<T> get(Locale lang) { return Optional.ofNullable(values.get(keyFromLocale(lang))); } /** * Returns the localized value for the provided {@code Locale} if found, if * such value is not found then it will try a second attempt with the default * language. * * @param lang the {@code Locale} * @return the value to which the specified {@code Locale} is mapped; * or {@code null} if no value is mapped with the {@code Locale} */ public T getOrDefault(Locale lang) { return Optional.ofNullable(values.get(keyFromLocale(lang))).orElseGet(defaultSupplier()); } /** * Returns the {@code Set} of the {@code Locale}s used in the current field. * @return the {@code Locale}s set */ public Set<Locale> locales() { return this.values.keySet().stream().map(LocalizedField::localeFromKey).collect(Collectors.toSet()); } /** * Creates a new {@code LocalizedField} with the provided value assigned * to the default {@code Locale}. * * @param value the value * @return a {@code LocalizedField} */ public static <T> LocalizedField<T> of(T value) { Objects.requireNonNull(value); String defaultLocale = keyFromLocale(AppGlobals.DEFAULT_LOCALE); return new LocalizedField<>(ImmutableMap.of(defaultLocale, value)); } /** * Creates a new {@code LocalizedField} with the provided value and {@code Locale}. * @param value the value * @return a {@code LocalizedField} */ public static <T> LocalizedField<T> of(Locale lang, T value) { Objects.requireNonNull(lang); Objects.requireNonNull(value); String key = keyFromLocale(lang); return new LocalizedField<>(ImmutableMap.of(key, value)); } /** * Builds a new {@code LocalizedField} using the value from the provided map. * <p> * The method returns {@code null} if the provided map is either null or * empty. * </p> * * @param values the map * @return a {@code LocalizedField} */ public static <T> LocalizedField<T> of(Map<String, T> values) { if (values == null || values.isEmpty()) { return null; } return new LocalizedField<>(values); } /** * Returns the default {@code Supplier} for the current {@code LocalizedField} object. * @return the {@code Supplier} producing the default value. */ public Supplier<T> defaultSupplier() { return () -> this.values.get(AppGlobals.DEFAULT_LANGUAGE); } /** * Return a sub map for the provided list of {@code Locale}s. * <p> * This method will return an immutable {@code Map} with an item for * each of the provided {@code Locale}. When the appropriate localized message * is not found, the default value is returned instead. * </p> * <p> * The result {@code Map} is not preserving the order of the arguments. * </p> * * @param locales a list of {@code Locale}s * @return a sub map of localized value */ public Map<Locale, T> getValues(Locale... locales) { Map<Locale, T> map = new HashMap<>(); for (Locale l : locales) { map.put(l, this.get(l).orElse(null)); } return Collections.unmodifiableMap(map); } /** * Returns {@code true} if the provided {@code Locale} is the default for * the current instance. * * @param locale the {@code Locale} * @return {@code true} if the provided {@code Locale} is the default for * the current instance. */ public static boolean isDefaultLocale(Locale locale) { return locale.equals(AppGlobals.DEFAULT_LOCALE); } /** * Adds a new value to this {@code LocalizedField} for the default language * and return a brand new object. * * @param value the new value * @return a new {@code LocalizedField} object */ public LocalizedField<T> with(T value) { return with(AppGlobals.DEFAULT_LOCALE, value); } /** * Adds a new value to this {@code LocalizedField} and return a brand new object. * @param locale the {@code Locale} for the new value * @param value the new value * @return a new {@code LocalizedField} object */ public LocalizedField<T> with(Locale locale, T value) { Objects.requireNonNull(locale, "LocalizedField: locale is required"); Objects.requireNonNull(value, "LocalizedField: value is required"); Map<String, T> map = new HashMap<>(); map.put(keyFromLocale(locale), value); map.putAll(values); return new LocalizedField<>(map); } @Override public String toString() { return this.values.toString(); } @Override public boolean equals(Object obj) { if (obj == this) return true; if (!(obj instanceof LocalizedField<?>)) return false; LocalizedField<?> that = (LocalizedField<?>) obj; return Objects.equals(this.values, that.values); } @Override public int hashCode() { return Objects.hash(this.values); } /** * Returns the current {@code LocalizedField} as a Java {@code Map}. * @return a Java {@code Map}. */ public Map<String, T> toMap() { return values; } private static String keyFromLocale(Locale locale) { return locale.getLanguage(); } private static Locale localeFromKey(String key) { return new Locale.Builder().setLanguage(key).build(); } private static <V> Map<String, V> validateValues(Map<String, V> values) { for (String locale : values.keySet()) { sanitizeLanguage(locale); } return ImmutableMap.copyOf(values); } private static String sanitizeLanguage(String lang) { Locale l = Locale.forLanguageTag(lang); if (!ISOValidationUtils.countryIsValid(l.getCountry())) { throw new IllegalArgumentException("'" + lang + "' is not a valid locale code (country)."); } if (!ISOValidationUtils.languageIsValid(l.getLanguage())) { throw new IllegalArgumentException("'" + lang + "' is not a valid locale code (language)."); } return l.getLanguage(); } @Override public void serialize(JsonGenerator json, SerializerProvider provider) throws IOException { json.writeObject(values); } @Override public void serializeWithType(JsonGenerator json, SerializerProvider provider, TypeSerializer typeSer) throws IOException { json.writeObject(values); } }