mirror of
https://github.com/BentoBoxWorld/BentoBox.git
synced 2024-11-26 20:55:41 +01:00
Skip synthetic fields from Jacoco or compiler
When using introspection, synthetic fields should be skipped. These fields can be added by Jacoco or even the compiler so will cause problems during unit testing. Refactored YamlDatabaseHandler to be easier to understand.
This commit is contained in:
parent
d11a27dc6d
commit
9ba34d1e92
@ -31,9 +31,9 @@ import org.bukkit.World;
|
||||
import org.bukkit.configuration.MemorySection;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
import world.bentobox.bentobox.BentoBox;
|
||||
import world.bentobox.bentobox.api.configuration.ConfigComment;
|
||||
import world.bentobox.bentobox.api.configuration.ConfigEntry;
|
||||
@ -186,23 +186,23 @@ public class YamlDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
|
||||
|
||||
// Run through all the fields in the object
|
||||
for (Field field : dataObject.getDeclaredFields()) {
|
||||
// Gets the getter and setters for this field using the JavaBeans system
|
||||
// Ignore synthetic fields, such as those added by Jacoco or the compiler
|
||||
if (field.isSynthetic()) {
|
||||
continue;
|
||||
}
|
||||
// Get the getter and setters for this field using the JavaBeans system
|
||||
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(field.getName(), dataObject);
|
||||
// Get the write method
|
||||
Method method = propertyDescriptor.getWriteMethod();
|
||||
|
||||
// Information about the field
|
||||
String storageLocation = field.getName();
|
||||
/*
|
||||
* Field annotation checks
|
||||
*/
|
||||
// Check if there is a ConfigEntry annotation on the field
|
||||
ConfigEntry configEntry = field.getAnnotation(ConfigEntry.class);
|
||||
|
||||
// If there is a config annotation then do something
|
||||
if (configEntry != null && !configEntry.path().isEmpty()) {
|
||||
storageLocation = configEntry.path();
|
||||
}
|
||||
// Determine the storage location
|
||||
String storageLocation = (configEntry != null && !configEntry.path().isEmpty()) ? configEntry.path() : field.getName();
|
||||
|
||||
// Some fields need custom handling to serialize or deserialize and the programmer will need to
|
||||
// define them herself. She can add an annotation to do that.
|
||||
Adapter adapterNotation = field.getAnnotation(Adapter.class);
|
||||
@ -213,20 +213,82 @@ public class YamlDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
|
||||
// Invoke the deserialization on this value
|
||||
method.invoke(instance, ((AdapterInterface<?,?>)adapterNotation.value().getDeclaredConstructor().newInstance()).deserialize(value));
|
||||
// We are done here. If a custom adapter was defined, the rest of this method does not need to be run
|
||||
continue;
|
||||
}
|
||||
} else if (config.contains(storageLocation)) { // Look in the YAML Config to see if this field exists (it should)
|
||||
/*
|
||||
* What follows is general deserialization code
|
||||
*/
|
||||
// Look in the YAML Config to see if this field exists (it should)
|
||||
if (config.contains(storageLocation)) {
|
||||
// Check for null values
|
||||
if (config.get(storageLocation) == null) {
|
||||
if (config.get(storageLocation) == null) { // Check for null values
|
||||
method.invoke(instance, (Object)null);
|
||||
continue;
|
||||
} else if (Map.class.isAssignableFrom(propertyDescriptor.getPropertyType())) {
|
||||
// Maps
|
||||
deserializeMap(method, instance, propertyDescriptor, storageLocation, config);
|
||||
} else if (Set.class.isAssignableFrom(propertyDescriptor.getPropertyType())) {
|
||||
// Sets
|
||||
deserializeSet(method, instance, propertyDescriptor, storageLocation, config);
|
||||
} else if (List.class.isAssignableFrom(propertyDescriptor.getPropertyType())) {
|
||||
// Lists
|
||||
deserializeLists(method, instance, propertyDescriptor, storageLocation, config);
|
||||
} else {
|
||||
// Non-collections
|
||||
deserializeValue(method, instance, propertyDescriptor, storageLocation, config);
|
||||
}
|
||||
// Handle storage of maps. Check if this field type is a Map
|
||||
if (Map.class.isAssignableFrom(propertyDescriptor.getPropertyType())) {
|
||||
}
|
||||
}
|
||||
// After deserialization is complete, return the instance of the class we have created
|
||||
return instance;
|
||||
}
|
||||
|
||||
private void deserializeValue(Method method, T instance, PropertyDescriptor propertyDescriptor, String storageLocation,
|
||||
YamlConfiguration config) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
|
||||
// Not a collection. Get the value and rely on YAML to supply it
|
||||
Object value = config.get(storageLocation);
|
||||
// If the value is a yml MemorySection then something is wrong, so ignore it. Maybe an admin did some bad editing
|
||||
if (value != null && !value.getClass().equals(MemorySection.class)) {
|
||||
Object setTo = deserialize(value,propertyDescriptor.getPropertyType());
|
||||
if (!(Enum.class.isAssignableFrom(propertyDescriptor.getPropertyType()) && setTo == null)) {
|
||||
// Do not invoke null on Enums
|
||||
method.invoke(instance, setTo);
|
||||
} else {
|
||||
plugin.logError("Default setting value will be used: " + propertyDescriptor.getReadMethod().invoke(instance));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void deserializeLists(Method method, T instance, PropertyDescriptor propertyDescriptor, String storageLocation, YamlConfiguration config) throws ClassNotFoundException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
|
||||
// Note that we have no idea what type of List this is
|
||||
List<Type> collectionTypes = getCollectionParameterTypes(method);
|
||||
// collectionTypes should be only 1 long
|
||||
Type setType = collectionTypes.get(0);
|
||||
// Create an empty list
|
||||
List<Object> value = new ArrayList<>();
|
||||
// Lists are stored as lists in YAML
|
||||
if (config.getList(storageLocation) != null) {
|
||||
for (Object listValue: config.getList(storageLocation)) {
|
||||
value.add(deserialize(listValue,Class.forName(setType.getTypeName())));
|
||||
}
|
||||
}
|
||||
// Store the list using the setting
|
||||
method.invoke(instance, value);
|
||||
}
|
||||
|
||||
private void deserializeSet(Method method, T instance, PropertyDescriptor propertyDescriptor, String storageLocation, YamlConfiguration config) throws ClassNotFoundException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
|
||||
// Note that we have no idea what type this set is
|
||||
List<Type> collectionTypes = getCollectionParameterTypes(method);
|
||||
// collectionTypes should be only 1 long
|
||||
Type setType = collectionTypes.get(0);
|
||||
// Create an empty set to fill
|
||||
Set<Object> value = new HashSet<>();
|
||||
// Sets are stored as a list in YAML
|
||||
if (config.getList(storageLocation) != null) {
|
||||
for (Object listValue: config.getList(storageLocation)) {
|
||||
value.add(deserialize(listValue,Class.forName(setType.getTypeName())));
|
||||
}
|
||||
}
|
||||
// Store the set using the setter in the class
|
||||
method.invoke(instance, value);
|
||||
}
|
||||
|
||||
private void deserializeMap(Method method, T instance, PropertyDescriptor propertyDescriptor, String storageLocation, YamlConfiguration config) throws ClassNotFoundException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
|
||||
// Note that we have no idea what type of map this is, so we need to find out
|
||||
List<Type> collectionTypes = getCollectionParameterTypes(method);
|
||||
// collectionTypes should be 2 long because there are two parameters in a Map (key, value)
|
||||
@ -255,54 +317,6 @@ public class YamlDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
|
||||
}
|
||||
// Invoke the setter in the class (this is why JavaBeans requires getters and setters for every field)
|
||||
method.invoke(instance, value);
|
||||
} else if (Set.class.isAssignableFrom(propertyDescriptor.getPropertyType())) {
|
||||
// Note that we have no idea what type this set is
|
||||
List<Type> collectionTypes = getCollectionParameterTypes(method);
|
||||
// collectionTypes should be only 1 long
|
||||
Type setType = collectionTypes.get(0);
|
||||
// Create an empty set to fill
|
||||
Set<Object> value = new HashSet<>();
|
||||
// Sets are stored as a list in YAML
|
||||
if (config.getList(storageLocation) != null) {
|
||||
for (Object listValue: config.getList(storageLocation)) {
|
||||
value.add(deserialize(listValue,Class.forName(setType.getTypeName())));
|
||||
}
|
||||
}
|
||||
// Store the set using the setter in the class
|
||||
method.invoke(instance, value);
|
||||
} else if (List.class.isAssignableFrom(propertyDescriptor.getPropertyType())) {
|
||||
// Note that we have no idea what type of List this is
|
||||
List<Type> collectionTypes = getCollectionParameterTypes(method);
|
||||
// collectionTypes should be only 1 long
|
||||
Type setType = collectionTypes.get(0);
|
||||
// Create an empty list
|
||||
List<Object> value = new ArrayList<>();
|
||||
// Lists are stored as lists in YAML
|
||||
if (config.getList(storageLocation) != null) {
|
||||
for (Object listValue: config.getList(storageLocation)) {
|
||||
value.add(deserialize(listValue,Class.forName(setType.getTypeName())));
|
||||
}
|
||||
}
|
||||
// Store the list using the setting
|
||||
method.invoke(instance, value);
|
||||
} else {
|
||||
// Not a collection. Get the value and rely on YAML to supply it
|
||||
Object value = config.get(storageLocation);
|
||||
// If the value is a yml MemorySection then something is wrong, so ignore it. Maybe an admin did some bad editing
|
||||
if (value != null && !value.getClass().equals(MemorySection.class)) {
|
||||
Object setTo = deserialize(value,propertyDescriptor.getPropertyType());
|
||||
if (!(Enum.class.isAssignableFrom(propertyDescriptor.getPropertyType()) && setTo == null)) {
|
||||
// Do not invoke null on Enums
|
||||
method.invoke(instance, setTo);
|
||||
} else {
|
||||
plugin.logError("Default setting value will be used: " + propertyDescriptor.getReadMethod().invoke(instance));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// After deserialization is complete, return the instance of the class we have created
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -319,7 +333,7 @@ public class YamlDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
|
||||
// There could be more than one argument, so step through them
|
||||
for (Type genericParameterType : genericParameterTypes) {
|
||||
// If the argument is a parameter, then do something - this should always be true if the parameter is a collection
|
||||
if( genericParameterType instanceof ParameterizedType ) {
|
||||
if(genericParameterType instanceof ParameterizedType ) {
|
||||
// Get the actual type arguments of the parameter
|
||||
Type[] parameters = ((ParameterizedType)genericParameterType).getActualTypeArguments();
|
||||
result.addAll(Arrays.asList(parameters));
|
||||
@ -362,6 +376,9 @@ public class YamlDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
|
||||
|
||||
// Run through all the fields in the class that is being stored. EVERY field must have a get and set method
|
||||
for (Field field : dataObject.getDeclaredFields()) {
|
||||
if (field.isSynthetic()) {
|
||||
continue;
|
||||
}
|
||||
// Get the property descriptor for this field
|
||||
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(field.getName(), dataObject);
|
||||
// Get the read method
|
||||
@ -374,7 +391,7 @@ public class YamlDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
|
||||
// Check if there is an annotation on the field
|
||||
ConfigEntry configEntry = field.getAnnotation(ConfigEntry.class);
|
||||
|
||||
// If there is a config path annotation then do something
|
||||
// If there is a config path annotation or adapter then deal with them
|
||||
if (configEntry != null && !configEntry.path().isEmpty()) {
|
||||
if (configEntry.hidden()) {
|
||||
// If the annotation tells us to not print the config entry, then we won't.
|
||||
@ -393,7 +410,77 @@ public class YamlDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
|
||||
handleConfigEntryComments(configEntry, config, yamlComments, parent);
|
||||
}
|
||||
|
||||
// Adapter
|
||||
if (checkAdapter(field, config, storageLocation, value)) {
|
||||
continue;
|
||||
}
|
||||
// Set the filename if it has not be set already
|
||||
if (filename.isEmpty() && method.getName().equals("getUniqueId")) {
|
||||
// Save the name for when the file is saved
|
||||
filename = getFilename(field, propertyDescriptor, instance, (String)value);
|
||||
}
|
||||
// Collections need special serialization
|
||||
if (Map.class.isAssignableFrom(propertyDescriptor.getPropertyType()) && value != null) {
|
||||
serializeMap((Map<Object,Object>)value, config, storageLocation);
|
||||
} else if (Set.class.isAssignableFrom(propertyDescriptor.getPropertyType()) && value != null) {
|
||||
serializeSet((Set<Object>)value, config, storageLocation);
|
||||
} else {
|
||||
// For all other data that doesn't need special serialization
|
||||
config.set(storageLocation, serialize(value));
|
||||
}
|
||||
}
|
||||
// If the filename has not been set by now then we have a problem
|
||||
if (filename.isEmpty()) {
|
||||
throw new IllegalArgumentException("No uniqueId in class");
|
||||
}
|
||||
|
||||
// Save
|
||||
save(filename, config.saveToString(), path, yamlComments);
|
||||
}
|
||||
|
||||
private void save(String name, String data, String path, Map<String, String> yamlComments) {
|
||||
if (plugin.isEnabled()) {
|
||||
// Async
|
||||
saveQueue.add(() -> ((YamlDatabaseConnector)databaseConnector).saveYamlFile(data, path, name, yamlComments));
|
||||
} else {
|
||||
// Sync for shutdown
|
||||
((YamlDatabaseConnector)databaseConnector).saveYamlFile(data, path, name, yamlComments);
|
||||
}
|
||||
}
|
||||
|
||||
private void serializeSet(Set<Object> value, YamlConfiguration config, String storageLocation) {
|
||||
// Sets need to be serialized as string lists
|
||||
List<Object> list = new ArrayList<>();
|
||||
for (Object object : value) {
|
||||
list.add(serialize(object));
|
||||
}
|
||||
// Save the list in the config file
|
||||
config.set(storageLocation, list);
|
||||
}
|
||||
|
||||
private void serializeMap(Map<Object, Object> value, YamlConfiguration config, String storageLocation) {
|
||||
// Maps need to have keys serialized
|
||||
Map<Object, Object> result = new HashMap<>();
|
||||
for (Entry<Object, Object> object : value.entrySet()) {
|
||||
// Serialize all key and values
|
||||
String key = (String)serialize(object.getKey());
|
||||
key = key.replaceAll("\\.", ":dot:");
|
||||
result.put(key, serialize(object.getValue()));
|
||||
}
|
||||
// Save the list in the config file
|
||||
config.set(storageLocation, result);
|
||||
}
|
||||
|
||||
private String getFilename(Field field, PropertyDescriptor propertyDescriptor, T instance, String id) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
|
||||
// If the object does not have a unique name assigned to it already, one is created at random
|
||||
if (id == null || id.isEmpty()) {
|
||||
id = databaseConnector.getUniqueId(dataObject.getSimpleName());
|
||||
// Set it in the class so that it will be used next time
|
||||
propertyDescriptor.getWriteMethod().invoke(instance, id);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
private boolean checkAdapter(Field field, YamlConfiguration config, String storageLocation, Object value) throws IllegalAccessException, InvocationTargetException {
|
||||
Adapter adapterNotation = field.getAnnotation(Adapter.class);
|
||||
if (adapterNotation != null && AdapterInterface.class.isAssignableFrom(adapterNotation.value())) {
|
||||
// A conversion adapter has been defined
|
||||
@ -403,67 +490,9 @@ public class YamlDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
|
||||
plugin.logError("Could not instantiate adapter " + adapterNotation.value().getName() + " " + e.getMessage());
|
||||
}
|
||||
// We are done here
|
||||
continue;
|
||||
}
|
||||
|
||||
// Depending on the value type, it'll need serializing differently
|
||||
// Check if this field is the mandatory UniqueId field. This is used to identify this instantiation of the class
|
||||
if (method.getName().equals("getUniqueId")) {
|
||||
// If the object does not have a unique name assigned to it already, one is created at random
|
||||
String id = (String)value;
|
||||
if (value == null || id.isEmpty()) {
|
||||
id = databaseConnector.getUniqueId(dataObject.getSimpleName());
|
||||
// Set it in the class so that it will be used next time
|
||||
propertyDescriptor.getWriteMethod().invoke(instance, id);
|
||||
}
|
||||
// Save the name for when the file is saved
|
||||
if (filename.isEmpty()) {
|
||||
filename = id;
|
||||
}
|
||||
}
|
||||
// Collections need special serialization
|
||||
if (Map.class.isAssignableFrom(propertyDescriptor.getPropertyType())) {
|
||||
// Maps need to have keys serialized
|
||||
if (value != null) {
|
||||
Map<Object, Object> result = new HashMap<>();
|
||||
for (Entry<Object, Object> object : ((Map<Object,Object>)value).entrySet()) {
|
||||
// Serialize all key and values
|
||||
String key = (String)serialize(object.getKey());
|
||||
key = key.replaceAll("\\.", ":dot:");
|
||||
result.put(key, serialize(object.getValue()));
|
||||
}
|
||||
// Save the list in the config file
|
||||
config.set(storageLocation, result);
|
||||
}
|
||||
} else if (Set.class.isAssignableFrom(propertyDescriptor.getPropertyType())) {
|
||||
// Sets need to be serialized as string lists
|
||||
if (value != null) {
|
||||
List<Object> list = new ArrayList<>();
|
||||
for (Object object : (Set<Object>)value) {
|
||||
list.add(serialize(object));
|
||||
}
|
||||
// Save the list in the config file
|
||||
config.set(storageLocation, list);
|
||||
}
|
||||
} else {
|
||||
// For all other data that doesn't need special serialization
|
||||
config.set(storageLocation, serialize(value));
|
||||
}
|
||||
}
|
||||
if (filename.isEmpty()) {
|
||||
throw new IllegalArgumentException("No uniqueId in class");
|
||||
}
|
||||
|
||||
// Save
|
||||
String name = filename;
|
||||
String data = config.saveToString();
|
||||
if (plugin.isEnabled()) {
|
||||
// Async
|
||||
saveQueue.add(() -> ((YamlDatabaseConnector)databaseConnector).saveYamlFile(data, path, name, yamlComments));
|
||||
} else {
|
||||
// Sync for shutdown
|
||||
((YamlDatabaseConnector)databaseConnector).saveYamlFile(data, path, name, yamlComments);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user