More work on the config code

This commit is contained in:
snowleo 2011-11-23 02:43:38 +01:00
parent e683ce5751
commit 8aaaed2ef8
34 changed files with 1016 additions and 298 deletions

View file

@ -862,4 +862,9 @@ public abstract class UserData extends PlayerExtension implements IConf
config.setProperty("timestamps.kits", kitTimestamps);
config.save();
}
public void save()
{
config.save();
}
}

View file

@ -8,7 +8,7 @@ import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = false)
public class Backup extends StorageObject
public class Backup implements StorageObject
{
@Comment("Interval in minutes")
private long interval = 60;

View file

@ -8,7 +8,7 @@ import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = false)
public class Chat extends StorageObject
public class Chat implements StorageObject
{
@Comment("The character(s) to prefix all nicknames, so that you know they are not true usernames.")
private String nicknamePrefix = "~";

View file

@ -12,7 +12,7 @@ import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = false)
public class Commands extends StorageObject
public class Commands implements StorageObject
{
private Afk afk = new Afk();
private God god = new God();

View file

@ -1,7 +1,7 @@
package com.earth2me.essentials.settings;
import com.earth2me.essentials.storage.Comment;
import com.earth2me.essentials.storage.MapType;
import com.earth2me.essentials.storage.MapValueType;
import com.earth2me.essentials.storage.StorageObject;
import java.util.HashMap;
import java.util.Map;
@ -11,11 +11,11 @@ import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = false)
public class Economy extends StorageObject
public class Economy implements StorageObject
{
@Comment("Defines the balance with which new players begin. Defaults to 0.")
private double startingBalance = 0.0;
@MapType(Double.class)
@MapValueType(Double.class)
@Comment("Defines the cost to use the given commands PER USE")
private Map<String, Double> commandCosts = new HashMap<String, Double>();
@Comment("Set this to a currency symbol you want to use.")
@ -39,4 +39,5 @@ public class Economy extends StorageObject
}
@Comment("Enable this to log all interactions with trade/buy/sell signs and sell command")
private boolean logEnabled = false;
private Worth worth = new Worth();
}

View file

@ -8,7 +8,7 @@ import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = false)
public class General extends StorageObject
public class General implements StorageObject
{
@Comment("Backup runs a command while saving is disabled")
private Backup backup = new Backup();

View file

@ -8,7 +8,7 @@ import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = false)
public class GroupOptions extends StorageObject
public class GroupOptions implements StorageObject
{
@Comment("Message format of chat messages")
private String messageFormat;

View file

@ -1,7 +1,7 @@
package com.earth2me.essentials.settings;
import com.earth2me.essentials.storage.Comment;
import com.earth2me.essentials.storage.MapType;
import com.earth2me.essentials.storage.MapValueType;
import com.earth2me.essentials.storage.StorageObject;
import java.util.LinkedHashMap;
import lombok.Data;
@ -10,7 +10,7 @@ import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = false)
public class Groups extends StorageObject
public class Groups implements StorageObject
{
public Groups()
{
@ -23,6 +23,6 @@ public class Groups extends StorageObject
"All group names have to be lower case.",
"The groups can be connected to users using the permission essentials.groups.groupname"
})
@MapType(GroupOptions.class)
@MapValueType(GroupOptions.class)
private LinkedHashMap<String, GroupOptions> groups = new LinkedHashMap<String, GroupOptions>();
}

View file

@ -1,28 +0,0 @@
package com.earth2me.essentials.settings;
import com.earth2me.essentials.storage.StorageObject;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.bukkit.Server;
@Data
@EqualsAndHashCode(callSuper = false)
public class Location extends StorageObject
{
private String worldName = "Test";
private double x;
private double y;
private double z;
private Float yaw;
private Float pitch;
public org.bukkit.Location getBukkit(Server server)
{
if (yaw == null || pitch == null)
{
return new org.bukkit.Location(server.getWorld(worldName), x, y, z);
}
return new org.bukkit.Location(server.getWorld(worldName), x, y, z, yaw, pitch);
}
}

View file

@ -0,0 +1,22 @@
package com.earth2me.essentials.settings;
import com.earth2me.essentials.storage.MapValueType;
import com.earth2me.essentials.storage.StorageObject;
import java.util.HashMap;
import java.util.Map;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.bukkit.Location;
@Data
@EqualsAndHashCode(callSuper = false)
public class Locations implements StorageObject
{
@MapValueType(Location.class)
Map<String, Location> jails = new HashMap<String, Location>();
@MapValueType(Location.class)
Map<String, Location> warps = new HashMap<String, Location>();
@MapValueType(Location.class)
Map<String, Location> spawns = new HashMap<String, Location>();
}

View file

@ -8,7 +8,7 @@ import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = false)
public class Settings extends StorageObject
public class Settings implements StorageObject
{
@Comment(
{

View file

@ -0,0 +1,29 @@
package com.earth2me.essentials.settings;
import com.earth2me.essentials.storage.MapKeyType;
import com.earth2me.essentials.storage.MapValueType;
import com.earth2me.essentials.storage.StorageObject;
import java.util.HashMap;
import java.util.Map;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.bukkit.Material;
import org.bukkit.material.MaterialData;
@Data
@EqualsAndHashCode(callSuper = false)
public class Worth implements StorageObject
{
@MapKeyType(MaterialData.class)
@MapValueType(Double.class)
private Map<MaterialData, Double> sell = new HashMap<MaterialData, Double>();
@MapKeyType(MaterialData.class)
@MapValueType(Double.class)
private Map<MaterialData, Double> buy = new HashMap<MaterialData, Double>();
public Worth()
{
sell.put(new MaterialData(Material.APPLE, (byte)0), 1.0);
}
}

View file

@ -8,7 +8,7 @@ import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = false)
public class Afk extends StorageObject
public class Afk implements StorageObject
{
@Comment(
{

View file

@ -8,7 +8,7 @@ import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = false)
public class God extends StorageObject
public class God implements StorageObject
{
@Comment("Turn off god mode when people exit")
private boolean removeOnDisconnect = false;

View file

@ -8,7 +8,7 @@ import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = false)
public class Help extends StorageObject
public class Help implements StorageObject
{
@Comment("Show other plugins commands in help")
private boolean showNonEssCommandsInHelp = true;

View file

@ -8,7 +8,7 @@ import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = false)
public class Home extends StorageObject
public class Home implements StorageObject
{
@Comment("When players die, should they respawn at their homes, instead of the spawnpoint?")
private boolean respawnAtHome = false;

View file

@ -1,25 +1,28 @@
package com.earth2me.essentials.settings.commands;
import com.earth2me.essentials.storage.MapType;
import com.earth2me.essentials.storage.MapValueType;
import com.earth2me.essentials.storage.StorageObject;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
@Data
@EqualsAndHashCode(callSuper = false)
public class Kit extends StorageObject
public class Kit implements StorageObject
{
public Kit()
{
final KitObject kit = new KitObject();
kit.setDelay(10.0);
kit.setItems(Arrays.asList("277 1,278 1,279 1".split(",")));
kit.getItems().add(new ItemStack(Material.DIAMOND_SPADE, 1));
kit.getItems().add(new ItemStack(Material.DIAMOND_PICKAXE, 1));
kit.getItems().add(new ItemStack(Material.DIAMOND_AXE, 1));
kits.put("tools", kit);
}
@MapType(KitObject.class)
@MapValueType(KitObject.class)
private Map<String, KitObject> kits = new HashMap<String, KitObject>();
}

View file

@ -6,13 +6,14 @@ import java.util.ArrayList;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.bukkit.inventory.ItemStack;
@Data
@EqualsAndHashCode(callSuper = false)
public class KitObject extends StorageObject
public class KitObject implements StorageObject
{
@ListType
private List<String> items = new ArrayList<String>();
@ListType(ItemStack.class)
private List<ItemStack> items = new ArrayList<ItemStack>();
private Double delay;
}

View file

@ -8,7 +8,7 @@ import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = false)
public class Lightning extends StorageObject
public class Lightning implements StorageObject
{
@Comment("Shall we notify users when using /lightning")
private boolean warnPlayer = true;

View file

@ -8,7 +8,7 @@ import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = false)
public class Spawnmob extends StorageObject
public class Spawnmob implements StorageObject
{
@Comment("The maximum amount of monsters, a player can spawn with a call of /spawnmob.")
private int limit = 10;

View file

@ -0,0 +1,182 @@
package com.earth2me.essentials.storage;
import java.util.regex.Pattern;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.inventory.ItemStack;
import org.bukkit.material.MaterialData;
import org.yaml.snakeyaml.constructor.Constructor;
import org.yaml.snakeyaml.nodes.MappingNode;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.NodeId;
import org.yaml.snakeyaml.nodes.NodeTuple;
import org.yaml.snakeyaml.nodes.ScalarNode;
public class BukkitConstructor extends Constructor
{
private final transient Pattern NUMPATTERN = Pattern.compile("\\d+");
public BukkitConstructor(Class clazz)
{
super(clazz);
yamlClassConstructors.put(NodeId.scalar, new ConstructBukkitScalar());
yamlClassConstructors.put(NodeId.mapping, new ConstructBukkitMapping());
}
private class ConstructBukkitScalar extends ConstructScalar
{
@Override
public Object construct(final Node node)
{
if (node.getType().equals(Material.class))
{
final String val = (String)constructScalar((ScalarNode)node);
Material mat;
if (NUMPATTERN.matcher(val).matches())
{
final int typeId = Integer.parseInt(val);
mat = Material.getMaterial(typeId);
}
else
{
mat = Material.matchMaterial(val);
}
return mat;
}
if (node.getType().equals(MaterialData.class))
{
final String val = (String)constructScalar((ScalarNode)node);
if (val.isEmpty())
{
return null;
}
final String[] split = val.split("[:+',;.]", 2);
if (split.length == 0)
{
return null;
}
Material mat;
if (NUMPATTERN.matcher(split[0]).matches())
{
final int typeId = Integer.parseInt(split[0]);
mat = Material.getMaterial(typeId);
}
else
{
mat = Material.matchMaterial(split[0]);
}
byte data = 0;
if (split.length == 2 && NUMPATTERN.matcher(split[1]).matches())
{
data = Byte.parseByte(split[1]);
}
return new MaterialData(mat, data);
}
if (node.getType().equals(ItemStack.class))
{
final String val = (String)constructScalar((ScalarNode)node);
if (val.isEmpty())
{
return null;
}
final String[] split1 = val.split("\\W", 2);
if (split1.length == 0)
{
return null;
}
final String[] split2 = split1[0].split("[:+',;.]", 2);
if (split2.length == 0)
{
return null;
}
Material mat;
if (NUMPATTERN.matcher(split2[0]).matches())
{
final int typeId = Integer.parseInt(split2[0]);
mat = Material.getMaterial(typeId);
}
else
{
mat = Material.matchMaterial(split2[0]);
}
short data = 0;
if (split2.length == 2 && NUMPATTERN.matcher(split2[1]).matches())
{
data = Short.parseShort(split2[1]);
}
int size = mat.getMaxStackSize();
if (split1.length == 2 && NUMPATTERN.matcher(split1[1]).matches())
{
size = Integer.parseInt(split1[1]);
}
return new ItemStack(mat, size, data);
}
return super.construct(node);
}
}
private class ConstructBukkitMapping extends ConstructMapping
{
@Override
public Object construct(final Node node)
{
if (node.getType().equals(Location.class))
{
//TODO: NPE checks
final MappingNode mnode = (MappingNode)node;
String worldName = "";
double x = 0, y = 0, z = 0;
float yaw = 0, pitch = 0;
if (mnode.getValue().size() < 4)
{
return null;
}
for (NodeTuple nodeTuple : mnode.getValue())
{
final String key = (String)constructScalar((ScalarNode)nodeTuple.getKeyNode());
final ScalarNode snode = (ScalarNode)nodeTuple.getValueNode();
if (key.equalsIgnoreCase("world"))
{
worldName = (String)constructScalar(snode);
}
if (key.equalsIgnoreCase("x"))
{
x = Double.parseDouble((String)constructScalar(snode));
}
if (key.equalsIgnoreCase("y"))
{
y = Double.parseDouble((String)constructScalar(snode));
}
if (key.equalsIgnoreCase("z"))
{
z = Double.parseDouble((String)constructScalar(snode));
}
if (key.equalsIgnoreCase("yaw"))
{
yaw = Float.parseFloat((String)constructScalar(snode));
}
if (key.equalsIgnoreCase("pitch"))
{
pitch = Float.parseFloat((String)constructScalar(snode));
}
}
if (worldName == null || worldName.isEmpty())
{
return null;
}
final World world = Bukkit.getWorld(worldName);
if (world == null)
{
return null;
}
return new Location(world, x, y, z, yaw, pitch);
}
return super.construct(node);
}
}
}

View file

@ -0,0 +1,7 @@
package com.earth2me.essentials.storage;
public interface IStorageReader
{
<T extends StorageObject> T load(final Class<? extends T> clazz);
}

View file

@ -0,0 +1,7 @@
package com.earth2me.essentials.storage;
public interface IStorageWriter
{
void save(final StorageObject object);
}

View file

@ -8,7 +8,7 @@ import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MapType
public @interface MapKeyType
{
Class value() default String.class;
}

View file

@ -0,0 +1,14 @@
package com.earth2me.essentials.storage;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MapValueType
{
Class value() default String.class;
}

View file

@ -1,243 +1,6 @@
package com.earth2me.essentials.storage;
import java.io.PrintWriter;
import java.io.Reader;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Map.Entry;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.TypeDescription;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;
public class StorageObject
public interface StorageObject
{
protected Class<? extends StorageObject> clazz;
protected StorageObject()
{
}
private static Map<Class, Constructor> constructors = new HashMap<Class, Constructor>();
public static <T extends StorageObject> T load(Class<? extends T> clazz, Reader reader)
{
Constructor constructor;
if (constructors.containsKey(clazz))
{
constructor = constructors.get(clazz);
}
else
{
constructor = prepareConstructor(clazz);
constructors.put(clazz, constructor);
}
final Yaml yaml = new Yaml(constructor);
T ret = (T)yaml.load(reader);
if (ret == null)
{
try
{
ret = (T)clazz.newInstance();
}
catch (InstantiationException ex)
{
Logger.getLogger(StorageObject.class.getName()).log(Level.SEVERE, null, ex);
}
catch (IllegalAccessException ex)
{
Logger.getLogger(StorageObject.class.getName()).log(Level.SEVERE, null, ex);
}
}
ret.clazz = clazz;
return ret;
}
private static Constructor prepareConstructor(final Class<?> clazz)
{
final Constructor constructor = new Constructor(clazz);
final Set<Class> classes = new HashSet<Class>();
prepareConstructor(constructor, classes, clazz);
return constructor;
}
private static void prepareConstructor(final Constructor constructor, final Set<Class> classes, final Class clazz)
{
classes.add(clazz);
final TypeDescription description = new TypeDescription(clazz);
for (Field field : clazz.getDeclaredFields())
{
final ListType listType = field.getAnnotation(ListType.class);
if (listType != null)
{
description.putListPropertyType(field.getName(), listType.value());
if (StorageObject.class.isAssignableFrom(listType.value())
&& !classes.contains(listType.value()))
{
prepareConstructor(constructor, classes, listType.value());
}
}
final MapType mapType = field.getAnnotation(MapType.class);
if (mapType != null)
{
description.putMapPropertyType(field.getName(), String.class, mapType.value());
if (StorageObject.class.isAssignableFrom(mapType.value())
&& !classes.contains(mapType.value()))
{
prepareConstructor(constructor, classes, mapType.value());
}
}
if (StorageObject.class.isAssignableFrom(field.getType())
&& !classes.contains(field.getType()))
{
prepareConstructor(constructor, classes, field.getType());
}
}
constructor.addTypeDescription(description);
}
private transient Yaml yaml;
public void save(final PrintWriter writer)
{
final DumperOptions ops = new DumperOptions();
yaml = new Yaml(ops);
try
{
writeToFile(this, writer, 0, clazz);
}
catch (IllegalArgumentException ex)
{
Logger.getLogger(StorageObject.class.getName()).log(Level.SEVERE, null, ex);
}
catch (IllegalAccessException ex)
{
Logger.getLogger(StorageObject.class.getName()).log(Level.SEVERE, null, ex);
}
}
private void writeToFile(final Object object, final PrintWriter writer, final int depth, final Class clazz) throws IllegalArgumentException, IllegalAccessException
{
for (Field field : clazz.getDeclaredFields())
{
final int modifier = field.getModifiers();
if (Modifier.isPrivate(modifier) && !Modifier.isTransient(modifier) && !Modifier.isStatic(modifier))
{
field.setAccessible(true);
final boolean commentPresent = field.isAnnotationPresent(Comment.class);
final String name = field.getName();
if (commentPresent)
{
final Comment comments = field.getAnnotation(Comment.class);
for (String comment : comments.value())
{
final String trimmed = comment.trim();
if (trimmed.isEmpty())
{
continue;
}
writeIndention(writer, depth);
writer.print("# ");
writer.print(trimmed);
writer.println();
}
}
final Object data = field.get(object);
if (data == null && !commentPresent)
{
continue;
}
writeIndention(writer, depth);
if (data == null && commentPresent)
{
writer.print('#');
}
writer.print(name);
writer.print(": ");
if (data == null && commentPresent)
{
writer.println();
writer.println();
continue;
}
if (data instanceof StorageObject)
{
writer.println();
writeToFile(data, writer, depth + 1, data.getClass());
}
else if (data instanceof Map)
{
writer.println();
for (Entry<String, Object> entry : ((Map<String, Object>)data).entrySet())
{
final Object value = entry.getValue();
if (value != null)
{
writeIndention(writer, depth + 1);
writer.print(entry.getKey());
writer.print(": ");
if (value instanceof StorageObject)
{
writer.println();
writeToFile(value, writer, depth + 2, value.getClass());
}
else if (value instanceof String || value instanceof Boolean || value instanceof Number)
{
yaml.dumpAll(Collections.singletonList(value).iterator(), writer);
writer.println();
}
else
{
throw new UnsupportedOperationException();
}
}
}
}
else if (data instanceof Collection)
{
writer.println();
for (Object entry : (Collection<Object>)data)
{
if (entry != null)
{
writeIndention(writer, depth + 1);
writer.print("- ");
if (entry instanceof String || entry instanceof Boolean || entry instanceof Number)
{
yaml.dumpAll(Collections.singletonList(entry).iterator(), writer);
}
else
{
throw new UnsupportedOperationException();
}
}
}
writer.println();
}
else if (data instanceof String || data instanceof Boolean || data instanceof Number)
{
yaml.dumpAll(Collections.singletonList(data).iterator(), writer);
writer.println();
}
else
{
throw new UnsupportedOperationException();
}
}
}
}
private void writeIndention(final PrintWriter writer, final int depth)
{
for (int i = 0; i < depth; i++)
{
writer.print(" ");
}
}
}

View file

@ -0,0 +1,126 @@
package com.earth2me.essentials.storage;
import java.io.Reader;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.yaml.snakeyaml.TypeDescription;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;
public class YamlStorageReader implements IStorageReader
{
private transient static Map<Class, Yaml> preparedYamls = Collections.synchronizedMap(new HashMap<Class, Yaml>());
private transient static Map<Class, ReentrantLock> locks = new HashMap<Class, ReentrantLock>();
private transient final Reader reader;
public YamlStorageReader(final Reader reader)
{
this.reader = reader;
}
public <T extends StorageObject> T load(final Class<? extends T> clazz)
{
Yaml yaml = preparedYamls.get(clazz);
if (yaml == null)
{
yaml = new Yaml(prepareConstructor(clazz));
preparedYamls.put(clazz, yaml);
}
ReentrantLock lock;
synchronized (locks)
{
lock = locks.get(clazz);
if (lock == null)
{
lock = new ReentrantLock();
}
}
T ret;
lock.lock();
try
{
ret = (T)yaml.load(reader);
}
finally
{
lock.unlock();
}
if (ret == null)
{
try
{
ret = (T)clazz.newInstance();
}
catch (InstantiationException ex)
{
Logger.getLogger(StorageObject.class.getName()).log(Level.SEVERE, null, ex);
}
catch (IllegalAccessException ex)
{
Logger.getLogger(StorageObject.class.getName()).log(Level.SEVERE, null, ex);
}
}
return ret;
}
private static Constructor prepareConstructor(final Class<?> clazz)
{
final Constructor constructor = new BukkitConstructor(clazz);
final Set<Class> classes = new HashSet<Class>();
prepareConstructor(constructor, classes, clazz);
return constructor;
}
private static void prepareConstructor(final Constructor constructor, final Set<Class> classes, final Class clazz)
{
classes.add(clazz);
final TypeDescription description = new TypeDescription(clazz);
for (Field field : clazz.getDeclaredFields())
{
prepareList(field, description, classes, constructor);
prepareMap(field, description, classes, constructor);
if (StorageObject.class.isAssignableFrom(field.getType())
&& !classes.contains(field.getType()))
{
prepareConstructor(constructor, classes, field.getType());
}
}
constructor.addTypeDescription(description);
}
private static void prepareList(final Field field, final TypeDescription description, final Set<Class> classes, final Constructor constructor)
{
final ListType listType = field.getAnnotation(ListType.class);
if (listType != null)
{
description.putListPropertyType(field.getName(), listType.value());
if (StorageObject.class.isAssignableFrom(listType.value())
&& !classes.contains(listType.value()))
{
prepareConstructor(constructor, classes, listType.value());
}
}
}
private static void prepareMap(final Field field, final TypeDescription description, final Set<Class> classes, final Constructor constructor)
{
final MapValueType mapType = field.getAnnotation(MapValueType.class);
if (mapType != null)
{
final MapKeyType mapKeyType = field.getAnnotation(MapKeyType.class);
description.putMapPropertyType(field.getName(),
mapKeyType == null ? String.class : mapKeyType.value(),
mapType.value());
if (StorageObject.class.isAssignableFrom(mapType.value())
&& !classes.contains(mapType.value()))
{
prepareConstructor(constructor, classes, mapType.value());
}
}
}
}

View file

@ -0,0 +1,295 @@
package com.earth2me.essentials.storage;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.bukkit.material.MaterialData;
import org.yaml.snakeyaml.Yaml;
public class YamlStorageWriter implements IStorageWriter
{
private transient static Pattern pattern = Pattern.compile("\\w");
private transient final PrintWriter writer;
private transient static final Yaml YAML = new Yaml();
public YamlStorageWriter(final PrintWriter writer)
{
this.writer = writer;
}
public void save(final StorageObject object)
{
try
{
writeToFile(object, 0, object.getClass());
}
catch (IllegalArgumentException ex)
{
Logger.getLogger(YamlStorageWriter.class.getName()).log(Level.SEVERE, null, ex);
}
catch (IllegalAccessException ex)
{
Logger.getLogger(YamlStorageWriter.class.getName()).log(Level.SEVERE, null, ex);
}
}
private void writeToFile(final Object object, final int depth, final Class clazz) throws IllegalAccessException
{
for (Field field : clazz.getDeclaredFields())
{
final int modifier = field.getModifiers();
if (Modifier.isPrivate(modifier) && !Modifier.isTransient(modifier) && !Modifier.isStatic(modifier))
{
field.setAccessible(true);
final Object data = field.get(object);
if (writeKey(field, depth, data))
{
continue;
}
if (data instanceof StorageObject)
{
writer.println();
writeToFile(data, depth + 1, data.getClass());
}
else if (data instanceof Map)
{
writeMap((Map<Object, Object>)data, depth + 1);
}
else if (data instanceof Collection)
{
writeCollection((Collection<Object>)data, depth + 1);
}
else if (data instanceof Location)
{
writeLocation((Location)data, depth + 1);
}
else
{
writeScalar(data);
writer.println();
}
}
}
}
private boolean writeKey(final Field field, final int depth, final Object data)
{
final boolean commentPresent = writeComment(field, depth);
if (data == null && !commentPresent)
{
return true;
}
writeIndention(depth);
if (data == null && commentPresent)
{
writer.print('#');
}
final String name = field.getName();
writer.print(name);
writer.print(": ");
if (data == null && commentPresent)
{
writer.println();
writer.println();
return true;
}
return false;
}
private boolean writeComment(final Field field, final int depth)
{
final boolean commentPresent = field.isAnnotationPresent(Comment.class);
if (commentPresent)
{
final Comment comments = field.getAnnotation(Comment.class);
for (String comment : comments.value())
{
final String trimmed = comment.trim();
if (trimmed.isEmpty())
{
continue;
}
writeIndention(depth);
writer.print("# ");
writer.print(trimmed);
writer.println();
}
}
return commentPresent;
}
private void writeCollection(final Collection<Object> data, final int depth) throws IllegalAccessException
{
writer.println();
if (data.isEmpty())
{
writer.println();
}
for (Object entry : data)
{
if (entry != null)
{
writeIndention(depth);
writer.print("- ");
if (entry instanceof StorageObject)
{
writer.println();
writeToFile(entry, depth + 1, entry.getClass());
}
else if (entry instanceof Location)
{
writeLocation((Location)entry, depth + 1);
}
else
{
writeScalar(entry);
}
}
}
writer.println();
}
private void writeMap(final Map<Object, Object> data, final int depth) throws IllegalArgumentException, IllegalAccessException
{
writer.println();
if (data.isEmpty())
{
writer.println();
}
for (Entry<Object, Object> entry : data.entrySet())
{
final Object value = entry.getValue();
if (value != null)
{
writeIndention(depth);
writeKey(entry.getKey());
writer.print(": ");
if (value instanceof StorageObject)
{
writer.println();
writeToFile(value, depth + 1, value.getClass());
}
else if (value instanceof Collection)
{
writeCollection((Collection<Object>)value, depth + 1);
}
else if (value instanceof Location)
{
writeLocation((Location)value, depth + 1);
}
else
{
writeScalar(value);
writer.println();
}
}
}
}
private void writeIndention(final int depth)
{
for (int i = 0; i < depth; i++)
{
writer.print(" ");
}
}
private void writeScalar(final Object data)
{
if (data instanceof String || data instanceof Boolean || data instanceof Number)
{
synchronized (YAML)
{
YAML.dumpAll(Collections.singletonList(data).iterator(), writer);
}
}
else if (data instanceof Material)
{
writer.println(data.toString().toLowerCase());
}
else if (data instanceof MaterialData)
{
final MaterialData matData = (MaterialData)data;
writer.println(matData.getItemType().toString().toLowerCase()
+ (matData.getData() > 0 ? ":" + matData.getData() : ""));
}
else if (data instanceof ItemStack)
{
final ItemStack itemStack = (ItemStack)data;
writer.println(itemStack.getType().toString().toLowerCase()
+ (itemStack.getDurability() > 0 ? ":" + itemStack.getDurability() : "")
+ " " + itemStack.getAmount());
}
else
{
throw new UnsupportedOperationException();
}
}
private void writeKey(final Object data)
{
if (data instanceof String || data instanceof Boolean || data instanceof Number)
{
String output = data.toString();
if (pattern.matcher(output).find())
{
writer.print('"');
writer.print(output.replace("\"", "\\\""));
writer.print('"');
}
else
{
writer.print(output);
}
}
else if (data instanceof Material)
{
writer.print(data.toString().toLowerCase());
}
else if (data instanceof MaterialData)
{
final MaterialData matData = (MaterialData)data;
writer.print(matData.getItemType().toString().toLowerCase()
+ (matData.getData() > 0 ? ":" + matData.getData() : ""));
}
else
{
throw new UnsupportedOperationException();
}
}
private void writeLocation(final Location entry, final int depth)
{
writer.println();
writeIndention(depth);
writer.print("world: ");
writeScalar(entry.getWorld().getName());
writeIndention(depth);
writer.print("x: ");
writeScalar(entry.getX());
writeIndention(depth);
writer.print("y: ");
writeScalar(entry.getY());
writeIndention(depth);
writer.print("z: ");
writeScalar(entry.getZ());
writeIndention(depth);
writer.print("yaw: ");
writeScalar(entry.getYaw());
writeIndention(depth);
writer.print("pitch: ");
writeScalar(entry.getPitch());
}
}

View file

@ -0,0 +1,14 @@
package com.earth2me.essentials.userdata;
import com.earth2me.essentials.storage.StorageObject;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = false)
public class Ban implements StorageObject
{
private String reason;
private long timeout;
}

View file

@ -0,0 +1,27 @@
package com.earth2me.essentials.userdata;
import com.earth2me.essentials.storage.MapKeyType;
import com.earth2me.essentials.storage.MapValueType;
import com.earth2me.essentials.storage.StorageObject;
import java.util.HashMap;
import java.util.Map;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
@Data
@EqualsAndHashCode(callSuper = false)
public class Inventory implements StorageObject
{
private int size;
@MapKeyType(Integer.class)
@MapValueType(ItemStack.class)
private Map<Integer, ItemStack> items = new HashMap<Integer, ItemStack>();
public Inventory()
{
items.put(1, new ItemStack(Material.APPLE, 64));
}
}

View file

@ -0,0 +1,65 @@
package com.earth2me.essentials.userdata;
import com.earth2me.essentials.storage.YamlStorageReader;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import lombok.Cleanup;
// this is a prototype for locking userdata
public class User
{
UserData data = new UserData();
ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
public void loadUserData()
{
data = new YamlStorageReader(null).load(UserData.class);
}
public void aquireReadLock()
{
rwl.readLock().lock();
}
public void aquireWriteLock()
{
while (rwl.getReadHoldCount() > 0)
{
rwl.readLock().unlock();
}
rwl.writeLock().lock();
rwl.readLock().lock();
}
public void close()
{
if (rwl.isWriteLockedByCurrentThread())
{
scheduleSaving();
rwl.writeLock().unlock();
}
while (rwl.getReadHoldCount() > 0)
{
rwl.readLock().unlock();
}
}
public void example()
{
// Cleanup will call close at the end of the function
@Cleanup
final User user = this;
// read lock allows to read data from the user
user.aquireReadLock();
double i = user.data.getMoney();
// write lock allows only one thread to modify the data
user.aquireWriteLock();
user.data.setMoney(10 + user.data.getMoney());
}
private void scheduleSaving()
{
System.out.println("Schedule saving...");
}
}

View file

@ -0,0 +1,61 @@
package com.earth2me.essentials.userdata;
import com.earth2me.essentials.storage.ListType;
import com.earth2me.essentials.storage.MapKeyType;
import com.earth2me.essentials.storage.MapValueType;
import com.earth2me.essentials.storage.StorageObject;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.bukkit.Location;
import org.bukkit.Material;
@Data
@EqualsAndHashCode(callSuper = false)
public class UserData implements StorageObject
{
private String nickname;
private double money;
@MapValueType(Location.class)
private Map<String, Location> homes = new HashMap<String, Location>();
@ListType(Material.class)
private Set<Material> unlimited = new HashSet<Material>();
@MapValueType(List.class)
@MapKeyType(Material.class)
private Map<Material, List<String>> powerTools = new HashMap<Material, List<String>>();
private Location lastLocation;
@MapValueType(Long.class)
private Map<String, Long> timestamps;
private String jail;
@ListType
private List<String> mails;
private Inventory inventory;
private boolean teleportEnabled;
@ListType
private Set<String> ignore;
private boolean godmode;
private boolean muted;
private boolean jailed;
private Ban ban;
private String ipAddress;
private boolean afk;
private boolean newplayer = true;
private String geolocation;
private boolean socialspy;
private boolean npc;
private boolean powertoolsenabled;
public UserData()
{
unlimited.add(Material.AIR);
unlimited.add(Material.ARROW);
unlimited.add(Material.APPLE);
powerTools.put(Material.DEAD_BUSH, Collections.singletonList("test"));
}
}

View file

@ -28,9 +28,17 @@ public class FakeServer implements Server
private List<Player> players = new ArrayList<Player>();
private final List<World> worlds = new ArrayList<World>();
public FakeServer()
{
if (Bukkit.getServer() == null)
{
Bukkit.setServer(this);
}
}
public String getName()
{
return "Test Server";
return "Essentials Fake Server";
}
public String getVersion()
@ -576,6 +584,6 @@ public class FakeServer implements Server
@Override
public String getBukkitVersion()
{
throw new UnsupportedOperationException("Not supported yet.");
return "Essentials Fake-Server";
}
}

View file

@ -2,32 +2,148 @@ package com.earth2me.essentials;
import com.earth2me.essentials.settings.Settings;
import com.earth2me.essentials.storage.StorageObject;
import com.earth2me.essentials.storage.YamlStorageReader;
import com.earth2me.essentials.storage.YamlStorageWriter;
import java.io.*;
import junit.framework.TestCase;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.World.Environment;
import org.bukkit.plugin.InvalidDescriptionException;
import org.junit.Test;
import org.yaml.snakeyaml.Yaml;
public class StorageTest extends TestCase
{
Essentials ess;
FakeServer server;
World world;
public StorageTest()
{
ess = new Essentials();
server = new FakeServer();
world = server.createWorld("testWorld", Environment.NORMAL);
try
{
ess.setupForTesting(server);
}
catch (InvalidDescriptionException ex)
{
fail("InvalidDescriptionException");
}
catch (IOException ex)
{
fail("IOException");
}
}
@Test
public void testSettings()
{
assertTrue(StorageObject.class.isAssignableFrom(Settings.class));
ExecuteTimer ext = new ExecuteTimer();
ext.start();
final ByteArrayInputStream bais = new ByteArrayInputStream(new byte[0]);
final Reader reader = new InputStreamReader(bais);
final Settings settings = StorageObject.load(Settings.class, reader);
final Settings settings = new YamlStorageReader(reader).load(Settings.class);
ext.mark("load empty settings");
final ByteArrayInputStream bais3 = new ByteArrayInputStream(new byte[0]);
final Reader reader3 = new InputStreamReader(bais3);
final Settings settings3 = new YamlStorageReader(reader3).load(Settings.class);
ext.mark("load empty settings (class cached)");
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
final PrintWriter writer = new PrintWriter(baos);
settings.save(writer);
new YamlStorageWriter(writer).save(settings);
writer.close();
ext.mark("write settings");
byte[] written = baos.toByteArray();
System.out.println(new String(written));
final ByteArrayInputStream bais2 = new ByteArrayInputStream(written);
final Reader reader2 = new InputStreamReader(bais2);
final Settings settings2 = StorageObject.load(Settings.class, reader2);
final Settings settings2 = new YamlStorageReader(reader2).load(Settings.class);
System.out.println(settings.toString());
System.out.println(settings2.toString());
ext.mark("reload settings");
System.out.println(ext.end());
//assertEquals("Default and rewritten config should be equal", settings, settings2);
//that assertion fails, because empty list and maps return as null
}
@Test
public void testUserdata()
{
FakeServer server = new FakeServer();
World world = server.createWorld("testWorld", Environment.NORMAL);
ExecuteTimer ext = new ExecuteTimer();
ext.start();
final ByteArrayInputStream bais = new ByteArrayInputStream(new byte[0]);
final Reader reader = new InputStreamReader(bais);
final com.earth2me.essentials.userdata.UserData userdata = new YamlStorageReader(reader).load(com.earth2me.essentials.userdata.UserData.class);
ext.mark("load empty user");
final ByteArrayInputStream bais3 = new ByteArrayInputStream(new byte[0]);
final Reader reader3 = new InputStreamReader(bais3);
final com.earth2me.essentials.userdata.UserData userdata3 = new YamlStorageReader(reader3).load(com.earth2me.essentials.userdata.UserData.class);
ext.mark("load empty user (class cached)");
for (int j = 0; j < 10000; j++)
{
userdata.getHomes().put("home", new Location(world, j, j, j));
}
ext.mark("change home 10000 times");
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
final PrintWriter writer = new PrintWriter(baos);
new YamlStorageWriter(writer).save(userdata);
writer.close();
ext.mark("write user");
final ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
final PrintWriter writer2 = new PrintWriter(baos2);
new YamlStorageWriter(writer2).save(userdata);
writer2.close();
ext.mark("write user (cached)");
byte[] written = baos.toByteArray();
System.out.println(new String(written));
ext.mark("debug output");
final ByteArrayInputStream bais2 = new ByteArrayInputStream(written);
final Reader reader2 = new InputStreamReader(bais2);
final com.earth2me.essentials.userdata.UserData userdata2 = new YamlStorageReader(reader2).load(com.earth2me.essentials.userdata.UserData.class);
ext.mark("reload file");
final ByteArrayInputStream bais4 = new ByteArrayInputStream(written);
final Reader reader4 = new InputStreamReader(bais4);
final com.earth2me.essentials.userdata.UserData userdata4 = new YamlStorageReader(reader4).load(com.earth2me.essentials.userdata.UserData.class);
ext.mark("reload file (cached)");
System.out.println(userdata.toString());
System.out.println(userdata2.toString());
System.out.println(ext.end());
com.earth2me.essentials.userdata.User test = new com.earth2me.essentials.userdata.User();
test.example();
}
@Test
public void testOldUserdata()
{
ExecuteTimer ext = new ExecuteTimer();
ext.start();
OfflinePlayer base1 = server.createPlayer("testPlayer1", ess);
server.addPlayer(base1);
ext.mark("fake user created");
UserData user = (UserData)ess.getUser(base1);
ext.mark("load empty user");
for (int j = 0; j < 1; j++)
{
user.setHome("home", new Location(world, j, j, j));
}
ext.mark("change home 1 times");
user.save();
ext.mark("write user");
user.save();
ext.mark("write user (cached)");
user.reloadConfig();
ext.mark("reloaded file");
user.reloadConfig();
ext.mark("reloaded file (cached)");
System.out.println(ext.end());
}
}