package me.sashie.skriptyaml.utils.yaml; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Calendar; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.TimeZone; import org.bukkit.Location; import org.bukkit.configuration.serialization.ConfigurationSerializable; import org.bukkit.configuration.serialization.ConfigurationSerialization; import org.bukkit.inventory.ItemStack; import org.bukkit.util.Vector; import org.yaml.snakeyaml.DumperOptions.FlowStyle; import org.yaml.snakeyaml.nodes.Node; import org.yaml.snakeyaml.nodes.Tag; import org.yaml.snakeyaml.representer.Represent; import org.yaml.snakeyaml.representer.Representer; import ch.njol.skript.aliases.ItemType; import ch.njol.skript.util.Color; import ch.njol.skript.util.Date; import ch.njol.skript.util.Slot; import ch.njol.skript.util.Time; import ch.njol.skript.util.Timespan; import ch.njol.skript.util.WeatherType; import me.sashie.skriptyaml.SkriptYaml; import me.sashie.skriptyaml.api.RepresentedClass; public class SkriptYamlRepresenter extends Representer { private static List<String> representedClasses = new ArrayList<>(); public SkriptYamlRepresenter() { this.nullRepresenter = new Represent() { @Override public Node representData(Object o) { return representScalar(Tag.NULL, ""); } }; this.representers.put(SkriptClass.class, new RepresentSkriptClass()); this.representers.put(ItemType.class, new RepresentSkriptItemType()); this.representers.put(Slot.class, new RepresentSkriptSlot()); this.representers.put(Date.class, new RepresentSkriptDate()); this.representers.put(Time.class, new RepresentSkriptTime()); this.representers.put(Timespan.class, new RepresentSkriptTimespan()); this.representers.put(Color.class, new RepresentSkriptColor()); this.representers.put(WeatherType.class, new RepresentSkriptWeather()); this.representers.put(Vector.class, new RepresentVector()); this.representers.put(Location.class, new RepresentLocation()); this.multiRepresenters.put(ConfigurationSerializable.class, new RepresentConfigurationSerializable()); for (Class<?> c : representers.keySet()) { if (c != null) { String name = c.getSimpleName(); if (!representedClasses.contains(name)) representedClasses.add(name); } } } public void register(String tag, Class<?> c, RepresentedClass<?> rc) { rc.tag = tag; this.representers.put(c, rc); String name = c.getSimpleName(); if (!representedClasses.contains(name)) representedClasses.add(name); } public static boolean contains(Object object) { if (object == null) return false; return representedClasses.contains(object.getClass().getSimpleName()); } public boolean contains(Class<?> c) { return representedClasses.contains(c.getSimpleName()); } private class RepresentConfigurationSerializable extends RepresentMap { @Override public Node representData(Object data) { return representConfigurationSerializable(data); } } private Node representConfigurationSerializable(Object data) { ConfigurationSerializable serializable = (ConfigurationSerializable) data; Map<String, Object> values = new LinkedHashMap<String, Object>(); values.put(ConfigurationSerialization.SERIALIZED_TYPE_KEY, ConfigurationSerialization.getAlias(serializable.getClass())); values.putAll(serializable.serialize()); return super.representData(values); } private class RepresentVector implements Represent { @Override public Node representData(Object data) { Map<String, Double> out = new LinkedHashMap<String, Double>(); Vector vec = (Vector) data; out.put("x", vec.getX()); out.put("y", vec.getY()); out.put("z", vec.getZ()); return representMapping(new Tag("!vector"), out); } } private class RepresentLocation implements Represent { @Override public Node representData(Object data) { Map<String, Object> out = new LinkedHashMap<String, Object>(); Location loc = (Location) data; out.put("world", loc.getWorld().getName()); out.put("x", loc.getX()); out.put("y", loc.getY()); out.put("z", loc.getZ()); out.put("yaw", (double) loc.getYaw()); out.put("pitch", (double) loc.getPitch()); return representMapping(new Tag("!location"), out); } } private class RepresentSkriptClass extends RepresentMap { @Override public Node representData(Object data) { Map<String, Object> out = new LinkedHashMap<String, Object>(); SkriptClass skriptClass = (SkriptClass) data; out.put("type", skriptClass.getType()); out.put("data", skriptClass.getData()); return representMapping(new Tag("!skriptclass"), out); } } private static Method representMappingMethod; static { if (SkriptYaml.getInstance().getServerVersion() <= 12) { try { Class<?> baseRepresenterClass = Class.forName("org.yaml.snakeyaml.representer.BaseRepresenter"); //representMapping(Tag tag, Map<?, ?> mapping, Boolean flowStyle) representMappingMethod = baseRepresenterClass.getDeclaredMethod("representMapping", Tag.class, Map.class, Boolean.class); representMappingMethod.setAccessible(true); } catch (SecurityException | ClassNotFoundException | NoSuchMethodException e) { e.printStackTrace(); } } } /* * To make things backwards compatible and prevent NoSuchMethod exceptions. * (spigot updated snakeyaml in 1.13.2) */ @SuppressWarnings({ "unchecked" }) public <T> T representMapping(Tag tag, Map<?, ?> mapping) { if (SkriptYaml.getInstance().getServerVersion() >= 13) { return (T) representMapping(tag, mapping, FlowStyle.BLOCK); } else { T node = null; try { node = (T) representMappingMethod.invoke(this, tag, mapping, null); } catch (SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { e.printStackTrace(); } return (T) node; } } private class RepresentSkriptItemType extends RepresentMap { @Override public Node representData(Object data) { ItemStack item = null; return representConfigurationSerializable(((ItemType) data).addTo(item)); } } private class RepresentSkriptSlot extends RepresentMap { @Override public Node representData(Object data) { return representConfigurationSerializable(((Slot) data).getItem()); } } /* TODO eventually add support for different slot types private class RepresentInventorySlot extends RepresentMap { @Override public Node representData(Object data) { Map<String, Object> out = new LinkedHashMap<String, Object>(); InventorySlot slot = (InventorySlot) data; out.put("index", slot.getIndex()); out.put("item", slot.getItem()); return representMapping(new Tag("!skriptclass"), out, null); } } */ private class RepresentSkriptDate implements Represent { @Override public Node representData(Object data) { Calendar calendar = Calendar.getInstance(getTimeZone() == null ? TimeZone.getTimeZone("UTC") : timeZone); calendar.setTime(new java.util.Date(((Date) data).getTimestamp())); int years = calendar.get(Calendar.YEAR); int months = calendar.get(Calendar.MONTH) + 1; // 0..12 int days = calendar.get(Calendar.DAY_OF_MONTH); // 1..31 int hour24 = calendar.get(Calendar.HOUR_OF_DAY); // 0..24 int minutes = calendar.get(Calendar.MINUTE); // 0..59 int seconds = calendar.get(Calendar.SECOND); // 0..59 int millis = calendar.get(Calendar.MILLISECOND); StringBuilder buffer = new StringBuilder(String.valueOf(years)); while (buffer.length() < 4) { // ancient years buffer.insert(0, "0"); } buffer.append("-"); if (months < 10) { buffer.append("0"); } buffer.append(String.valueOf(months)); buffer.append("-"); if (days < 10) { buffer.append("0"); } buffer.append(String.valueOf(days)); buffer.append("T"); if (hour24 < 10) { buffer.append("0"); } buffer.append(String.valueOf(hour24)); buffer.append(":"); if (minutes < 10) { buffer.append("0"); } buffer.append(String.valueOf(minutes)); buffer.append(":"); if (seconds < 10) { buffer.append("0"); } buffer.append(String.valueOf(seconds)); if (millis > 0) { if (millis < 10) { buffer.append(".00"); } else if (millis < 100) { buffer.append(".0"); } else { buffer.append("."); } buffer.append(String.valueOf(millis)); } // Get the offset from GMT taking DST into account int gmtOffset = calendar.getTimeZone().getOffset(calendar.get(Calendar.ERA), calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH), calendar.get(Calendar.DAY_OF_WEEK), calendar.get(Calendar.MILLISECOND)); if (gmtOffset == 0) { buffer.append('Z'); } else { if (gmtOffset < 0) { buffer.append('-'); gmtOffset *= -1; } else { buffer.append('+'); } int minutesOffset = gmtOffset / (60 * 1000); int hoursOffset = minutesOffset / 60; int partOfHour = minutesOffset % 60; if (hoursOffset < 10) { buffer.append('0'); } buffer.append(hoursOffset); buffer.append(':'); if (partOfHour < 10) { buffer.append('0'); } buffer.append(partOfHour); } return representScalar(new Tag("!skriptdate"), buffer.toString()); } } private class RepresentSkriptTime implements Represent { @Override public Node representData(Object data) { return representScalar(new Tag("!skripttime"), ((Time) data).toString()); } } private class RepresentSkriptTimespan implements Represent { @Override public Node representData(Object data) { return representScalar(new Tag("!skripttimespan"), ((Timespan) data).toString()); } } private class RepresentSkriptColor implements Represent { @Override public Node representData(Object data) { return representScalar(new Tag("!skriptcolor"), ((Color) data).toString()); } } private class RepresentSkriptWeather implements Represent { @Override public Node representData(Object data) { return representScalar(new Tag("!skriptweather"), ((WeatherType) data).toString().toLowerCase()); } } }