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.setProperty("timestamps.kits", kitTimestamps);
config.save(); config.save();
} }
public void save()
{
config.save();
}
} }

View file

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

View file

@ -8,7 +8,7 @@ import lombok.EqualsAndHashCode;
@Data @Data
@EqualsAndHashCode(callSuper = false) @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.") @Comment("The character(s) to prefix all nicknames, so that you know they are not true usernames.")
private String nicknamePrefix = "~"; private String nicknamePrefix = "~";

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -8,7 +8,7 @@ import lombok.EqualsAndHashCode;
@Data @Data
@EqualsAndHashCode(callSuper = false) @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?") @Comment("When players die, should they respawn at their homes, instead of the spawnpoint?")
private boolean respawnAtHome = false; private boolean respawnAtHome = false;

View file

@ -1,25 +1,28 @@
package com.earth2me.essentials.settings.commands; 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 com.earth2me.essentials.storage.StorageObject;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
@Data @Data
@EqualsAndHashCode(callSuper = false) @EqualsAndHashCode(callSuper = false)
public class Kit extends StorageObject public class Kit implements StorageObject
{ {
public Kit() public Kit()
{ {
final KitObject kit = new KitObject(); final KitObject kit = new KitObject();
kit.setDelay(10.0); 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); kits.put("tools", kit);
} }
@MapType(KitObject.class) @MapValueType(KitObject.class)
private Map<String, KitObject> kits = new HashMap<String, KitObject>(); private Map<String, KitObject> kits = new HashMap<String, KitObject>();
} }

View file

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

View file

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

View file

@ -8,7 +8,7 @@ import lombok.EqualsAndHashCode;
@Data @Data
@EqualsAndHashCode(callSuper = false) @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.") @Comment("The maximum amount of monsters, a player can spawn with a call of /spawnmob.")
private int limit = 10; 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) @Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface MapType public @interface MapKeyType
{ {
Class value() default String.class; 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; 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 interface StorageObject
public class 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 List<Player> players = new ArrayList<Player>();
private final List<World> worlds = new ArrayList<World>(); private final List<World> worlds = new ArrayList<World>();
public FakeServer()
{
if (Bukkit.getServer() == null)
{
Bukkit.setServer(this);
}
}
public String getName() public String getName()
{ {
return "Test Server"; return "Essentials Fake Server";
} }
public String getVersion() public String getVersion()
@ -576,6 +584,6 @@ public class FakeServer implements Server
@Override @Override
public String getBukkitVersion() 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.settings.Settings;
import com.earth2me.essentials.storage.StorageObject; import com.earth2me.essentials.storage.StorageObject;
import com.earth2me.essentials.storage.YamlStorageReader;
import com.earth2me.essentials.storage.YamlStorageWriter;
import java.io.*; import java.io.*;
import junit.framework.TestCase; 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.junit.Test;
import org.yaml.snakeyaml.Yaml;
public class StorageTest extends TestCase 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 @Test
public void testSettings() public void testSettings()
{ {
assertTrue(StorageObject.class.isAssignableFrom(Settings.class)); assertTrue(StorageObject.class.isAssignableFrom(Settings.class));
ExecuteTimer ext = new ExecuteTimer();
ext.start();
final ByteArrayInputStream bais = new ByteArrayInputStream(new byte[0]); final ByteArrayInputStream bais = new ByteArrayInputStream(new byte[0]);
final Reader reader = new InputStreamReader(bais); 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 ByteArrayOutputStream baos = new ByteArrayOutputStream();
final PrintWriter writer = new PrintWriter(baos); final PrintWriter writer = new PrintWriter(baos);
settings.save(writer); new YamlStorageWriter(writer).save(settings);
writer.close(); writer.close();
ext.mark("write settings");
byte[] written = baos.toByteArray(); byte[] written = baos.toByteArray();
System.out.println(new String(written)); System.out.println(new String(written));
final ByteArrayInputStream bais2 = new ByteArrayInputStream(written); final ByteArrayInputStream bais2 = new ByteArrayInputStream(written);
final Reader reader2 = new InputStreamReader(bais2); 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(settings.toString());
System.out.println(settings2.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); //assertEquals("Default and rewritten config should be equal", settings, settings2);
//that assertion fails, because empty list and maps return as null //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());
}
} }