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,91 +213,24 @@ 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;
|
||||
}
|
||||
/*
|
||||
* 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) {
|
||||
} else if (config.contains(storageLocation)) { // Look in the YAML Config to see if this field exists (it should)
|
||||
/*
|
||||
* What follows is general deserialization code
|
||||
*/
|
||||
if (config.get(storageLocation) == null) { // Check for null values
|
||||
method.invoke(instance, (Object)null);
|
||||
continue;
|
||||
}
|
||||
// Handle storage of maps. Check if this field type is a Map
|
||||
if (Map.class.isAssignableFrom(propertyDescriptor.getPropertyType())) {
|
||||
// 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)
|
||||
Type keyType = collectionTypes.get(0);
|
||||
Type valueType = collectionTypes.get(1);
|
||||
// Create a map that we'll put the values into
|
||||
Map<Object,Object> value = new HashMap<>();
|
||||
// Map values are stored in a configuration section in the YAML. Check that it exists
|
||||
if (config.getConfigurationSection(storageLocation) != null) {
|
||||
// Run through the values stored
|
||||
for (String key : config.getConfigurationSection(storageLocation).getKeys(false)) {
|
||||
// Map values can be null - it is allowed here
|
||||
Object mapValue = deserialize(config.get(storageLocation + "." + key), Class.forName(valueType.getTypeName()));
|
||||
// Keys cannot be null - skip if they exist
|
||||
// Convert any serialized dots back to dots
|
||||
// In YAML dots . cause a lot of problems, so I serialize them as :dot:
|
||||
// There may be a better way to do this.
|
||||
key = key.replaceAll(":dot:", ".");
|
||||
Object mapKey = deserialize(key,Class.forName(keyType.getTypeName()));
|
||||
if (mapKey == null) {
|
||||
continue;
|
||||
}
|
||||
// Put the value in the map
|
||||
value.put(mapKey, mapValue);
|
||||
}
|
||||
}
|
||||
// Invoke the setter in the class (this is why JavaBeans requires getters and setters for every field)
|
||||
method.invoke(instance, value);
|
||||
} else if (Map.class.isAssignableFrom(propertyDescriptor.getPropertyType())) {
|
||||
// Maps
|
||||
deserializeMap(method, instance, propertyDescriptor, storageLocation, config);
|
||||
} 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);
|
||||
// Sets
|
||||
deserializeSet(method, instance, propertyDescriptor, storageLocation, config);
|
||||
} 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);
|
||||
// Lists
|
||||
deserializeLists(method, instance, propertyDescriptor, storageLocation, config);
|
||||
} 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));
|
||||
}
|
||||
}
|
||||
// Non-collections
|
||||
deserializeValue(method, instance, propertyDescriptor, storageLocation, config);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -305,6 +238,87 @@ public class YamlDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
|
||||
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)
|
||||
Type keyType = collectionTypes.get(0);
|
||||
Type valueType = collectionTypes.get(1);
|
||||
// Create a map that we'll put the values into
|
||||
Map<Object,Object> value = new HashMap<>();
|
||||
// Map values are stored in a configuration section in the YAML. Check that it exists
|
||||
if (config.getConfigurationSection(storageLocation) != null) {
|
||||
// Run through the values stored
|
||||
for (String key : config.getConfigurationSection(storageLocation).getKeys(false)) {
|
||||
// Map values can be null - it is allowed here
|
||||
Object mapValue = deserialize(config.get(storageLocation + "." + key), Class.forName(valueType.getTypeName()));
|
||||
// Keys cannot be null - skip if they exist
|
||||
// Convert any serialized dots back to dots
|
||||
// In YAML dots . cause a lot of problems, so I serialize them as :dot:
|
||||
// There may be a better way to do this.
|
||||
key = key.replaceAll(":dot:", ".");
|
||||
Object mapKey = deserialize(key,Class.forName(keyType.getTypeName()));
|
||||
if (mapKey == null) {
|
||||
continue;
|
||||
}
|
||||
// Put the value in the map
|
||||
value.put(mapKey, mapValue);
|
||||
}
|
||||
}
|
||||
// Invoke the setter in the class (this is why JavaBeans requires getters and setters for every field)
|
||||
method.invoke(instance, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of parameter types for the collection argument in this method
|
||||
* @param writeMethod - write method
|
||||
@ -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,70 +410,34 @@ public class YamlDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
|
||||
handleConfigEntryComments(configEntry, config, yamlComments, parent);
|
||||
}
|
||||
|
||||
// Adapter
|
||||
Adapter adapterNotation = field.getAnnotation(Adapter.class);
|
||||
if (adapterNotation != null && AdapterInterface.class.isAssignableFrom(adapterNotation.value())) {
|
||||
// A conversion adapter has been defined
|
||||
try {
|
||||
config.set(storageLocation, ((AdapterInterface<?,?>)adapterNotation.value().getDeclaredConstructor().newInstance()).serialize(value));
|
||||
} catch (InstantiationException | IllegalArgumentException | NoSuchMethodException | SecurityException e) {
|
||||
plugin.logError("Could not instantiate adapter " + adapterNotation.value().getName() + " " + e.getMessage());
|
||||
}
|
||||
// We are done here
|
||||
if (checkAdapter(field, config, storageLocation, value)) {
|
||||
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);
|
||||
}
|
||||
// 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
|
||||
if (filename.isEmpty()) {
|
||||
filename = id;
|
||||
}
|
||||
filename = getFilename(field, propertyDescriptor, instance, (String)value);
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
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
|
||||
String name = filename;
|
||||
String data = config.saveToString();
|
||||
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));
|
||||
@ -466,6 +447,54 @@ public class YamlDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
try {
|
||||
config.set(storageLocation, ((AdapterInterface<?,?>)adapterNotation.value().getDeclaredConstructor().newInstance()).serialize(value));
|
||||
} catch (InstantiationException | IllegalArgumentException | NoSuchMethodException | SecurityException e) {
|
||||
plugin.logError("Could not instantiate adapter " + adapterNotation.value().getName() + " " + e.getMessage());
|
||||
}
|
||||
// We are done here
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles comments that are set on a Field or a Class using the {@link ConfigComment} annotation.
|
||||
* @since 1.3.0
|
||||
|
Loading…
Reference in New Issue
Block a user