/*
* 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);
}
}