/* * Copyright 2015 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 org.springframework.data.rest.webmvc.json; import java.util.ArrayList; import java.util.List; import java.util.Locale; import org.springframework.context.NoSuchMessageException; import org.springframework.context.support.MessageSourceAccessor; import org.springframework.data.rest.core.config.EnumTranslationConfiguration; import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** * Configuration to tweak enum serialization. * * @author Oliver Gierke */ public class EnumTranslator implements EnumTranslationConfiguration { private final MessageSourceAccessor messageSourceAccessor; private boolean enableDefaultTranslation; private boolean parseEnumNameAsFallback; /** * Creates a new {@link EnumTranslator} using the given {@link MessageSourceAccessor}. * * @param messageSourceAccessor must not be {@literal null}. */ public EnumTranslator(MessageSourceAccessor messageSourceAccessor) { Assert.notNull(messageSourceAccessor, "MessageSourceAccessor must not be null!"); this.messageSourceAccessor = messageSourceAccessor; this.enableDefaultTranslation = true; this.parseEnumNameAsFallback = true; } /* * (non-Javadoc) * @see org.springframework.data.rest.core.config.EnumTranslationConfiguration#setEnableDefaultTranslation(boolean) */ @Override public void setEnableDefaultTranslation(boolean enableDefaultTranslation) { this.enableDefaultTranslation = enableDefaultTranslation; } /* * (non-Javadoc) * @see org.springframework.data.rest.core.config.EnumTranslationConfiguration#setParseEnumNameAsFallback(boolean) */ @Override public void setParseEnumNameAsFallback(boolean parseEnumNameAsFallback) { this.parseEnumNameAsFallback = parseEnumNameAsFallback; } /** * Resolves the given enum value into a {@link String} consulting the configured {@link MessageSourceAccessor} * potentially falling back to the default translation if configured. Returning the plain enum name if no resolution * applies. * * @param value must not be {@literal null}. * @return */ public String asText(Enum<?> value) { Assert.notNull(value, "Enum value must not be null!"); String code = String.format("%s.%s", value.getDeclaringClass().getName(), value.name()); try { return messageSourceAccessor.getMessage(code); } catch (NoSuchMessageException o_O) { return enableDefaultTranslation ? toDefault(value) : value.name(); } } public List<String> getValues(Class<? extends Enum<?>> type) { List<String> result = new ArrayList<String>(); for (Enum<?> value : type.getEnumConstants()) { result.add(asText(value)); } return result; } /** * Parses the given source text into the corresponding enum value using the configured {@link MessageSourceAccessor} * potentially falling back to the default translation or the plain enum name if configured. * * @param type must not be {@literal null}. * @param text can be {@literal null} * @return the resolved enum or {@literal null} if the resolution failed. */ public <T extends Enum<?>> T fromText(Class<T> type, String text) { if (!StringUtils.hasText(text)) { return null; } Assert.notNull(type, "Enum type must not be null!"); T value = resolveEnum(type, text, true); if (value != null) { return value; } value = fromDefault(type, text); // Only parse default translation if no explicit translation is available if (value != null && enableDefaultTranslation && asText(value).equals(text)) { return value; } return parseEnumNameAsFallback ? resolveEnum(type, text, false) : null; } /** * Resolves the given {@link String} text into an enum value of the given type potentially trying to resolve it * through the configured {@link MessageSourceAccessor}. * * @param type must not be {@literal null}. * @param text must not be {@literal null} or empty. * @param resolve whether to resolve the source {@link String} through the message source. * @return */ @SuppressWarnings("unchecked") private <T extends Enum<?>> T resolveEnum(Class<T> type, String text, boolean resolve) { for (Enum<?> value : type.getEnumConstants()) { String resolved = resolve ? asText(value) : value.name(); if (resolved != null && resolved.equals(text)) { return (T) value; } } return null; } /** * Renders a default translation for the given enum (capitalized, lower case, underscores replaced by spaces). * * @param value must not be {@literal null}. * @return */ private String toDefault(Enum<?> value) { return StringUtils.capitalize(value.name().toLowerCase(Locale.US).replaceAll("_", " ")); } /** * Tries to obtain an enum value assuming the given text is a default translation of the enum name. * * @param type must not be {@literal null}. * @param text must not be {@literal null} or empty. * @return */ private <T extends Enum<?>> T fromDefault(Class<T> type, String text) { return resolveEnum(type, text.toUpperCase(Locale.US).replaceAll(" ", "_"), true); } }