Skip to content

Commit

Permalink
Add support for ignore annotation / shuffle things around -- WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
ljacqu committed Nov 14, 2023
1 parent e2f32ca commit 1eaa86f
Show file tree
Hide file tree
Showing 20 changed files with 235 additions and 562 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@
<dependency>
<groupId>ch.jalu</groupId>
<artifactId>typeresolver</artifactId>
<version>0.1.0</version>
<version>0.2.0-SNAPSHOT</version>
</dependency>

<!-- Annotations for @NotNull and @Nullable -->
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/ch/jalu/configme/beanmapper/Ignore.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package ch.jalu.configme.beanmapper;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Ignore {
}
26 changes: 15 additions & 11 deletions src/main/java/ch/jalu/configme/beanmapper/MapperImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
import ch.jalu.configme.beanmapper.leafvaluehandler.LeafValueHandler;
import ch.jalu.configme.beanmapper.leafvaluehandler.LeafValueHandlerImpl;
import ch.jalu.configme.beanmapper.leafvaluehandler.MapperLeafType;
import ch.jalu.configme.beanmapper.propertydescription.BeanDescriptionFactory;
import ch.jalu.configme.beanmapper.propertydescription.BeanDescriptionFactoryImpl;
import ch.jalu.configme.beanmapper.propertydescription.BeanPropertyComments;
import ch.jalu.configme.beanmapper.propertydescription.BeanPropertyDescription;
import ch.jalu.configme.properties.convertresult.ConvertErrorRecorder;
Expand All @@ -21,6 +19,7 @@

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
Expand Down Expand Up @@ -71,23 +70,22 @@ public class MapperImpl implements Mapper {
// Fields and general configurable methods
// ---------

private final BeanDescriptionFactory beanDescriptionFactory;
private final LeafValueHandler leafValueHandler;
private final BeanInspector beanInspector = new BeanInspector(); // TODO: Make configurable, add interface...
private final BeanInspector beanInspector;

public MapperImpl() {
this(new BeanDescriptionFactoryImpl(),
this(new BeanInspector(),
new LeafValueHandlerImpl(LeafValueHandlerImpl.createDefaultLeafTypes()));
}

public MapperImpl(@NotNull BeanDescriptionFactory beanDescriptionFactory,
public MapperImpl(@NotNull BeanInspector beanInspector,
@NotNull LeafValueHandler leafValueHandler) {
this.beanDescriptionFactory = beanDescriptionFactory;
this.beanInspector = beanInspector;
this.leafValueHandler = leafValueHandler;
}

protected final @NotNull BeanDescriptionFactory getBeanDescriptionFactory() {
return beanDescriptionFactory;
protected final @NotNull BeanInspector getBeanInspector() {
return beanInspector;
}

protected final @NotNull LeafValueHandler getLeafValueHandler() {
Expand Down Expand Up @@ -135,8 +133,7 @@ public MapperImpl(@NotNull BeanDescriptionFactory beanDescriptionFactory,

// Step 3: treat as bean
Map<String, Object> mappedBean = new LinkedHashMap<>();
// TODO: Adapt me
for (BeanPropertyDescription property : beanDescriptionFactory.getAllProperties(value.getClass())) {
for (BeanPropertyDescription property : getBeanProperties(value)) {
Object exportValueOfProperty = toExportValue(property.getValue(value), exportContext);
if (exportValueOfProperty != null) {
BeanPropertyComments propComments = property.getComments();
Expand All @@ -151,6 +148,13 @@ public MapperImpl(@NotNull BeanDescriptionFactory beanDescriptionFactory,
return mappedBean;
}

@NotNull
private List<BeanPropertyDescription> getBeanProperties(@NotNull Object value) {
return beanInspector.findInstantiation(value.getClass())
.map(BeanInstantiation::getProperties)
.orElse(Collections.emptyList());
}

/**
* Handles values of types which need special handling (such as Optional). Null means the value is not
* a special type and that the export value should be built differently. Use {@link #RETURN_NULL} to
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,15 @@

import ch.jalu.configme.beanmapper.propertydescription.BeanDescriptionFactory;
import ch.jalu.configme.beanmapper.propertydescription.BeanDescriptionFactoryImpl;
import ch.jalu.configme.beanmapper.propertydescription.BeanPropertyComments;
import ch.jalu.configme.beanmapper.propertydescription.BeanPropertyDescription;
import ch.jalu.configme.beanmapper.propertydescription.FieldProperty;
import ch.jalu.configme.exception.ConfigMeException;
import ch.jalu.configme.internal.record.RecordComponent;
import ch.jalu.configme.internal.record.RecordInspector;
import ch.jalu.configme.internal.record.ReflectionHelper;
import ch.jalu.configme.properties.convertresult.ConvertErrorRecorder;
import ch.jalu.typeresolver.TypeInfo;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

public class BeanInspector {

Expand All @@ -31,140 +20,27 @@ public class BeanInspector {
public @NotNull Optional<BeanInstantiation> findInstantiation(@NotNull Class<?> clazz) {
if (recordInspector.isRecord(clazz)) {
RecordComponent[] recordComponents = recordInspector.getRecordComponents(clazz);
return Optional.of(new RecordInstantiation(clazz, Arrays.asList(recordComponents)));
List<FieldProperty> properties =
beanDescriptionFactory.createRecordProperties(clazz, recordComponents);

return Optional.of(new RecordInstantiation(clazz, properties));
}

Optional<Constructor<?>> zeroArgConstructor = getConstructor(clazz);
if (zeroArgConstructor.isPresent()) {
List<FieldProperty> properties = beanDescriptionFactory.getAllProperties2(clazz);
return Optional.of(new BeanNoArgConstructor(zeroArgConstructor.get(), properties));
List<FieldProperty> properties = beanDescriptionFactory.getAllProperties(clazz);
return Optional.of(new BeanZeroArgConstrInstantiation(zeroArgConstructor.get(), properties));
}

return Optional.empty();
}

private static @NotNull Optional<Constructor<?>> getConstructor(@NotNull Class<?> declarer,
Class<?> @NotNull ... parameters) {
static @NotNull Optional<Constructor<?>> getConstructor(@NotNull Class<?> declarer,
Class<?> @NotNull ... parameters) {
try {
return Optional.of(declarer.getDeclaredConstructor(parameters));
} catch (NoSuchMethodException ignore) {
return Optional.empty();
}
}

private static final class BeanNoArgConstructor implements BeanInstantiation {

private final Constructor<?> zeroArgsConstructor;
private final List<FieldProperty> properties;

private BeanNoArgConstructor(@NotNull Constructor<?> zeroArgsConstructor,
@NotNull List<FieldProperty> properties) {
this.zeroArgsConstructor = zeroArgsConstructor;
this.properties = properties;
}

@Override
public @NotNull List<BeanPropertyDescription> getProperties() {
return Collections.unmodifiableList(properties);
}

@Override
public @Nullable Object create(@NotNull List<Object> propertyValues,
@NotNull ConvertErrorRecorder errorRecorder) {
final Object bean;
try {
bean = zeroArgsConstructor.newInstance();
} catch (ReflectiveOperationException e) {
throw new ConfigMeException("Failed to call constructor for "
+ zeroArgsConstructor.getDeclaringClass());
}

if (propertyValues.size() != properties.size()) {
throw new ConfigMeException("Invalid property values, " + propertyValues.size() + " were given, but "
+ zeroArgsConstructor.getDeclaringClass() + " has " + properties.size() + " properties");
}

Iterator<FieldProperty> propIt = properties.iterator();
Iterator<Object> valuesIt = propertyValues.iterator();
while (propIt.hasNext() && valuesIt.hasNext()) {
FieldProperty property = propIt.next();
Object value = valuesIt.next();
if (value == null) {
if (property.getValue(bean) == null) {
return null; // No default value on field, return null -> no bean with a null value
}
errorRecorder.setHasError("Fallback to default value");
} else {
property.setValue(bean, value);
}
}
return bean;
}
}

private static final class RecordInstantiation implements BeanInstantiation {

private final Constructor<?> canonicalConstructor;
private final List<BeanPropertyDescription> properties;

public RecordInstantiation(@NotNull Class<?> clazz, @NotNull List<RecordComponent> components) {
this.properties = components.stream()
.map(RecordProperty::new)
.collect(Collectors.toList());
Class<?>[] recordTypes = components.stream().map(RecordComponent::getType).toArray(Class[]::new);
this.canonicalConstructor = getConstructor(clazz, recordTypes)
.orElseThrow(() -> new ConfigMeException("Could not get canonical constructor of " + clazz));
}

@Override
public @NotNull List<BeanPropertyDescription> getProperties() {
return properties;
}

@Override
public @Nullable Object create(@NotNull List<Object> propertyValues,
@NotNull ConvertErrorRecorder errorRecorder) {
if (propertyValues.stream().anyMatch(Objects::isNull)) {
return null; // No support for null values in records
}

Object[] properties = propertyValues.toArray();
try {
return canonicalConstructor.newInstance(properties);
} catch (IllegalArgumentException | ReflectiveOperationException e) {
// TODO: Separate clause for InvocationTargetException?
throw new ConfigMeException("Error calling record constructor for "
+ canonicalConstructor.getDeclaringClass(), e);
}
}
}

private static final class RecordProperty implements BeanPropertyDescription {

private final RecordComponent recordComponent;

private RecordProperty(@NotNull RecordComponent recordComponent) {
this.recordComponent = recordComponent;
}

@Override
public @NotNull String getName() {
return recordComponent.getName();
}

@Override
public @NotNull TypeInfo getTypeInformation() {
return TypeInfo.of(recordComponent.getGenericType());
}

@Override
public @Nullable Object getValue(@NotNull Object bean) {
throw new UnsupportedOperationException(); // TODO
}

@Override
public @NotNull BeanPropertyComments getComments() {
return BeanPropertyComments.EMPTY; // TODO
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package ch.jalu.configme.beanmapper.instantiation;

import ch.jalu.configme.beanmapper.propertydescription.BeanPropertyDescription;
import ch.jalu.configme.beanmapper.propertydescription.FieldProperty;
import ch.jalu.configme.exception.ConfigMeException;
import ch.jalu.configme.properties.convertresult.ConvertErrorRecorder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.lang.reflect.Constructor;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

final class BeanZeroArgConstrInstantiation implements BeanInstantiation {

private final Constructor<?> zeroArgsConstructor;
private final List<FieldProperty> properties;

public BeanZeroArgConstrInstantiation(@NotNull Constructor<?> zeroArgsConstructor,
@NotNull List<FieldProperty> properties) {
this.zeroArgsConstructor = zeroArgsConstructor;
this.properties = properties;
}

@Override
public @NotNull List<BeanPropertyDescription> getProperties() {
return Collections.unmodifiableList(properties);
}

@Override
public @Nullable Object create(@NotNull List<Object> propertyValues,
@NotNull ConvertErrorRecorder errorRecorder) {
final Object bean;
try {
bean = zeroArgsConstructor.newInstance();
} catch (ReflectiveOperationException e) {
throw new ConfigMeException("Failed to call constructor for "
+ zeroArgsConstructor.getDeclaringClass());
}

if (propertyValues.size() != properties.size()) {
throw new ConfigMeException("Invalid property values, " + propertyValues.size() + " were given, but "
+ zeroArgsConstructor.getDeclaringClass() + " has " + properties.size() + " properties");
}

Iterator<FieldProperty> propIt = properties.iterator();
Iterator<Object> valuesIt = propertyValues.iterator();
while (propIt.hasNext() && valuesIt.hasNext()) {
FieldProperty property = propIt.next();
Object value = valuesIt.next();
if (value == null) {
if (property.getValue(bean) == null) {
return null; // No default value on field, return null -> no bean with a null value
}
errorRecorder.setHasError("Fallback to default value");
} else {
property.setValue(bean, value);
}
}
return bean;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package ch.jalu.configme.beanmapper.instantiation;

import ch.jalu.configme.beanmapper.propertydescription.BeanPropertyDescription;
import ch.jalu.configme.beanmapper.propertydescription.FieldProperty;
import ch.jalu.configme.exception.ConfigMeException;
import ch.jalu.configme.properties.convertresult.ConvertErrorRecorder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.lang.reflect.Constructor;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

final class RecordInstantiation implements BeanInstantiation {

private final Constructor<?> canonicalConstructor;
private final List<FieldProperty> properties;

public RecordInstantiation(@NotNull Class<?> clazz, @NotNull List<FieldProperty> properties) {
this.properties = properties;
Class<?>[] recordTypes = properties.stream().map(FieldProperty::getType).toArray(Class[]::new);
this.canonicalConstructor = BeanInspector.getConstructor(clazz, recordTypes)
.orElseThrow(() -> new ConfigMeException("Could not get canonical constructor of " + clazz));
}

@Override
public @NotNull List<BeanPropertyDescription> getProperties() {
return Collections.unmodifiableList(properties);
}

@Override
public @Nullable Object create(@NotNull List<Object> propertyValues,
@NotNull ConvertErrorRecorder errorRecorder) {
if (propertyValues.stream().anyMatch(Objects::isNull)) {
return null; // No support for null values in records
}

Object[] properties = propertyValues.toArray();
try {
return canonicalConstructor.newInstance(properties);
} catch (IllegalArgumentException | ReflectiveOperationException e) {
// TODO: Separate clause for InvocationTargetException?
throw new ConfigMeException("Error calling record constructor for "
+ canonicalConstructor.getDeclaringClass(), e);
}
}
}
Loading

0 comments on commit 1eaa86f

Please sign in to comment.