Skip to content

Commit

Permalink
Move local classes to inner to reduce the number of classes in the ma…
Browse files Browse the repository at this point in the history
…in package (#1226)
  • Loading branch information
radcortez authored Sep 23, 2024
1 parent b26ba8b commit 3bb973a
Show file tree
Hide file tree
Showing 8 changed files with 355 additions and 395 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.ArrayList;
import java.util.List;
Expand All @@ -14,6 +15,7 @@
import org.eclipse.microprofile.config.inject.ConfigProperties;

import io.smallrye.common.classloader.ClassDefiner;
import io.smallrye.common.constraint.Assert;
import io.smallrye.config._private.ConfigMessages;

public final class ConfigMappingLoader {
Expand Down Expand Up @@ -181,7 +183,7 @@ static void validateAnnotations(Class<?> type) {
/**
* Do not remove this method or inline it. It is keep separate on purpose, so it is easier to substitute it with
* the GraalVM API for native image compilation.
*
* <p>
* We cannot keep dynamic references to LOOKUP, so this method may be replaced. This is not a problem, since for
* native image we can generate the mapping class bytes in the binary so we don't need to dynamically load them.
*/
Expand All @@ -204,4 +206,68 @@ public Class<? extends ConfigMappingObject> getImplementationClass() {
return implementationClass;
}
}

/**
* Implementation of {@link ConfigMappingMetadata} for MicroProfile {@link ConfigProperties}.
*/
static final class ConfigMappingClass implements ConfigMappingMetadata {
private static final ClassValue<ConfigMappingClass> cv = new ClassValue<>() {
@Override
protected ConfigMappingClass computeValue(final Class<?> classType) {
return createConfigurationClass(classType);
}
};

static ConfigMappingClass getConfigurationClass(Class<?> classType) {
Assert.checkNotNullParam("classType", classType);
return cv.get(classType);
}

private static ConfigMappingClass createConfigurationClass(final Class<?> classType) {
if (classType.isInterface() && classType.getTypeParameters().length == 0 ||
Modifier.isAbstract(classType.getModifiers()) ||
classType.isEnum()) {
return null;
}

return new ConfigMappingClass(classType);
}

private static String generateInterfaceName(final Class<?> classType) {
if (classType.isInterface() && classType.getTypeParameters().length == 0 ||
Modifier.isAbstract(classType.getModifiers()) ||
classType.isEnum()) {
throw new IllegalArgumentException();
}

return classType.getPackage().getName() +
"." +
classType.getSimpleName() +
classType.getName().hashCode() +
"I";
}

private final Class<?> classType;
private final String interfaceName;

public ConfigMappingClass(final Class<?> classType) {
this.classType = classType;
this.interfaceName = generateInterfaceName(classType);
}

@Override
public Class<?> getInterfaceType() {
return classType;
}

@Override
public String getClassName() {
return interfaceName;
}

@Override
public byte[] getClassBytes() {
return ConfigMappingGenerator.generate(classType, interfaceName);
}
}
}
208 changes: 207 additions & 1 deletion implementation/src/main/java/io/smallrye/config/Converters.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.InetAddress;
Expand Down Expand Up @@ -281,7 +286,7 @@ public static Type getConverterType(Class<?> clazz) {
* @return the implicit converter for the given type class, or {@code null} if none exists
*/
public static <T> Converter<T> getImplicitConverter(Class<? extends T> type) {
return ImplicitConverters.getConverter(type);
return Implicit.getConverter(type);
}

/**
Expand Down Expand Up @@ -1128,4 +1133,205 @@ private void processEntry(Map<K, V> map, String key, String value) {
map.put(keyConverter.convert(key), valueConverter.convert(value));
}
}

static class Implicit {

@SuppressWarnings("unchecked")
static <T> Converter<T> getConverter(Class<? extends T> clazz) {
if (clazz.isEnum()) {
return new HyphenateEnumConverter(clazz);
}

// implicit converters required by the specification
Converter<T> converter = getConverterFromStaticMethod(clazz, "of", String.class);
if (converter == null) {
converter = getConverterFromStaticMethod(clazz, "of", CharSequence.class);
if (converter == null) {
converter = getConverterFromStaticMethod(clazz, "valueOf", String.class);
if (converter == null) {
converter = getConverterFromStaticMethod(clazz, "valueOf", CharSequence.class);
if (converter == null) {
converter = getConverterFromStaticMethod(clazz, "parse", String.class);
if (converter == null) {
converter = getConverterFromStaticMethod(clazz, "parse", CharSequence.class);
if (converter == null) {
converter = getConverterFromConstructor(clazz, String.class);
if (converter == null) {
converter = getConverterFromConstructor(clazz, CharSequence.class);
}
}
}
}
}
}
}
return converter;
}

private static <T> Converter<T> getConverterFromConstructor(Class<? extends T> clazz, Class<? super String> paramType) {
try {
final Constructor<? extends T> declaredConstructor = SecuritySupport.getDeclaredConstructor(clazz, paramType);
if (!isAccessible(declaredConstructor)) {
SecuritySupport.setAccessible(declaredConstructor, true);
}
return new ConstructorConverter<>(declaredConstructor);
} catch (NoSuchMethodException e) {
return null;
}
}

private static <T> Converter<T> getConverterFromStaticMethod(Class<? extends T> clazz, String methodName,
Class<? super String> paramType) {
try {
final Method method = clazz.getMethod(methodName, paramType);
if (clazz != method.getReturnType()) {
// doesn't meet requirements of the spec
return null;
}
if (!Modifier.isStatic(method.getModifiers())) {
return null;
}
if (!isAccessible(method)) {
SecuritySupport.setAccessible(method, true);
}
return new StaticMethodConverter<>(clazz, method);
} catch (NoSuchMethodException e) {
return null;
}
}

private static boolean isAccessible(Executable e) {
return Modifier.isPublic(e.getModifiers()) && Modifier.isPublic(e.getDeclaringClass().getModifiers()) ||
e.isAccessible();
}

static class StaticMethodConverter<T> implements Converter<T>, Serializable {

private static final long serialVersionUID = 3350265927359848883L;

private final Class<? extends T> clazz;
private final Method method;

StaticMethodConverter(Class<? extends T> clazz, Method method) {
assert clazz == method.getReturnType();
this.clazz = clazz;
this.method = method;
}

@Override
public T convert(String value) {
if (value.isEmpty()) {
return null;
}
try {
return clazz.cast(method.invoke(null, value));
} catch (IllegalAccessException | InvocationTargetException e) {
throw ConfigMessages.msg.staticMethodConverterFailure(e);
}
}

Object writeReplace() {
return new Serialized(method.getDeclaringClass(), method.getName(), method.getParameterTypes()[0]);
}

static final class Serialized implements Serializable {
private static final long serialVersionUID = -6334004040897615452L;

private final Class<?> c;
@SuppressWarnings("unused")
private final String m;
@SuppressWarnings("unused")
private final Class<?> p;

Serialized(final Class<?> c, final String m, final Class<?> p) {
this.c = c;
this.m = m;
this.p = p;
}

Object readResolve() throws ObjectStreamException {
return getConverter(c);
}
}
}

static class ConstructorConverter<T> implements Converter<T>, Serializable {

private static final long serialVersionUID = 3350265927359848883L;

private final Constructor<? extends T> ctor;

public ConstructorConverter(final Constructor<? extends T> ctor) {
this.ctor = ctor;
}

@Override
public T convert(String value) {
if (value.isEmpty()) {
return null;
}
try {
return ctor.newInstance(value);
} catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
throw ConfigMessages.msg.constructorConverterFailure(e);
}
}

Object writeReplace() {
return new Serialized(ctor.getDeclaringClass(), ctor.getParameterTypes()[0]);
}

static final class Serialized implements Serializable {
private static final long serialVersionUID = -2903564775826815453L;

private final Class<?> c;
@SuppressWarnings("unused")
private final Class<?> p;

Serialized(final Class<?> c, final Class<?> p) {
this.c = c;
this.p = p;
}

Object readResolve() throws ObjectStreamException {
return getConverter(c);
}
}
}

static class HyphenateEnumConverter<E extends Enum<E>> implements Converter<E>, Serializable {
private static final long serialVersionUID = -8298320652413719873L;

private final Class<E> enumType;
private final Map<String, E> values = new HashMap<>();

public HyphenateEnumConverter(final Class<E> enumType) {
this.enumType = enumType;
for (E enumValue : this.enumType.getEnumConstants()) {
values.put(hyphenate(enumValue.name()), enumValue);
}
}

@Override
public E convert(final String value) throws IllegalArgumentException, NullPointerException {
final String trimmedValue = value.trim();
if (trimmedValue.isEmpty()) {
return null;
}

final String hyphenatedValue = hyphenate(trimmedValue);
final Enum<?> enumValue = values.get(hyphenatedValue);

if (enumValue != null) {
return enumType.cast(enumValue);
}

throw ConfigMessages.msg.cannotConvertEnum(value, enumType, String.join(",", values.keySet()));
}

private static String hyphenate(String value) {
return StringUtil.skewer(value);
}
}
}
}
Loading

0 comments on commit 3bb973a

Please sign in to comment.