hm.binkley.util.Converter Maven / Gradle / Ivy
/*
* This is free and unencumbered software released into the public domain.
*
* Anyone is free to copy, modify, publish, use, compile, sell, or
* distribute this software, either in source code form or as a compiled
* binary, for any purpose, commercial or non-commercial, and by any
* means.
*
* In jurisdictions that recognize copyright laws, the author or authors
* of this software dedicate any and all copyright interest in the
* software to the public domain. We make this dedication for the benefit
* of the public at large and to the detriment of our heirs and
* successors. We intend this dedication to be an overt act of
* relinquishment in perpetuity of all present and future rights to this
* software under copyright law.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* For more information, please refer to .
*/
package hm.binkley.util;
import com.google.common.net.HostAndPort;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.annotation.Nonnull;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.URI;
import java.net.URL;
import java.nio.file.Paths;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.Period;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;
import static com.google.common.base.Throwables.getRootCause;
import static java.lang.Thread.currentThread;
import static java.net.InetSocketAddress.createUnresolved;
import static java.util.Arrays.asList;
/**
* {@code Converter} needs documentation.
*
* @author B. K. Oxley (binkley)
* @todo Needs documentation.
* @todo Replace with prefix->class mapping and use the other Converter
*/
public final class Converter {
private final Map> factories
= new ConcurrentHashMap<>();
public Converter() {
register("address", value -> {
final HostAndPort parsed = HostAndPort.fromString(value).requireBracketsForIPv6();
return createUnresolved(parsed.getHostText(), parsed.getPort());
});
register("bundle", ResourceBundle::getBundle);
register("byte", Byte::valueOf);
register("class", Class::forName);
register("date", LocalDate::parse);
register("decimal", BigDecimal::new);
register("double", Double::valueOf);
register("duration", Duration::parse);
register("file", File::new);
register("float", Float::valueOf);
register("inet", InetAddress::getByName);
register("int", Integer::valueOf);
register("integer", BigInteger::new);
register("long", Long::valueOf);
register("path", Paths::get);
register("period", Period::parse);
register("regex", Pattern::compile);
register("resource",
value -> new DefaultResourceLoader(currentThread().getContextClassLoader())
.getResource(value));
register("resource*", value -> asList(
new PathMatchingResourcePatternResolver(currentThread().getContextClassLoader())
.getResources(value)));
register("short", Short::valueOf);
register("time", LocalDateTime::parse);
register("timestamp", Instant::parse);
register("tz", TimeZone::getTimeZone);
register("uri", URI::create);
register("url", URL::new);
}
/**
* Registers a new alias mapping for converting property values to typed objects using the given
* prefix and factory.
*
* The type prefix represents a Java or JDK type as listed here:
*
Prefix Type address {@code
* java.net.InetSocketAddress} bundle {@code
* java.util.ResourceBundle} byte {@code java.lang.Byte}
* class java.lang.Class date {@code
* java.time.LocalDate} decimal {@code java.math.BigDecimal}
* double {@code java.lang.Double} duration {@code
* java.time.Duration} file {@code java.io.File}
* float {@code java.lang.Float} inet {@code
* java.net.InetAddress} int {@code java.lang.Integer}
* integer {@code java.math.BigInteger} long {@code
* java.lang.Long} path {@code java.nio.file.Path}
* period {@code java.time.Period} resource
* regex {@code java.util.regex.Pattern} {@code
* org.springframework.core.io.Resource} resource* {@code
* java.util.List<org.springframework.core.io.Resource>} short
* {@code java.lang.Short} time {@code
* java.time.LocalDateTime} timestamp {@code
* java.time.Instant} tz {@code java.util.TimeZone}
* uri {@code java.net.URI} url {@code
* java.net.URL}
*
* @param prefix the alias prefix, never missing
* @param factory the factory, never missing
*
* @throws DuplicateConversionException if prefix is already registered
*/
public void register(@Nonnull final String prefix, @Nonnull final Conversion, ?> factory)
throws DuplicateConversionException {
if (null != factories.putIfAbsent(prefix, factory))
throw new DuplicateConversionException(prefix);
}
/** @todo Documentation */
@SuppressWarnings("unchecked")
public T convert(@Nonnull final String key, @Nonnull final String value)
throws Exception {
return (T) factoryFor(key).convert(value);
}
@Nonnull
@SuppressWarnings({"unchecked", "ReuseOfLocalVariable"})
private Conversion factoryFor(final String type) {
// Look for alias first
Conversion factory = (Conversion) factories.get(type);
if (null != factory)
return factory;
final Class token = tokenFor(type);
// Try Type.valueOf
factory = invokeValueOf(token);
if (null != factory)
return factory;
// Try Type.of
factory = invokeOf(token);
if (null != factory)
return factory;
// Try new Type
factory = invokeConstructor(token);
if (null != factory)
return factory;
throw new Bug("Unsupported conversion: %s", type);
}
@SuppressWarnings("unchecked")
private static Conversion invokeValueOf(final Class token)
throws NoSuchMethodError {
try {
final Method method = token.getMethod("valueOf", String.class);
return value -> {
try {
return (T) method.invoke(null, value);
} catch (final IllegalAccessException e) {
final IllegalAccessError x = new IllegalAccessError(e.getMessage());
x.setStackTrace(e.getStackTrace());
throw x;
} catch (final InvocationTargetException e) {
final Throwable root = getRootCause(e);
final RuntimeException x = new RuntimeException(root);
x.setStackTrace(root.getStackTrace());
throw x;
}
};
} catch (final NoSuchMethodException ignored) {
return null;
}
}
@SuppressWarnings("unchecked")
private static Conversion invokeOf(final Class token)
throws NoSuchMethodError {
try {
final Method method = token.getMethod("of", String.class);
return value -> {
try {
return (T) method.invoke(null, value);
} catch (final IllegalAccessException e) {
final IllegalAccessError x = new IllegalAccessError(e.getMessage());
x.setStackTrace(e.getStackTrace());
throw x;
} catch (final InvocationTargetException e) {
final Throwable root = getRootCause(e);
final RuntimeException x = new RuntimeException(root);
x.setStackTrace(root.getStackTrace());
throw x;
}
};
} catch (final NoSuchMethodException ignored) {
return null;
}
}
private static Conversion invokeConstructor(final Class token)
throws NoSuchMethodError {
try {
final Constructor ctor = token.getConstructor(String.class);
return value -> {
try {
return ctor.newInstance(value);
} catch (final IllegalAccessException e) {
final IllegalAccessError x = new IllegalAccessError(e.getMessage());
x.setStackTrace(e.getStackTrace());
throw x;
} catch (final InvocationTargetException e) {
final Throwable root = getRootCause(e);
final RuntimeException x = new RuntimeException(root);
x.setStackTrace(root.getStackTrace());
throw x;
} catch (final InstantiationException e) {
final InstantiationError x = new InstantiationError(e.getMessage());
x.setStackTrace(e.getStackTrace());
throw x;
}
};
} catch (final NoSuchMethodException ignored) {
return null;
}
}
@SuppressWarnings("unchecked")
private static Class tokenFor(final String type) {
try {
return (Class) Class.forName(type);
} catch (final ClassNotFoundException e) {
final IllegalArgumentException x = new IllegalArgumentException(type + ": " + e);
x.setStackTrace(e.getStackTrace());
throw x;
}
}
/**
* Converts a string property value into a typed object.
*
* @param the converted type
* @param the exception type on failed converstion, {@code RuntimeException} if none
*/
@FunctionalInterface
public interface Conversion {
/**
* Converts the given property value into a typed object.
*
* @param value the property value, never missing
*
* @return the typed object
*
* @throws E if conversion fails
*/
T convert(@Nonnull final String value)
throws E;
}
/** @todo Documentation */
public static class DuplicateConversionException
extends IllegalArgumentException {
public DuplicateConversionException(final String key) {
super(key);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy