package me.sashie.skriptyaml.skript; import java.lang.reflect.Field; import java.util.Iterator; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.annotation.Nullable; import org.bukkit.event.Event; import ch.njol.skript.ScriptLoader; import ch.njol.skript.Skript; import ch.njol.skript.classes.Converter; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.Loop; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.util.ConvertedExpression; import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.skript.log.SkriptLogger; import ch.njol.skript.registrations.Classes; import ch.njol.skript.registrations.Converters; import ch.njol.skript.util.Utils; import ch.njol.util.Kleenean; import ch.njol.util.coll.iterator.ArrayIterator; import me.sashie.skriptyaml.SkriptYaml; import me.sashie.skriptyaml.skript.ExprYaml.YamlState; import me.sashie.skriptyaml.utils.StringUtil; @Name("Yaml Loop") @Description("The currently looped value of a yaml expression.") @Examples({"", "loop yaml node keys \"node\" from \"config\":", " message yaml value loop-node from loop-id", "loop yaml node list \"node\" from \"config\":", " message yaml value loop-node from loop-id"}) @Since("1.3") public class ExprLoopYaml extends SimpleExpression<Object> { static { Skript.registerExpression(ExprLoopYaml.class, Object.class, ExpressionType.SIMPLE, "[the] loop-(1¦id|2¦val|3¦list|4¦node|5¦key|6¦subnodekey[s]|7¦iteration)"); } public static enum LoopState { ID, VALUE, LIST, NODE, NODE_KEY, SUB_NODE_KEYS, INDEX } private String name; private Loop loop; YamlState yamlState; LoopState loopState; boolean isYamlLoop = false; @Override public boolean init(final Expression<?>[] vars, final int matchedPattern, final Kleenean isDelayed, final ParseResult parser) { name = parser.expr; String s = name.split("-")[1]; int i = -1; final Matcher m = Pattern.compile("^(.+)-(\\d+)$").matcher(s); if (m.matches()) { s = "" + m.group(1); i = Utils.parseInt("" + m.group(2)); } int j = 1; Loop loop = null; for (final Loop l : ScriptLoader.currentLoops) { if (l.getLoopedExpression() instanceof ExprYaml) { if (j < i) { j++; continue; } if (loop != null) { //Skript.error("There are multiple loops that match loop-" + s + ". Use loop-" + s + "-1/2/3/etc. to specify which loop's value you want.", ErrorQuality.SEMANTIC_ERROR); return false; } loop = l; if (j == i) break; } } if (loop == null) { //Skript.error("There's no loop that matches 'loop-" + s + "'", ErrorQuality.SEMANTIC_ERROR); return false; } if (loop.getLoopedExpression() instanceof ExprYaml) { yamlState = ((ExprYaml<?>) loop.getLoopedExpression()).getState(); if (!yamlState.equals(YamlState.VALUE)) { if (parser.mark == 7) { loopState = LoopState.INDEX; } else if (parser.mark == 1) { loopState = LoopState.ID; } else if (parser.mark == 2) { loopState = LoopState.VALUE; } else if (parser.mark == 3) { loopState = LoopState.LIST; } else if (parser.mark == 4) { if (yamlState.equals(YamlState.LIST)) return loopStateListError(s); loopState = LoopState.NODE; } else if (parser.mark == 5) { if (yamlState.equals(YamlState.LIST)) return loopStateListError(s); loopState = LoopState.NODE_KEY; } else if (parser.mark == 6) { if (yamlState.equals(YamlState.LIST)) return loopStateListError(s); loopState = LoopState.SUB_NODE_KEYS; } } isYamlLoop = true; } else { SkriptYaml.error("A 'loop-" + s + "' can only be used in a yaml expression loop ie. 'loop yaml node keys \"node\" from \"config\"'" + getNodeMsg()); return false; } this.loop = loop; return true; } private boolean loopStateListError(String s) { //Skript.error("There's no 'loop-" + s + "' in a yaml list", ErrorQuality.SEMANTIC_ERROR); SkriptYaml.error("There's no 'loop-" + s + "' in a yaml list " + getNodeMsg()); return false; } private String getNodeMsg() { ch.njol.skript.config.Node n = SkriptLogger.getNode(); if (n == null) { return ""; } return "(" + n.getConfig().getFileName() + ", line " + n.getLine() + ": " + n.save().trim() + "')"; } @Override public boolean isSingle() { return false; } @SuppressWarnings("unchecked") @Override @Nullable protected <R> ConvertedExpression<Object, ? extends R> getConvertedExpr(final Class<R>... to) { if (isYamlLoop && loopState != LoopState.INDEX) { return new ConvertedExpression<>(this, (Class<R>) Utils.getSuperType(to), new Converter<Object, R>() { @Override @Nullable public R convert(final Object o) { return Converters.convert(o, to); } }); } else { return super.getConvertedExpr(to); } } @Override public Class<? extends Object> getReturnType() { if (loopState == LoopState.INDEX) return Number.class; else if (loopState == LoopState.ID || loopState == LoopState.NODE_KEY || loopState == LoopState.NODE) return String.class; return ((ExprYaml<?>) loop.getLoopedExpression()).getReturnType(yamlState); } @Override @Nullable protected Object[] get(final Event e) { if (isYamlLoop) { final Object current = loop.getCurrent(e); ExprYaml<?> yamlExpr = ((ExprYaml<?>) loop.getLoopedExpression()); if (current == null) return null; switch (loopState) { case INDEX: return new Number[] {getIndex()}; case ID: return new String[] {yamlExpr.getId(e)}; case VALUE: if (yamlState.equals(YamlState.LIST)) return new Object[] {current}; String n = getCurrentNode(current, yamlExpr.getNode(e)); if (n == null) return null; return yamlExpr.get(e, n, YamlState.VALUE); case LIST: String n2 = getCurrentNode(current, yamlExpr.getNode(e)); if (n2 == null) return null; return yamlExpr.get(e, n2, YamlState.LIST); case NODE: if (yamlState.equals(YamlState.NODE_KEYS)) return new String[] {StringUtil.addLastNodeSeperator(yamlExpr.getNode(e)) + current}; else if (yamlState.equals(YamlState.NODES)) return new String[] {current.toString()}; case NODE_KEY: if (yamlState.equals(YamlState.NODE_KEYS)) return new String[] {current.toString()}; else if (yamlState.equals(YamlState.NODES)) return new String[] {StringUtil.stripBeforeLastNode(current.toString())}; case SUB_NODE_KEYS: String n3 = getCurrentNode(current, yamlExpr.getNode(e)); if (n3 == null) return null; Object[] objects = yamlExpr.get(e, n3); if (objects == null) return null; return objects; default: break; } } return null; } private String getCurrentNode(Object current, String node) { String key = null; if (yamlState.equals(YamlState.NODE_KEYS)) key = StringUtil.addLastNodeSeperator(node) + current; else if (yamlState.equals(YamlState.NODES)) key = current.toString(); return key; } @SuppressWarnings("unchecked") public Number getIndex() { try { Field currentIterField = loop.getClass().getDeclaredField("currentIter"); currentIterField.setAccessible(true); Field indexField = ArrayIterator.class.getDeclaredField("index"); indexField.setAccessible(true); for (Iterator<?> entry : ((Map<Event, Iterator<?>>) currentIterField.get(loop)).values()) { return ((int) indexField.get(entry)); } } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) { e.printStackTrace(); } return null; } @Override public String toString(final @Nullable Event e, final boolean debug) { //TODO if (e == null) return name; if (isYamlLoop) { final Object current = loop.getCurrent(e); Object[] objects = ((ExprYaml<?>) loop.getLoopedExpression()).get(e); if (current == null || objects == null) return Classes.getDebugMessage(null); return loopState == LoopState.INDEX ? "\"" + getIndex() + "\"" : Classes.getDebugMessage(current); } return Classes.getDebugMessage(loop.getCurrent(e)); } }