com.swirlds.config.extensions.reflection.ConfigReflectionUtils Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of swirlds-config-extensions Show documentation
Show all versions of swirlds-config-extensions Show documentation
Swirlds is a software platform designed to build fully-distributed applications that harness the power of the cloud without servers. Now you can develop applications with fairness in decision making, speed, trust and reliability, at a fraction of the cost of traditional server-based platforms.
/*
* Copyright (C) 2022-2024 Hedera Hashgraph, LLC
*
* 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 com.swirlds.config.extensions.reflection;
import com.swirlds.config.api.ConfigData;
import com.swirlds.config.api.ConfigProperty;
import com.swirlds.config.api.Configuration;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.RecordComponent;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* Some methods that are needed for the initialization of the config that internally use reflection.
*/
public final class ConfigReflectionUtils {
private ConfigReflectionUtils() {}
/**
* Returns the generic type of the class or throws an {@link IllegalArgumentException} if the given class has not
* exactly one generic type.
*
* @param parameterizedType the class
* @return the generic type of the class
*/
public static Type getSingleGenericTypeArgument(final ParameterizedType parameterizedType) {
if (parameterizedType.getActualTypeArguments().length != 1) {
throw new IllegalArgumentException("Only exactly 1 generic type is supported");
}
return parameterizedType.getActualTypeArguments()[0];
}
/**
* Returns true if the given class is public.
*
* @param type the class
* @return true if the given class is public
*/
public static boolean isPublic(final Class> type) {
return Modifier.isPublic(type.getModifiers());
}
/**
* Returns the config property name for a property of a config data object (see {@link ConfigData}).
*
* @param prefix the prefix of the config data type
* @param component the record component thatd efines the property
* @return the config property name for a property
*/
public static String getPropertyNameForConfigDataProperty(final String prefix, final RecordComponent component) {
return Optional.ofNullable(component.getAnnotation(ConfigProperty.class))
.map(propertyAnnotation -> {
if (!propertyAnnotation.value().isBlank()) {
return getPropertyNameForConfigDataProperty(prefix, propertyAnnotation.value());
} else {
return getPropertyNameForConfigDataProperty(prefix, component.getName());
}
})
.orElseGet(() -> getPropertyNameForConfigDataProperty(prefix, component.getName()));
}
/**
* Returns the config property name for a property of a config data object (see {@link ConfigData}).
*
* @param prefix the prefix of the config data type
* @param name the name of the property
* @return the config property name
*/
public static String getPropertyNameForConfigDataProperty(final String prefix, final String name) {
if (prefix.isBlank()) {
return name;
}
return prefix + "." + name;
}
/**
* Returns the name of a config data type (see {@link ConfigData}).
*
* @param type the config data type
* @return the name of a config data type
*/
public static String getNamePrefixForConfigDataRecord(final AnnotatedElement type) {
return Optional.ofNullable(type.getAnnotation(ConfigData.class))
.map(ConfigData::value)
.orElse("");
}
/**
* Returns all {@link AnnotatedProperty} that can be found for the given constraint annotation.
*
* @param constraintAnnotationType the type of the constraint annotation
* @param configuration the configuration that should be used for the search
* @param the annotation type
* @param the type of possible values
* @return all {@link AnnotatedProperty} that can be found for the given constraint annotation
*/
public static
List> getAllMatchingPropertiesForConstraintAnnotation(
final Class constraintAnnotationType, final Configuration configuration) {
Objects.requireNonNull(constraintAnnotationType, "annotationType can not be null");
Objects.requireNonNull(configuration, "configuration can not be null");
return configuration.getConfigDataTypes().stream()
.flatMap(recordType -> Arrays.stream(recordType.getRecordComponents()))
.filter(component -> component.isAnnotationPresent(constraintAnnotationType))
.map(component ->
(AnnotatedProperty) createData(constraintAnnotationType, configuration, component))
.collect(Collectors.toList());
}
/**
* Creates a {@link AnnotatedProperty} for the given values.
*
* @param annotationType the type of the annotation
* @param configuration the configuration
* @param component the component
* @param type of the annotation
* @param type of the value
* @return the AnnotatedProperty
*/
private static AnnotatedProperty createData(
final Class annotationType, final Configuration configuration, final RecordComponent component) {
try {
final A annotation = component.getAnnotation(annotationType);
final Class extends Record> recordType = (Class extends Record>) component.getDeclaringRecord();
final String propertyNamePrefix = getNamePrefixForConfigDataRecord(recordType);
final Object recordInstance = configuration.getConfigData(recordType);
final V propertyValue;
propertyValue = (V) component.getAccessor().invoke(recordInstance);
final String propertyName = getPropertyNameForConfigDataProperty(propertyNamePrefix, component);
final Class propertyType = (Class) component.getType();
return new AnnotatedProperty<>(annotation, component, propertyName, propertyValue, propertyType);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new IllegalArgumentException("Can not get the needed metadata for the given type", e);
}
}
public record AnnotatedProperty(
A annotation, RecordComponent component, String propertyName, V propertyValue, Class propertyType) {}
}