Skip to content

Commit

Permalink
GH-83 Support serialization of methods (Resolve #83)
Browse files Browse the repository at this point in the history
  • Loading branch information
dzikoysk committed Oct 19, 2021
1 parent a20bbe3 commit 8ee1484
Show file tree
Hide file tree
Showing 20 changed files with 153 additions and 86 deletions.
4 changes: 2 additions & 2 deletions README.md → .github/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ repositories {
dependencies {
// Default
implementation 'net.dzikoysk:cdn:1.9.5'
implementation 'net.dzikoysk:cdn:1.10.0'
// Kotlin wrapper
implementation 'net.dzikoysk:cdn-kt:1.9.5'
implementation 'net.dzikoysk:cdn-kt:1.10.0'
}
```

Expand Down
7 changes: 7 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
version: 2
updates:
- package-ecosystem: "maven"
directory: "/"
schedule:
interval: "daily"
time: "16:15"
17 changes: 0 additions & 17 deletions .travis.yml

This file was deleted.

1 change: 0 additions & 1 deletion STANDARD.md

This file was deleted.

2 changes: 1 addition & 1 deletion cdn-kt/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<parent>
<artifactId>cdn-parent</artifactId>
<groupId>net.dzikoysk</groupId>
<version>1.9.5</version>
<version>1.10.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import net.dzikoysk.cdn.entity.Description

class KotlinConfiguration {

@Description("Description")
@get:Description("# Description")
var key = "value"

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ class KotlinReaderTest {
fun `should load configuration in kotlin`() {
val configuration = CdnFactory.createStandard().load("key: custom", KotlinConfiguration::class.java)
assertEquals("custom", configuration.key)

assertEquals("""
# Description
key: custom
""".trimIndent(), CdnFactory.createStandard().render(configuration))
}

}
4 changes: 1 addition & 3 deletions cdn/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<parent>
<artifactId>cdn-parent</artifactId>
<groupId>net.dzikoysk</groupId>
<version>1.9.5</version>
<version>1.10.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

Expand All @@ -36,12 +36,10 @@
<dependency>
<groupId>org.panda-lang</groupId>
<artifactId>panda-utilities</artifactId>
<version>0.5.0-alpha</version>
</dependency>
<dependency>
<groupId>org.panda-lang</groupId>
<artifactId>expressible</artifactId>
<version>1.0.13</version>
</dependency>

<!-- Benchmarks -->
Expand Down
15 changes: 14 additions & 1 deletion cdn/src/main/java/net/dzikoysk/cdn/Cdn.java
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,20 @@ public Configuration load(Path file, Charset charset) throws IOException {
* @throws Exception in case of any deserialization error
*/
public <T> T load(String source, Class<T> configurationClass) throws Exception {
return new CdnDeserializer<T>(settings).deserialize(configurationClass, load(source));
return new CdnDeserializer<T>(settings).deserialize(load(source), configurationClass);
}

/**
* Load configuration from the given source and map default {@link net.dzikoysk.cdn.model.Configuration} structure into the given configuration class.
*
* @param source the source to load
* @param instance the instance to use
* @param <T> the expected type
* @return an instance of configuration class mapped from {@link net.dzikoysk.cdn.model.Configuration} structure
* @throws Exception in case of any deserialization error
*/
public <T> T load(String source, T instance) throws Exception {
return new CdnDeserializer<T>(settings).deserialize(load(source), instance);
}

/**
Expand Down
32 changes: 16 additions & 16 deletions cdn/src/main/java/net/dzikoysk/cdn/CdnDeserializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,12 @@ public CdnDeserializer(CdnSettings settings) {
this.settings = settings;
}

public T deserialize(Class<T> scheme, Section content) throws ReflectiveOperationException {
return deserialize(scheme.getConstructor().newInstance(), content);
public T deserialize(Section source, Class<T> template) throws ReflectiveOperationException {
return deserialize(source, template.getConstructor().newInstance());
}

public T deserialize(T instance, Section content) throws ReflectiveOperationException {
deserializeToSection(instance, content);
public T deserialize(Section source, T instance) throws ReflectiveOperationException {
deserializeToSection(source, instance);

if (instance instanceof DeserializationHandler) {
DeserializationHandler<T> handler = ObjectUtils.cast(instance);
Expand All @@ -52,56 +52,56 @@ public T deserialize(T instance, Section content) throws ReflectiveOperationExce
return instance;
}

private Object deserializeToSection(Object instance, Section root) throws ReflectiveOperationException {
private Object deserializeToSection(Section source, Object instance) throws ReflectiveOperationException {
for (Field field : instance.getClass().getFields()) {
deserializeField(instance, field, root);
deserializeField(source, instance, field);
}

for (Method method : instance.getClass().getMethods()) {
deserializeMethod(instance, method, root);
deserializeMethod(source, instance, method);
}

return instance;
}

private void deserializeField(Object instance, Field field, Section root) throws ReflectiveOperationException {
private void deserializeField(Section source, Object instance, Field field) throws ReflectiveOperationException {
if (!CdnUtils.isIgnored(field)) {
deserializeMember(instance, new FieldMember(field), root);
deserializeMember(source, new FieldMember(instance, field));
}
}

private void deserializeMethod(Object instance, Method setter, Section root) throws ReflectiveOperationException {
private void deserializeMethod(Section source, Object instance, Method setter) throws ReflectiveOperationException {
try {
if (!setter.getName().startsWith("set")) {
return;
}

Method getter = instance.getClass().getMethod("get" + setter.getName().substring(3));
deserializeMember(instance, new MethodMember(setter, getter), root);
deserializeMember(source, new MethodMember(instance, setter, getter));
}
catch (NoSuchMethodException ignored) {
// cannot set this property, ignore
}
}

private void deserializeMember(Object instance, AnnotatedMember member, Section root) throws ReflectiveOperationException {
Option<Element<?>> elementValue = root.get(member.getName());
private void deserializeMember(Section source, AnnotatedMember member) throws ReflectiveOperationException {
Option<Element<?>> elementValue = source.get(member.getName());

if (elementValue.isEmpty()) {
return;
}

Element<?> element = elementValue.get();
Object defaultValue = member.getValue(instance);
Object defaultValue = member.getValue();

if (member.isAnnotationPresent(Contextual.class)) {
deserializeToSection(defaultValue, (Section) element);
deserializeToSection((Section) element, defaultValue);
return;
}

Deserializer<Object> deserializer = CdnUtils.findComposer(settings, member.getType(), member.getAnnotatedType(), member);
Object value = deserializer.deserialize(settings, element, member.getAnnotatedType(), defaultValue, false);
member.setValue(instance, value);
member.setValue(value);
}

}
72 changes: 47 additions & 25 deletions cdn/src/main/java/net/dzikoysk/cdn/CdnSerializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,11 @@
import net.dzikoysk.cdn.model.Configuration;
import net.dzikoysk.cdn.model.Section;
import net.dzikoysk.cdn.serialization.Serializer;
import net.dzikoysk.cdn.shared.AnnotatedMember;
import net.dzikoysk.cdn.shared.AnnotatedMember.FieldMember;
import net.dzikoysk.cdn.shared.AnnotatedMember.MethodMember;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
Expand All @@ -36,46 +39,65 @@ public CdnSerializer(CdnSettings settings) {
}

public Configuration serialize(Object entity) {
Configuration root = new Configuration();

try {
serialize(root, entity);
return serialize(entity, new Configuration());
}
catch (Exception exception) {
throw new IllegalStateException("Cannot access serialize member", exception);
}

return root;
}

public Section serialize(Section root, Object entity) throws Exception {
Class<?> scheme = entity.getClass();
public <S extends Section> S serialize(Object entity, S output) throws ReflectiveOperationException {
Class<?> template = entity.getClass();

for (Field field : scheme.getFields()) {
if (CdnUtils.isIgnored(field)) {
continue;
}
for (Field field : template.getFields()) {
serializeField(entity, field, output);
}

List<String> description = Arrays.stream(field.getAnnotationsByType(Description.class))
.flatMap(annotation -> Arrays.stream(annotation.value()))
.collect(Collectors.toList());
for (Method method : template.getMethods()) {
serializeMethod(entity, method, output);
}

if (field.isAnnotationPresent(Contextual.class)) {
Section section = new Section(description, CdnConstants.OBJECT_SEPARATOR, field.getName());
root.append(section);
serialize(section, field.get(entity));
continue;
}
return output;
}

Object propertyValue = field.get(entity);
private void serializeField(Object entity, Field field, Section output) throws ReflectiveOperationException {
if (!CdnUtils.isIgnored(field)) {
serializeMember(new FieldMember(entity, field), output);
}
}

if (propertyValue != null) {
Serializer<Object> serializer = CdnUtils.findComposer(settings, field.getType(), field.getAnnotatedType(), new FieldMember(field));
root.append(serializer.serialize(settings, description, field.getName(), field.getAnnotatedType(), propertyValue));
private void serializeMethod(Object entity, Method getter, Section output) throws ReflectiveOperationException {
try {
if (!getter.getName().startsWith("get")) {
return;
}

Method setter = entity.getClass().getMethod("set" + getter.getName().substring(3), getter.getReturnType());
serializeMember(new MethodMember(entity, setter, getter), output);
}
catch (NoSuchMethodException ignored) {
// cannot set this property, ignore
}
}

private void serializeMember(AnnotatedMember member, Section output) throws ReflectiveOperationException {
Object propertyValue = member.getValue();
List<String> description = Arrays.stream(member.getAnnotationsByType(Description.class))
.flatMap(annotation -> Arrays.stream(annotation.value()))
.collect(Collectors.toList());

if (member.isAnnotationPresent(Contextual.class)) {
Section section = new Section(description, CdnConstants.OBJECT_SEPARATOR, member.getName());
output.append(section);
serialize(propertyValue, section);
return;
}

return root;
if (propertyValue != null) {
Serializer<Object> serializer = CdnUtils.findComposer(settings, member.getType(), member.getAnnotatedType(), member);
output.append(serializer.serialize(settings, description, member.getName(), member.getAnnotatedType(), propertyValue));
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ public final class ContextualComposers implements Composer<Object> {
@Override
@SuppressWarnings({ "rawtypes", "unchecked" })
public Object deserialize(CdnSettings settings, Element<?> source, AnnotatedType type, Object defaultValue, boolean entryAsRecord) throws ReflectiveOperationException {
return new CdnDeserializer(settings).deserialize(CdnUtils.toClass(type.getType()), (Section) source);
return new CdnDeserializer(settings).deserialize((Section) source, CdnUtils.toClass(type.getType()));
}

@Override
public Element<?> serialize(CdnSettings settings, List<String> description, String key, AnnotatedType type, Object entity) throws Exception {
public Element<?> serialize(CdnSettings settings, List<String> description, String key, AnnotatedType type, Object entity) throws ReflectiveOperationException {
Section section = new Section(description, CdnConstants.OBJECT_SEPARATOR, key);
return new CdnSerializer(settings).serialize(section, entity);
return new CdnSerializer(settings).serialize(entity, section);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public Enum<?> deserialize(String source) {
}

@Override
public Element<?> serialize(CdnSettings settings, List<String> description, String key, AnnotatedType type, Enum<?> entity) throws Exception {
public Element<?> serialize(CdnSettings settings, List<String> description, String key, AnnotatedType type, Enum<?> entity) throws ReflectiveOperationException {
return new Entry(description, key, entity.name());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public T deserialize(CdnSettings settings, Element<?> source, AnnotatedType type

@Override
@SuppressWarnings("unchecked")
public NamedElement<?> serialize(CdnSettings settings, List<String> description, String key, AnnotatedType type, T entity) throws Exception {
public NamedElement<?> serialize(CdnSettings settings, List<String> description, String key, AnnotatedType type, T entity) throws ReflectiveOperationException {
Collection<Object> collection = (Collection<Object>) entity;

if (collection.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ else if (element instanceof Section) {

@Override
@SuppressWarnings("unchecked")
public NamedElement<?> serialize(CdnSettings settings, List<String> description, String key, AnnotatedType type, T entity) throws Exception {
public NamedElement<?> serialize(CdnSettings settings, List<String> description, String key, AnnotatedType type, T entity) throws ReflectiveOperationException {
Map<Object, Object> map = (Map<Object, Object>) entity;

if (map.isEmpty()) {
Expand Down
2 changes: 1 addition & 1 deletion cdn/src/main/java/net/dzikoysk/cdn/entity/Description.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Descriptions.class)
public @interface Description {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,6 @@
@FunctionalInterface
public interface Serializer<T> {

Element<?> serialize(CdnSettings settings, List<String> description, String key, AnnotatedType type, T entity) throws Exception;
Element<?> serialize(CdnSettings settings, List<String> description, String key, AnnotatedType type, T entity) throws ReflectiveOperationException;

}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public T deserialize(CdnSettings settings, Element<?> source, AnnotatedType type
}

@Override
public Element<?> serialize(CdnSettings settings, List<String> description, String key, AnnotatedType type, T entity) throws Exception {
public Element<?> serialize(CdnSettings settings, List<String> description, String key, AnnotatedType type, T entity) throws ReflectiveOperationException {
return serializer.serialize(settings, description, key, type, entity);
}

Expand Down
Loading

0 comments on commit 8ee1484

Please sign in to comment.