io.inverno.mod.configuration.internal.JavaStringConverter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of inverno-configuration Show documentation
Show all versions of inverno-configuration Show documentation
Inverno configuration module
/*
* Copyright 2021 Jeremy KUHN
*
* 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 io.inverno.mod.configuration.internal;
import io.inverno.mod.base.converter.ConverterException;
import io.inverno.mod.base.converter.ObjectConverter;
import java.io.File;
import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.UnknownHostException;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.time.format.DateTimeParseException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Currency;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import java.util.stream.Collectors;
import org.apache.commons.text.StringEscapeUtils;
/**
*
* Java String {@link ObjectConverter} implementation that converts primitive and common types from/to Java String literals (ie. escaping characters in a String using Java String rules).
*
*
* @author Jeremy Kuhn
* @since 1.0
*
* @see ObjectConverter
*/
public class JavaStringConverter implements ObjectConverter {
/**
* Default array list separator
*/
public static final String DEFAULT_ARRAY_LIST_SEPARATOR = ",";
private String arrayListSeparator;
/**
*
* Creates a Java String converter with the default array/list separator.
*
*/
public JavaStringConverter() {
this(DEFAULT_ARRAY_LIST_SEPARATOR);
}
/**
*
* Creates a Java String converter with the specified array/list separator.
*
*
* @param arrayListSeparator an array/list separator
*/
public JavaStringConverter(String arrayListSeparator) {
this.arrayListSeparator = arrayListSeparator;
}
/**
*
* Returns the array/list separator used to convert lists and arrays.
*
*
* @return an array/list separator
*/
public String getArrayListSeparator() {
return arrayListSeparator;
}
/**
*
* Sets the array/list separator used to convert lists and arrays.
*
*
* @param arrayListSeparator an array/list separator
*/
public void setArrayListSeparator(String arrayListSeparator) {
this.arrayListSeparator = arrayListSeparator;
}
@Override
public String encode(T value) {
return this.encode(value, value.getClass());
}
@Override
public String encode(T value, Class type) throws ConverterException {
if(value == null) {
return null;
}
if(type.isArray()) {
return this.encodeArray((Object[])value, type.getComponentType());
}
if(Collection.class.isAssignableFrom(type)) {
return this.encodeCollection((Collection>)value);
}
if(Byte.class.equals(type)) {
return this.encode((Byte)value);
}
if(Short.class.equals(type)) {
return this.encode((Short)value);
}
if(Integer.class.equals(type)) {
return this.encode((Integer)value);
}
if(Long.class.equals(type)) {
return this.encode((Long)value);
}
if(Float.class.equals(type)) {
return this.encode((Float)value);
}
if(Double.class.equals(type)) {
return this.encode((Double)value);
}
if(Character.class.equals(type)) {
return this.encode((Character)value);
}
if(Boolean.class.equals(type)) {
return this.encode((Boolean)value);
}
if(String.class.equals(type)) {
return this.encode((String)value);
}
if(BigInteger.class.isAssignableFrom(type)) {
return this.encode((BigInteger)value);
}
if(BigDecimal.class.isAssignableFrom(type)) {
return this.encode((BigDecimal)value);
}
if(LocalDate.class.equals(type)) {
return this.encode((LocalDate)value);
}
if(LocalDateTime.class.equals(type)) {
return this.encode((LocalDateTime)value);
}
if(ZonedDateTime.class.equals(type)) {
return this.encode((ZonedDateTime)value);
}
if(Currency.class.equals(type)) {
return this.encode((Currency)value);
}
if(Locale.class.equals(type)) {
return this.encode((Locale)value);
}
if(File.class.isAssignableFrom(type)) {
return this.encode((File)value);
}
if(Path.class.isAssignableFrom(type)) {
return this.encode((Path)value);
}
if(URI.class.equals(type)) {
return this.encode((URI)value);
}
if(URL.class.equals(type)) {
return this.encode((URL)value);
}
if(Pattern.class.equals(type)) {
return this.encode((Pattern)value);
}
if(InetAddress.class.isAssignableFrom(type)) {
return this.encode((InetAddress)value);
}
if(Class.class.equals(type)) {
return this.encode((Class>)value);
}
throw new ConverterException("Can't encode " + type.getCanonicalName() + " to " + String.class.getCanonicalName());
}
@SuppressWarnings("unchecked")
@Override
public String encode(T value, Type type) throws ConverterException {
if(value == null) {
return null;
}
if(type instanceof Class) {
return this.encode(value, (Class)type);
}
else if(type instanceof GenericArrayType) {
return this.encodeArray((Object[])value, ((GenericArrayType)type).getGenericComponentType());
}
else if(type instanceof ParameterizedType) {
ParameterizedType parameterizedType = ((ParameterizedType)type);
if(parameterizedType.getActualTypeArguments().length == 1) {
if(parameterizedType.getRawType() instanceof Class) {
Class> rawType = (Class>)parameterizedType.getRawType();
Type typeArgument = parameterizedType.getActualTypeArguments()[0];
if(rawType.equals(Collection.class) || rawType.equals(List.class)) {
return this.encodeList((List)value, typeArgument);
}
else if(rawType.equals(Set.class)) {
return this.encodeSet((Set)value, typeArgument);
}
}
}
}
throw new ConverterException("Can't encode " + type.getTypeName() + " to " + String.class.getCanonicalName());
}
private String encodeCollection(Collection value) {
return value != null ?value.stream().map(this::encode).collect(Collectors.joining(String.valueOf(this.arrayListSeparator))) : null;
}
private String encodeCollection(Collection value, Type type) {
return value != null ? value.stream().map(element -> this.encode(element, type)).collect(Collectors.joining(String.valueOf(this.arrayListSeparator))) : null;
}
@Override
public String encodeList(List value) {
return this.encodeCollection(value);
}
@Override
public String encodeList(List value, Class type) {
return this.encodeCollection(value, type);
}
@Override
public String encodeList(List value, Type type) {
return this.encodeCollection(value, type);
}
@Override
public String encodeSet(Set value) {
return this.encodeCollection(value);
}
@Override
public String encodeSet(Set value, Class type) {
return this.encodeCollection(value, type);
}
@Override
public String encodeSet(Set value, Type type) {
return this.encodeCollection(value, type);
}
@Override
public String encodeArray(T[] value) {
if(value == null) {
return null;
}
StringBuilder result = new StringBuilder();
for(int i = 0;i String encodeArray(T[] value, Class type) {
return this.encodeArray(value, (Type)type);
}
@Override
public String encodeArray(T[] value, Type type) {
if(value == null) {
return null;
}
StringBuilder result = new StringBuilder();
for(int i = 0;i value) throws ConverterException {
return value != null ? value.getCanonicalName() : null;
}
@SuppressWarnings("unchecked")
@Override
public T decode(String value, Class type) {
if(value == null) {
return null;
}
if(CharSequence.class.isAssignableFrom(type)) {
return (T) this.decodeString(value);
}
if(type.isArray()) {
return (T) this.decodeToArray(value, type.getComponentType());
}
if(Boolean.class.equals(type) || Boolean.TYPE.equals(type)) {
return (T) this.decodeBoolean(value);
}
if(Character.class.equals(type) || Character.TYPE.equals(type)) {
return (T) this.decodeCharacter(value);
}
if(Integer.class.equals(type) || Integer.TYPE.equals(type)) {
return (T) this.decodeInteger(value);
}
if(Long.class.equals(type) || Long.TYPE.equals(type)) {
return (T) this.decodeLong(value);
}
if(Byte.class.equals(type) || Byte.TYPE.equals(type)) {
return (T) this.decodeByte(value);
}
if(Short.class.equals(type) || Short.TYPE.equals(type)) {
return (T) this.decodeShort(value);
}
if(Float.class.equals(type) || Float.TYPE.equals(type)) {
return (T) this.decodeFloat(value);
}
if(Double.class.equals(type) || Double.TYPE.equals(type)) {
return (T) this.decodeDouble(value);
}
if(BigInteger.class.equals(type)) {
return (T) this.decodeBigInteger(value);
}
if(BigDecimal.class.equals(type)) {
return (T) this.decodeBigDecimal(value);
}
if(LocalDate.class.equals(type)) {
return (T) this.decodeLocalDate(value);
}
if(LocalDateTime.class.equals(type)) {
return (T) this.decodeLocalDateTime(value);
}
if(ZonedDateTime.class.equals(type)) {
return (T) this.decodeZonedDateTime(value);
}
if(Currency.class.equals(type)) {
return (T) this.decodeCurrency(value);
}
if(File.class.equals(type)) {
return (T) this.decodeFile(value);
}
if(Path.class.equals(type)) {
return (T) this.decodePath(value);
}
if(URI.class.equals(type)) {
return (T) this.decodeURI(value);
}
if(URL.class.equals(type)) {
return (T) this.decodeURL(value);
}
if(Pattern.class.equals(type)) {
return (T) this.decodePattern(value);
}
if(Locale.class.equals(type)) {
return (T) this.decodeLocale(value);
}
if(type.isEnum()) {
return (T) this.decodeEnum(value, type.asSubclass(Enum.class));
}
if(type.equals(Class.class)) {
return (T) this.decodeClass(value);
}
if(InetAddress.class.isAssignableFrom(type)) {
return (T) this.decodeInetAddress(value);
}
// TODO we could inject another String to object decoder based on json, xml...
// to convert other types
throw new ConverterException(value + " can't be decoded to the requested type: " + type.getCanonicalName());
}
@SuppressWarnings("unchecked")
@Override
public T decode(String value, Type type) throws ConverterException {
if(value == null) {
return null;
}
if(type instanceof Class) {
return this.decode(value, (Class)type);
}
else if(type instanceof GenericArrayType) {
return (T) this.decodeToArray(value, ((GenericArrayType)type).getGenericComponentType());
}
else if(type instanceof ParameterizedType) {
ParameterizedType parameterizedType = ((ParameterizedType)type);
if(parameterizedType.getActualTypeArguments().length == 1) {
if(parameterizedType.getRawType() instanceof Class) {
Class> rawType = (Class>)parameterizedType.getRawType();
Type typeArgument = parameterizedType.getActualTypeArguments()[0];
if(rawType.equals(Collection.class) || rawType.equals(List.class)) {
return (T) this.decodeToList(value, typeArgument);
}
else if(rawType.equals(Set.class)) {
return (T) this.decodeToSet(value, typeArgument);
}
}
}
}
throw new ConverterException("Can't decode " + String.class.getCanonicalName() + " to " + type.getTypeName());
}
@Override
public List decodeToList(String value, Class type) throws ConverterException {
return value != null ? this.decodeToList(value, (Type)type) : null;
}
@SuppressWarnings("unchecked")
@Override
public List decodeToList(String value, Type type) throws ConverterException {
if(value == null) {
return null;
}
return Arrays.stream(value.split(this.arrayListSeparator)).map(String::trim).map(item -> (T)this.decode(item, type))
.collect(Collectors.toList());
}
@Override
public Set decodeToSet(String value, Class type) throws ConverterException {
return this.decodeToSet(value, (Type)type);
}
@SuppressWarnings("unchecked")
@Override
public Set decodeToSet(String value, Type type) throws ConverterException {
if(value == null) {
return null;
}
return Arrays.stream(value.split(this.arrayListSeparator)).map(String::trim).map(item -> (T)this.decode(item, type))
.collect(Collectors.toSet());
}
@SuppressWarnings("unchecked")
@Override
public T[] decodeToArray(String value, Class type) throws ConverterException {
if(value == null) {
return null;
}
List objects = this.decodeToList(value, type);
return objects.toArray((T[]) Array.newInstance(type, objects.size()));
}
@SuppressWarnings("unchecked")
@Override
public T[] decodeToArray(String value, Type type) throws ConverterException {
if(value == null) {
return null;
}
List objects = this.decodeToList(value, type);
if(type instanceof Class) {
return objects.toArray((T[]) Array.newInstance((Class)type, objects.size()));
}
else if(type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType)type;
return objects.toArray((T[]) Array.newInstance((Class)parameterizedType.getRawType(), objects.size()));
}
else {
throw new ConverterException("Can't decode " + String.class.getCanonicalName() + " to array of " + type.getTypeName());
}
}
@Override
public Byte decodeByte(String value) throws ConverterException {
try {
return value != null ? Byte.valueOf(value) : null;
}
catch (NumberFormatException e) {
throw new ConverterException(value + " can't be decoded to the requested type", e);
}
}
@Override
public Short decodeShort(String value) throws ConverterException {
try {
return value != null ? Short.valueOf(value) : null;
}
catch (NumberFormatException e) {
throw new ConverterException(value + " can't be decoded to the requested type", e);
}
}
@Override
public Integer decodeInteger(String value) throws ConverterException {
try {
return value != null ? Integer.valueOf(value) : null;
}
catch (NumberFormatException e) {
throw new ConverterException(value + " can't be decoded to the requested type", e);
}
}
@Override
public Long decodeLong(String value) throws ConverterException {
try {
return value != null ? Long.valueOf(value) : null;
}
catch (NumberFormatException e) {
throw new ConverterException(value + " can't be decoded to the requested type", e);
}
}
@Override
public Float decodeFloat(String value) throws ConverterException {
try {
return value != null ? Float.valueOf(value) : null;
}
catch (NumberFormatException e) {
throw new ConverterException(value + " can't be decoded to the requested type", e);
}
}
@Override
public Double decodeDouble(String value) throws ConverterException {
try {
return value != null ? Double.valueOf(value) : null;
}
catch (NumberFormatException e) {
throw new ConverterException(value + " can't be decoded to the requested type", e);
}
}
@Override
public Character decodeCharacter(String value) throws ConverterException {
if(value == null) {
return null;
}
value = StringEscapeUtils.unescapeJava(value);
if (value.length() == 1) {
return value.charAt(0);
}
throw new ConverterException(value + " can't be decoded to the requested type");
}
@Override
public Boolean decodeBoolean(String value) throws ConverterException {
return value != null ? Boolean.valueOf(value) : null;
}
@Override
public String decodeString(String value) throws ConverterException {
return StringEscapeUtils.unescapeJava(value);
}
@Override
public BigInteger decodeBigInteger(String value) throws ConverterException {
try {
return value != null ? new BigInteger(value) : null;
}
catch (NumberFormatException e) {
throw new ConverterException(value + " can't be decoded to the requested type", e);
}
}
@Override
public BigDecimal decodeBigDecimal(String value) throws ConverterException {
try {
return value != null ? new BigDecimal(value) : null;
}
catch (NumberFormatException e) {
throw new ConverterException(value + " can't be decoded to the requested type", e);
}
}
@Override
public LocalDate decodeLocalDate(String value) throws ConverterException {
try {
return value != null ? LocalDate.parse(StringEscapeUtils.unescapeJava(value)) : null;
}
catch (Exception e) {
throw new ConverterException(value + " can't be decoded to the requested type", e);
}
}
@Override
public LocalDateTime decodeLocalDateTime(String value) throws ConverterException {
try {
return value != null ? LocalDateTime.parse(StringEscapeUtils.unescapeJava(value)) : null;
}
catch (DateTimeParseException e) {
throw new ConverterException(value + " can't be decoded to the requested type", e);
}
}
@Override
public ZonedDateTime decodeZonedDateTime(String value) throws ConverterException {
try {
return value != null ? ZonedDateTime.parse(StringEscapeUtils.unescapeJava(value)) : null;
}
catch (DateTimeParseException e) {
throw new ConverterException(value + " can't be decoded to the requested type", e);
}
}
@Override
public Currency decodeCurrency(String value) throws ConverterException {
try {
return value != null ? Currency.getInstance(StringEscapeUtils.unescapeJava(value)) : null;
}
catch (IllegalArgumentException e) {
throw new ConverterException(value + " can't be decoded to the requested type", e);
}
}
@Override
public Locale decodeLocale(String value) throws ConverterException {
if(value == null) {
return null;
}
String[] elements = StringEscapeUtils.unescapeJava(value).split("_");
if (elements.length >= 1 && (elements[0].length() == 2 || elements[0].length() == 0)) {
return new Locale(elements[0], elements.length >= 2 ? elements[1] : "", elements.length >= 3 ? elements[2] : "");
}
throw new ConverterException(value + " can't be decoded to the requested type");
}
@Override
public File decodeFile(String value) throws ConverterException {
return value != null ? new File(StringEscapeUtils.unescapeJava(value)) : null;
}
@Override
public Path decodePath(String value) throws ConverterException {
try {
return value != null ? Path.of(StringEscapeUtils.unescapeJava(value)) : null;
} catch (InvalidPathException e) {
throw new ConverterException(value + " can't be decoded to the requested type", e);
}
}
@Override
public URI decodeURI(String value) throws ConverterException {
try {
return value != null ? new URI(StringEscapeUtils.unescapeJava(value)) : null;
}
catch (URISyntaxException e) {
throw new ConverterException(value + " can't be decoded to the requested type", e);
}
}
@Override
public URL decodeURL(String value) throws ConverterException {
try {
return value != null ? new URL(StringEscapeUtils.unescapeJava(value)) : null;
}
catch (MalformedURLException e) {
throw new ConverterException(value + " can't be decoded to the requested type", e);
}
}
@Override
public Pattern decodePattern(String value) throws ConverterException {
try {
return value != null ? Pattern.compile(StringEscapeUtils.unescapeJava(value)) : null;
}
catch (PatternSyntaxException e) {
throw new ConverterException(value + " can't be decoded to the requested type", e);
}
}
@Override
public InetAddress decodeInetAddress(String value) throws ConverterException {
try {
return value != null ? InetAddress.getByName(StringEscapeUtils.unescapeJava(value)) : null;
}
catch (UnknownHostException e) {
throw new ConverterException(value + " can't be decoded to the requested type", e);
}
}
@Override
public Class> decodeClass(String value) throws ConverterException {
try {
return value != null ? Class.forName(StringEscapeUtils.unescapeJava(value)) : null;
}
catch (ClassNotFoundException e) {
throw new ConverterException(value + " can't be decoded to the requested type", e);
}
}
private > T decodeEnum(String value, Class type) {
try {
return value != null ? Enum.valueOf(type, value) : null;
}
catch (IllegalArgumentException e) {
throw new ConverterException(value + " can't be decoded to the requested type", e);
}
}
}