Skip to content

Commit b27b346

Browse files
committed
Added automatic serializer for custom classes based on reflections
1 parent b062d03 commit b27b346

File tree

6 files changed

+140
-5
lines changed

6 files changed

+140
-5
lines changed

README.MD

+38-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Config API for Bukkit 1.8 - 1.17 based on dynamic proxies
1010
- Automatic update of config's file after add new methods to it's interface
1111
- Support for setting new values to config
1212
- System of serializers for custom objects (e.g. ItemStack, Location)
13+
- Automatic serialization of custom objects based on reflections (mainly for simple DAO/DTO objects)
1314
- Support of comments in YAML config
1415
- Automatic translation of `&` based colors
1516

@@ -20,7 +21,7 @@ maven {
2021
url = 'https://repo.mikigal.pl/releases'
2122
}
2223
23-
compile group: 'pl.mikigal', name: 'ConfigAPI', version: '1.1.6'
24+
compile group: 'pl.mikigal', name: 'ConfigAPI', version: '1.1.7'
2425
```
2526

2627
#### Maven
@@ -33,7 +34,7 @@ compile group: 'pl.mikigal', name: 'ConfigAPI', version: '1.1.6'
3334
<dependency>
3435
<groupId>pl.mikigal</groupId>
3536
<artifactId>ConfigAPI</artifactId>
36-
<version>1.1.6</version>
37+
<version>1.1.7</version>
3738
<scope>compile</scope>
3839
</dependency>
3940
```
@@ -160,7 +161,41 @@ award:
160161
- ShapedRecipe
161162
- UUID
162163
164+
#### Automatic serializing of custom objects
165+
If you have some simple DAO/DTO object you can serialize it without writing custom serializer!
166+
167+
```java
168+
public class User implements Serializable { // It must implement Serializable interface
169+
private String username;
170+
private int kills;
171+
private transient String temporary; // Transient fields will not be serialized!
172+
173+
public User() { // It MUST have no-args constructor!
174+
175+
}
176+
177+
public User(String username, int kills, String temporary) {
178+
this.username = username;
179+
this.kills = kills;
180+
this.temporary = temporary;
181+
}
182+
183+
// Getters and setters...
184+
}
185+
186+
@ConfigName("config")
187+
public interface MyConfig extends Config {
188+
189+
void setUser(User user);
190+
191+
default User getTest() {
192+
return new User("mikigal", 1, "some text");
193+
}
194+
}
195+
```
196+
163197
#### You can also make your own serializers
198+
For more advanced objects you can make your own serializer
164199
```java
165200
public class PotionEffectSerializer extends Serializer<PotionEffect> {
166201

@@ -198,3 +233,4 @@ public class TestPlugin extends JavaPlugin {
198233

199234
}
200235
}
236+

build.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ plugins {
44
}
55

66
group 'pl.mikigal'
7-
version '1.1.6'
7+
version '1.1.7'
88

99
publishing {
1010
repositories {

src/main/java/pl/mikigal/config/ConfigInvocationHandler.java

+4
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,10 @@ private boolean updateConfigFile() {
217217
throw new InvalidConfigException("Found non getter/setter method (name: " + name + ") in " + clazz.getCanonicalName());
218218
}
219219

220+
if (method.getParameters().length != 0) {
221+
throw new InvalidConfigException("Found method with parameters (name: " + name + ") in " + clazz.getCanonicalName());
222+
}
223+
220224
if (!method.isDefault() || this.configuration.contains(this.getConfigPath(method))) {
221225
continue;
222226
}

src/main/java/pl/mikigal/config/serializer/Serializers.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
import pl.mikigal.config.serializer.universal.UniversalArraySerializer;
1010
import pl.mikigal.config.serializer.universal.UniversalCollectionSerializer;
1111
import pl.mikigal.config.serializer.universal.UniversalMapSerializer;
12+
import pl.mikigal.config.serializer.universal.UniversalObjectSerializer;
1213

14+
import java.io.Serializable;
1315
import java.util.*;
1416

1517
/**
@@ -22,7 +24,7 @@ public class Serializers {
2224
/**
2325
* Map of registered serializers
2426
*/
25-
public static final Map<Class<?>, Serializer<?>> SERIALIZERS = new HashMap<>();
27+
public static final Map<Class<?>, Serializer<?>> SERIALIZERS = new LinkedHashMap<>();
2628

2729
static {
2830
register(ItemStack.class, new ItemStackSerializer());
@@ -34,6 +36,7 @@ public class Serializers {
3436
register(Object[].class, new UniversalArraySerializer());
3537
register(Collection.class, new UniversalCollectionSerializer());
3638
register(Map.class, new UniversalMapSerializer());
39+
register(Serializable.class, new UniversalObjectSerializer());
3740
}
3841

3942
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package pl.mikigal.config.serializer.universal;
2+
3+
import pl.mikigal.config.BukkitConfiguration;
4+
import pl.mikigal.config.serializer.Serializer;
5+
6+
import java.io.Serializable;
7+
import java.lang.reflect.Field;
8+
import java.lang.reflect.Modifier;
9+
10+
/**
11+
* Helper built-in serializer for custom objects which implemenet Serializable interface.
12+
* It uses reflections to serialize all fields from given Object, which are not transient and static.
13+
* Class must have default constructor (no-args).
14+
* @see Serializer
15+
* @see Serializable
16+
* @since 1.1.7
17+
* @author Mikołaj Gałązka
18+
*/
19+
public class UniversalObjectSerializer extends Serializer<Serializable> {
20+
21+
@Override
22+
protected void saveObject(String path, Serializable object, BukkitConfiguration configuration) {
23+
this.validateDefaultConstructor(object);
24+
25+
try {
26+
for (Field field : object.getClass().getDeclaredFields()) {
27+
if (Modifier.isStatic(field.getModifiers()) || Modifier.isTransient(field.getModifiers())) {
28+
continue;
29+
}
30+
31+
field.setAccessible(true);
32+
Object value = field.get(object);
33+
34+
try {
35+
configuration.set(path + "." + configuration.getNameStyle().format(field.getName()), value);
36+
} catch (Exception e) {
37+
throw new RuntimeException("An error occurred while serializing field '" + field.getName() + "' from class '" + object.getClass().getName() + "'", e);
38+
}
39+
}
40+
41+
configuration.set(path + "." + configuration.getNameStyle().format("type"), object.getClass().getName());
42+
} catch (IllegalAccessException e) {
43+
throw new RuntimeException("An error occurred while serializing class '" + object.getClass().getName() + "'", e);
44+
}
45+
}
46+
47+
@Override
48+
public Serializable deserialize(String path, BukkitConfiguration configuration) {
49+
String classPath = configuration.getString(path + "." + configuration.getNameStyle().format("type"));
50+
Class<?> clazz;
51+
52+
try {
53+
clazz = Class.forName(classPath);
54+
} catch (ClassNotFoundException e) {
55+
throw new RuntimeException("An error occurred while deserializing class '" + classPath + "'", e);
56+
}
57+
58+
if (!Serializable.class.isAssignableFrom(clazz)) {
59+
throw new RuntimeException("Class " + classPath + " does not implements Serializable");
60+
}
61+
62+
Serializable instance;
63+
try {
64+
instance = (Serializable) clazz.newInstance();
65+
} catch (InstantiationException | IllegalAccessException e) {
66+
throw new RuntimeException("Could not create instance of class (" + classPath + ") with default constructor", e);
67+
}
68+
69+
try {
70+
for (Field field : clazz.getDeclaredFields()) {
71+
if (Modifier.isStatic(field.getModifiers()) || Modifier.isTransient(field.getModifiers())) {
72+
continue;
73+
}
74+
75+
field.setAccessible(true);
76+
field.set(instance, configuration.get(path + "." + configuration.getNameStyle().format(field.getName())));
77+
}
78+
} catch (IllegalAccessException e) {
79+
throw new RuntimeException("Could not deserialize " + classPath, e);
80+
}
81+
82+
return instance;
83+
}
84+
85+
private void validateDefaultConstructor(Object object) {
86+
try {
87+
object.getClass().getConstructor();
88+
} catch (NoSuchMethodException e) {
89+
throw new IllegalArgumentException("Class " + object.getClass().getName() + " does not have a default constructor");
90+
}
91+
}
92+
}

src/main/java/pl/mikigal/config/style/NameStyle.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,6 @@ public enum NameStyle {
5151
* @return name of field in config
5252
*/
5353
public String format(String methodName) {
54-
return CaseFormat.UPPER_CAMEL.to(this.caseFormat, methodName.replace("get", "").replace("set", ""));
54+
return caseFormat.to(this.caseFormat, methodName.replace("get", "").replace("set", ""));
5555
}
5656
}

0 commit comments

Comments
 (0)