Package ch.njol.skript.lang

Source Code of ch.njol.skript.lang.VariableString$ExpressionInfo

/*
*   This file is part of Skript.
*
*  Skript is free software: you can redistribute it and/or modify
*  it under the terms of the GNU General Public License as published by
*  the Free Software Foundation, either version 3 of the License, or
*  (at your option) any later version.
*
*  Skript is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU General Public License for more details.
*
*  You should have received a copy of the GNU General Public License
*  along with Skript.  If not, see <http://www.gnu.org/licenses/>.
*
*
* Copyright 2011-2014 Peter Güttinger
*
*/

package ch.njol.skript.lang;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Pattern;

import org.bukkit.ChatColor;
import org.bukkit.event.Event;
import org.eclipse.jdt.annotation.Nullable;

import ch.njol.skript.ScriptLoader;
import ch.njol.skript.Skript;
import ch.njol.skript.SkriptConfig;
import ch.njol.skript.classes.Changer.ChangeMode;
import ch.njol.skript.classes.ClassInfo;
import ch.njol.skript.classes.Parser;
import ch.njol.skript.config.Config;
import ch.njol.skript.lang.SkriptParser.ParseResult;
import ch.njol.skript.lang.util.SimpleExpression;
import ch.njol.skript.localization.Language;
import ch.njol.skript.localization.Noun;
import ch.njol.skript.log.BlockingLogHandler;
import ch.njol.skript.log.RetainingLogHandler;
import ch.njol.skript.log.SkriptLogger;
import ch.njol.skript.registrations.Classes;
import ch.njol.skript.util.StringMode;
import ch.njol.skript.util.Utils;
import ch.njol.util.Checker;
import ch.njol.util.Kleenean;
import ch.njol.util.StringUtils;
import ch.njol.util.coll.CollectionUtils;
import ch.njol.util.coll.iterator.SingleItemIterator;

/**
* Represents a string that may contain expressions, and is thus "variable".
*
* @author Peter Güttinger
*/
public class VariableString implements Expression<String> {
 
  private final static class ExpressionInfo {
    ExpressionInfo(final Expression<?> expr) {
      this.expr = expr;
    }
   
    final Expression<?> expr;
    int flags = 0;
    boolean toChatStyle = false;
  }
 
  private final String orig;
 
  @Nullable
  private final Object[] string;
  private final boolean isSimple;
  @Nullable
  private final String simple;
  private final StringMode mode;
 
  private VariableString(final String s) {
    isSimple = true;
    simple = s;
   
    orig = s;
    string = null;
    mode = StringMode.MESSAGE;
  }
 
  private VariableString(final String orig, final Object[] string, final StringMode mode) {
    this.orig = orig;
    this.string = string;
    this.mode = mode;
   
    isSimple = false;
    simple = null;
  }
 
  /**
   * Prints errors
   */
  @Nullable
  public static VariableString newInstance(final String s) {
    return newInstance(s, StringMode.MESSAGE);
  }
 
  public final static Map<String, Pattern> variableNames = new HashMap<String, Pattern>();
 
  /**
   * Tests whether a string is correctly quoted, i.e. only has doubled double quotes in it.
   *
   * @param s The string
   * @param withQuotes Whether s must be surrounded by double quotes or not
   * @return Whether the string is quoted correctly
   */
  public final static boolean isQuotedCorrectly(final String s, final boolean withQuotes) {
    if (withQuotes && (!s.startsWith("\"") || !s.endsWith("\"")))
      return false;
    boolean quote = false;
    for (int i = withQuotes ? 1 : 0; i < (withQuotes ? s.length() - 1 : s.length()); i++) {
      if (s.charAt(i) != '"') {
        if (quote)
          return false;
      } else {
        quote = !quote;
      }
    }
    return !quote;
  }
 
  /**
   * Removes quoted quotes from a string.
   *
   * @param s The string
   * @param surroundingQuotes Whether the string has quotes at the start & end that should be removed
   * @return The string with double quotes replaced with signle ones and optionally with removed surrounding quotes.
   */
  public final static String unquote(final String s, final boolean surroundingQuotes) {
    assert isQuotedCorrectly(s, surroundingQuotes);
    if (surroundingQuotes)
      return "" + s.substring(1, s.length() - 1).replace("\"\"", "\"");
    return "" + s.replace("\"\"", "\"");
  }
 
  /**
   * Prints errors
   *
   * @param orig unquoted string
   * @param mode
   * @return A new VariableString instance
   */
  @Nullable
  public static VariableString newInstance(final String orig, final StringMode mode) {
    if (!isQuotedCorrectly(orig, false))
      return null;
    final int n = StringUtils.count(orig, '%');
    if (n % 2 != 0) {
      Skript.error("The percent sign is used for expressions (e.g. %player%). To insert a '%' type it twice: %%.");
      return null;
    }
    final String s = Utils.replaceChatStyles("" + orig.replace("\"\"", "\""));
    final ArrayList<Object> string = new ArrayList<Object>(n / 2 + 2);
    int c = s.indexOf('%');
    if (c != -1) {
      if (c != 0)
        string.add(s.substring(0, c));
      while (c != s.length()) {
        int c2 = s.indexOf('%', c + 1);
        int a = c, b;
        while (c2 != -1 && (b = s.indexOf('{', a + 1)) != -1 && b < c2) {
          a = nextVariableBracket(s, b + 1);
          if (a == -1) {
            Skript.error("Missing closing bracket '}' to end variable");
            return null;
          }
          c2 = s.indexOf('%', a + 1);
        }
        if (c2 == -1) {
          assert false;
          return null;
        }
        if (c + 1 == c2) {
          if (string.size() > 0 && string.get(string.size() - 1) instanceof String) {
            string.set(string.size() - 1, (String) string.get(string.size() - 1) + "%");
          } else {
            string.add("%");
          }
        } else {
          final RetainingLogHandler log = SkriptLogger.startRetainingLog();
          try {
            @SuppressWarnings("unchecked")
            final Expression<?> expr = new SkriptParser("" + s.substring(c + 1, c2), SkriptParser.PARSE_EXPRESSIONS, ParseContext.DEFAULT).parseExpression(Object.class);
            if (expr == null) {
              log.printErrors("Can't understand this expression: " + s.substring(c + 1, c2));
              return null;
            } else {
              if (mode != StringMode.MESSAGE) {
                string.add(expr);
              } else {
                final ExpressionInfo i = new ExpressionInfo(expr);
                if (c2 <= s.length() - 2 && s.charAt(c2 + 1) == 's' && (c2 == s.length() - 2 || !Character.isLetter(s.charAt(c2 + 2)))) {
                  i.flags |= Language.F_PLURAL;
                  c2++; // remove the 's'
                }
                if (string.size() > 0 && string.get(string.size() - 1) instanceof String) {
                  final String last = (String) string.get(string.size() - 1);
                  if (c2 <= s.length() - 2 && s.charAt(c2 + 1) == '>' && last.endsWith("<")) {
                    i.toChatStyle = true;
                    string.set(string.size() - 1, last.substring(0, last.length() - 1));
                    c2++; // remove the '>'
                  } else {
                    final int l = last.lastIndexOf(' ', last.endsWith(" ") ? last.length() - 2 : last.length() - 1);
                    final String lastWord = "" + last.substring(l + 1).trim();
                    if (Noun.isLocalIndefiniteArticle(lastWord))
                      i.flags |= Language.F_INDEFINITE_ARTICLE;
                    else if (Noun.isLocalDefiniteArticle(lastWord))
                      i.flags |= Language.F_DEFINITE_ARTICLE;
                    if ((i.flags & (Language.F_INDEFINITE_ARTICLE | Language.F_DEFINITE_ARTICLE)) != 0)
                      string.set(string.size() - 1, last.substring(0, l + 1));
                  }
                }
                string.add(i);
              }
            }
            log.printLog();
          } finally {
            log.stop();
          }
        }
        c = s.indexOf('%', c2 + 1);
        if (c == -1)
          c = s.length();
        final String l = s.substring(c2 + 1, c);
        if (!l.isEmpty()) {
          if (string.size() > 0 && string.get(string.size() - 1) instanceof String) {
            string.set(string.size() - 1, (String) string.get(string.size() - 1) + l);
          } else {
            string.add(l);
          }
        }
      }
    } else {
      string.add(s);
    }
   
    checkVariableConflicts(s, mode, string);
   
    if (string.size() == 1 && string.get(0) instanceof String)
      return new VariableString("" + string.get(0));
    final Object[] sa = string.toArray();
    assert sa != null;
    return new VariableString(orig, sa, mode);
  }
 
  private static void checkVariableConflicts(final String name, final StringMode mode, final @Nullable Iterable<Object> string) {
    if (mode != StringMode.VARIABLE_NAME || variableNames.containsKey(name))
      return;
    if (name.startsWith("%")) {// inside the if to only print this message once per variable
      final Config script = ScriptLoader.currentScript;
      if (script != null)
        Skript.warning("Starting a variable's name with an expression is discouraged ({" + name + "}). You could prefix it with the script's name: {" + StringUtils.substring(script.getFileName(), 0, -3) + "." + name + "}");
    }
   
    final Pattern pattern;
    if (string != null) {
      final StringBuilder p = new StringBuilder();
      stringLoop: for (final Object o : string) {
        if (o instanceof Expression) {
          for (final ClassInfo<?> ci : Classes.getClassInfos()) {
            final Parser<?> parser = ci.getParser();
            if (parser != null && ci.getC().isAssignableFrom(((Expression<?>) o).getReturnType())) {
              p.append("(?!%)" + parser.getVariableNamePattern() + "(?<!%)");
              continue stringLoop;
            }
          }
          p.append("[^%*](.*[^%*])?"); // [^*] to not report {var::%index%}/{var::*} as conflict
        } else {
          p.append(Pattern.quote(o.toString()));
        }
      }
      pattern = Pattern.compile(p.toString());
    } else {
      pattern = Pattern.compile(Pattern.quote(name));
    }
    if (!SkriptConfig.disableVariableConflictWarnings.value()) {
      for (final Entry<String, Pattern> e : variableNames.entrySet()) {
        if (e.getValue().matcher(name).matches() || pattern.matcher(e.getKey()).matches()) {
          Skript.warning("Possible name conflict of variables {" + name + "} and {" + e.getKey() + "} (there might be more conflicts).");
          break;
        }
      }
    }
    variableNames.put(name, pattern);
  }
 
  private void readObject(final ObjectInputStream in) throws ClassNotFoundException, IOException {
    in.defaultReadObject();
    checkVariableConflicts(Utils.replaceChatStyles("" + orig.replace("\"\"", "\"")), mode, string == null ? null : Arrays.asList(string));
  }
 
  /**
   * Copied from {@link SkriptParser#nextBracket(String, char, char, int, boolean)}, but removed escaping & returns -1 on error.
   *
   * @param s
   * @param start Index after the opening bracket
   * @return The next closing curly bracket
   */
  public static int nextVariableBracket(final String s, final int start) {
    int n = 0;
    for (int i = start; i < s.length(); i++) {
      if (s.charAt(i) == '}') {
        if (n == 0)
          return i;
        n--;
      } else if (s.charAt(i) == '{') {
        n++;
      }
    }
    return -1;
  }
 
  public static VariableString[] makeStrings(final String[] args) {
    VariableString[] strings = new VariableString[args.length];
    int j = 0;
    for (int i = 0; i < args.length; i++) {
      final VariableString vs = newInstance("" + args[i]);
      if (vs != null)
        strings[j++] = vs;
    }
    if (j != args.length)
      strings = Arrays.copyOf(strings, j);
    assert strings != null;
    return strings;
  }
 
  /**
   * @param args Quoted strings - This is not checked!
   * @return a new array containing all newly created VariableStrings, or null if one is invalid
   */
  @Nullable
  public static VariableString[] makeStringsFromQuoted(final List<String> args) {
    final VariableString[] strings = new VariableString[args.size()];
    for (int i = 0; i < args.size(); i++) {
      assert args.get(i).startsWith("\"") && args.get(i).endsWith("\"");
      final VariableString vs = newInstance("" + args.get(i).substring(1, args.get(i).length() - 1));
      if (vs == null)
        return null;
      strings[i] = vs;
    }
    return strings;
  }
 
  /**
   * Parses all expressions in the string and returns it.
   *
   * @param e Event to pass to the expressions.
   * @return The input string with all expressions replaced.
   */
  public String toString(final Event e) {
    if (isSimple) {
      assert simple != null;
      return simple;
    }
    final Object[] string = this.string;
    assert string != null;
    final StringBuilder b = new StringBuilder();
    for (int i = 0; i < string.length; i++) {
      final Object o = string[i];
      if (o instanceof Expression<?>) {
        assert mode != StringMode.MESSAGE;
        b.append(Classes.toString(((Expression<?>) o).getArray(e), true, mode));
      } else if (o instanceof ExpressionInfo) {
        assert mode == StringMode.MESSAGE;
        final ExpressionInfo info = (ExpressionInfo) o;
        int flags = info.flags;
        if ((flags & Language.F_PLURAL) == 0 && b.length() > 0 && Math.abs(StringUtils.numberBefore(b, b.length() - 1)) != 1)
          flags |= Language.F_PLURAL;
        if (info.toChatStyle) {
          final String s = Classes.toString(info.expr.getArray(e), flags, getLastColor(b));
          final String style = Utils.getChatStyle(s);
          b.append(style == null ? "<" + s + ">" : style);
        } else {
          b.append(Classes.toString(info.expr.getArray(e), flags, getLastColor(b)));
        }
      } else {
        b.append(o);
      }
    }
    return "" + b.toString();
  }
 
  @Nullable
  private final static ChatColor getLastColor(final CharSequence s) {
    for (int i = s.length() - 2; i >= 0; i--) {
      if (s.charAt(i) == ChatColor.COLOR_CHAR) {
        final ChatColor c = ChatColor.getByChar(s.charAt(i + 1));
        if (c != null && (c.isColor() || c == ChatColor.RESET))
          return c;
      }
    }
    return null;
  }
 
  @Override
  public String toString() {
    return toString(null, false);
  }
 
  /**
   * Use {@link #toString(Event)} to get the actual string
   */
  @Override
  public String toString(final @Nullable Event e, final boolean debug) {
    if (isSimple) {
      assert simple != null;
      return '"' + simple + '"';
    }
    final Object[] string = this.string;
    assert string != null;
    final StringBuilder b = new StringBuilder("\"");
    for (final Object o : string) {
      if (o instanceof Expression) {
        b.append("%").append(((Expression<?>) o).toString(e, debug)).append("%");
      } else if (o instanceof ExpressionInfo) {
        b.append("%").append(((ExpressionInfo) o).expr.toString(e, debug)).append("%");
      } else {
        b.append(o);
      }
    }
    b.append('"');
    return "" + b.toString();
  }
 
  public String getDefaultVariableName() {
    if (isSimple) {
      assert simple != null;
      return simple;
    }
    final Object[] string = this.string;
    assert string != null;
    final StringBuilder b = new StringBuilder();
    for (final Object o : string) {
      if (o instanceof Expression) {
        b.append("<" + Classes.getSuperClassInfo(((Expression<?>) o).getReturnType()).getCodeName() + ">");
      } else {
        b.append(o);
      }
    }
    return "" + b.toString();
  }
 
  public boolean isSimple() {
    return isSimple;
  }
 
  public StringMode getMode() {
    return mode;
  }
 
  public VariableString setMode(final StringMode mode) {
    if (this.mode == mode || isSimple)
      return this;
    final BlockingLogHandler h = SkriptLogger.startLogHandler(new BlockingLogHandler());
    try {
      final VariableString vs = newInstance(orig, mode);
      if (vs == null) {
        assert false : this + "; " + mode;
        return this;
      }
      return vs;
    } finally {
      h.stop();
    }
  }
 
  @Override
  public boolean init(final Expression<?>[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parseResult) {
    throw new UnsupportedOperationException();
  }
 
  @Override
  public String getSingle(final Event e) {
    return toString(e);
  }
 
  @Override
  public String[] getArray(final Event e) {
    return new String[] {toString(e)};
  }
 
  @Override
  public String[] getAll(final Event e) {
    return new String[] {toString(e)};
  }
 
  @Override
  public boolean isSingle() {
    return true;
  }
 
  @Override
  public boolean check(final Event e, final Checker<? super String> c, final boolean negated) {
    return SimpleExpression.check(getAll(e), c, negated, false);
  }
 
  @Override
  public boolean check(final Event e, final Checker<? super String> c) {
    return SimpleExpression.check(getAll(e), c, false, false);
  }
 
  @SuppressWarnings("unchecked")
  @Override
  @Nullable
  public <R> Expression<? extends R> getConvertedExpression(final Class<R>... to) {
    if (CollectionUtils.containsSuperclass(to, String.class))
      return (Expression<? extends R>) this;
    return null;
  }
 
  @Override
  public Class<? extends String> getReturnType() {
    return String.class;
  }
 
  @Override
  @Nullable
  public Class<?>[] acceptChange(final ChangeMode mode) {
    return null;
  }
 
  @Override
  public void change(final Event e, final @Nullable Object[] delta, final ChangeMode mode) throws UnsupportedOperationException {
    throw new UnsupportedOperationException();
  }
 
  @Override
  public boolean getAnd() {
    return false;
  }
 
  @Override
  public boolean setTime(final int time) {
    return false;
  }
 
  @Override
  public int getTime() {
    return 0;
  }
 
  @Override
  public boolean isDefault() {
    return false;
  }
 
  @Override
  public Iterator<? extends String> iterator(final Event e) {
    return new SingleItemIterator<String>(toString(e));
  }
 
  @Override
  public boolean isLoopOf(final String s) {
    return false;
  }
 
  @Override
  public Expression<?> getSource() {
    return this;
  }
 
  @SuppressWarnings("unchecked")
  public final static <T> Expression<T> setStringMode(final Expression<T> e, final StringMode mode) {
    if (e instanceof ExpressionList) {
      final Expression<?>[] ls = ((ExpressionList<?>) e).getExpressions();
      for (int i = 0; i < ls.length; i++) {
        final Expression<?> l = ls[i];
        assert l != null;
        ls[i] = setStringMode(l, mode);
      }
    } else if (e instanceof VariableString) {
      return (Expression<T>) ((VariableString) e).setMode(mode);
    }
    return e;
  }
 
  @Override
  public Expression<String> simplify() {
    return this;
  }
 
  /* REMIND allow special characters?
  private static String allowedChars = null;
  private static Field allowedCharacters = null;
 
  static {
    if (Skript.isRunningCraftBukkit()) {
      try {
        allowedCharacters = SharedConstants.class.getDeclaredField("allowedCharacters");
        allowedCharacters.setAccessible(true);
        Field modifiersField = Field.class.getDeclaredField("modifiers");
        modifiersField.setAccessible(true);
        modifiersField.setInt(allowedCharacters, allowedCharacters.getModifiers() & ~Modifier.FINAL);
        allowedChars = (String) allowedCharacters.get(null);
      } catch (Throwable e) {
        allowedChars = null;
        allowedCharacters = null;
      }
    }
  }
  */
}
 
TOP

Related Classes of ch.njol.skript.lang.VariableString$ExpressionInfo

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.