Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move local classes to inner to reduce the number of classes in the main package #1226

Merged
merged 1 commit into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

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