/* * 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.util; import java.lang.reflect.Array; import java.util.Iterator; 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.SkriptAPIException; import ch.njol.skript.classes.Changer; import ch.njol.skript.classes.Changer.ChangeMode; import ch.njol.skript.classes.ClassInfo; import ch.njol.skript.classes.Converter; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.registrations.Classes; import ch.njol.skript.util.Utils; import ch.njol.util.Checker; import ch.njol.util.Kleenean; import ch.njol.util.coll.CollectionUtils; import ch.njol.util.coll.iterator.ArrayIterator; /** * An implementation of the {@link Expression} interface. You should usually extend this class to make a new expression. * * @see Skript#registerExpression(Class, Class, ExpressionType, String...) * @author Peter Güttinger */ public abstract class SimpleExpression<T> implements Expression<T> { private int time = 0; protected SimpleExpression() {} @Override @Nullable public final T getSingle(final Event e) { final T[] all = getArray(e); if (all.length == 0) return null; if (all.length > 1) throw new SkriptAPIException("Call to getSingle() on a non-single expression"); return all[0]; } /** * {@inheritDoc} * <p> * Unlike {@link #get(Event)} you have to make sure that the this method's returned array is neither null nor contains null elements. */ @SuppressWarnings("unchecked") @Override public T[] getAll(final Event e) { final T[] all = get(e); if (all == null) { final T[] r = (T[]) Array.newInstance(getReturnType(), 0); assert r != null; return r; } if (all.length == 0) return all; int numNonNull = 0; for (final T t : all) if (t != null) numNonNull++; if (numNonNull == all.length) return all; final T[] r = (T[]) Array.newInstance(getReturnType(), numNonNull); assert r != null; int i = 0; for (final T t : all) if (t != null) r[i++] = t; return r; } @SuppressWarnings("unchecked") @Override public final T[] getArray(final Event e) { final T[] all = get(e); if (all == null) { final T[] r = (T[]) Array.newInstance(getReturnType(), 0); assert r != null; return r; } if (all.length == 0) return all; int numNonNull = 0; for (final T t : all) if (t != null) numNonNull++; if (!getAnd()) { if (all.length == 1 && all[0] != null) return all; int rand = Utils.random(0, numNonNull); final T[] one = (T[]) Array.newInstance(getReturnType(), 1); for (final T t : all) { if (t != null) { if (rand == 0) { one[0] = t; return one; } rand--; } } assert false; } if (numNonNull == all.length) return all; final T[] r = (T[]) Array.newInstance(getReturnType(), numNonNull); assert r != null; int i = 0; for (final T t : all) if (t != null) r[i++] = t; return r; } /** * This is the internal method to get an expression's values.<br> * To get the expression's value from the outside use {@link #getSingle(Event)} or {@link #getArray(Event)}. * * @param e The event * @return An array of values for this event. May not contain nulls. */ @Nullable protected abstract T[] get(Event e); @Override public final boolean check(final Event e, final Checker<? super T> c) { return check(e, c, false); } @Override public final boolean check(final Event e, final Checker<? super T> c, final boolean negated) { return check(get(e), c, negated, getAnd()); } // TODO return a kleenean (UNKNOWN if 'all' is null or empty) public final static <T> boolean check(final @Nullable T[] all, final Checker<? super T> c, final boolean invert, final boolean and) { if (all == null) return false; boolean hasElement = false; for (final T t : all) { if (t == null) continue; hasElement = true; final boolean b = c.check(t); if (and && !b) return invert ^ false; if (!and && b) return invert ^ true; } if (!hasElement) return false; return invert ^ and; } /** * Converts this expression to another type. Unless the expression is special, the default implementation is sufficient. * <p> * This method is never called with a supertype of the return type of this expression, or the return type itself. * * @param to The desired return type of the returned expression * @return Expression with the desired return type or null if it can't be converted to the given type * @see Expression#getConvertedExpression(Class...) * @see ConvertedExpression#newInstance(Expression, Class...) * @see Converter */ @Nullable protected <R> ConvertedExpression<T, ? extends R> getConvertedExpr(final Class<R>... to) { assert !CollectionUtils.containsSuperclass(to, getReturnType()); return ConvertedExpression.newInstance(this, to); } @Override @SuppressWarnings("unchecked") @Nullable public final <R> Expression<? extends R> getConvertedExpression(final Class<R>... to) { if (CollectionUtils.containsSuperclass(to, getReturnType())) return (Expression<? extends R>) this; return this.getConvertedExpr(to); } @Nullable private ClassInfo<?> returnTypeInfo; @Override @Nullable public Class<?>[] acceptChange(final ChangeMode mode) { ClassInfo<?> rti = returnTypeInfo; if (rti == null) returnTypeInfo = rti = Classes.getSuperClassInfo(getReturnType()); final Changer<?> c = rti.getChanger(); if (c == null) return null; return c.acceptChange(mode); } @SuppressWarnings("unchecked") @Override public void change(final Event e, final @Nullable Object[] delta, final ChangeMode mode) { final ClassInfo<?> rti = returnTypeInfo; if (rti == null) throw new UnsupportedOperationException(); final Changer<?> c = rti.getChanger(); if (c == null) throw new UnsupportedOperationException(); ((Changer<T>) c).change(getArray(e), delta, mode); } /** * {@inheritDoc} * <p> * This implementation sets the time but returns false. * * @see #setTime(int, Class, Expression...) * @see #setTime(int, Expression, Class...) */ @Override public boolean setTime(final int time) { if (ScriptLoader.hasDelayBefore == Kleenean.TRUE && time != 0) { Skript.error("Can't use time states after the event has already passed"); return false; } this.time = time; return false; } protected final boolean setTime(final int time, final Class<? extends Event> applicableEvent, final Expression<?>... mustbeDefaultVars) { if (ScriptLoader.hasDelayBefore == Kleenean.TRUE && time != 0) { Skript.error("Can't use time states after the event has already passed"); return false; } if (!ScriptLoader.isCurrentEvent(applicableEvent)) return false; for (final Expression<?> var : mustbeDefaultVars) { if (!var.isDefault()) { return false; } } this.time = time; return true; } protected final boolean setTime(final int time, final Expression<?> mustbeDefaultVar, final Class<? extends Event>... applicableEvents) { if (ScriptLoader.hasDelayBefore == Kleenean.TRUE && time != 0) { Skript.error("Can't use time states after the event has already passed"); return false; } if (!mustbeDefaultVar.isDefault()) return false; for (final Class<? extends Event> e : applicableEvents) { if (ScriptLoader.isCurrentEvent(e)) { this.time = time; return true; } } return false; } @Override public int getTime() { return time; } @Override public boolean isDefault() { return false; } @Override public boolean isLoopOf(final String s) { return false; } @Override @Nullable public Iterator<? extends T> iterator(final Event e) { return new ArrayIterator<T>(getArray(e)); } @Override public String toString() { return toString(null, false); } @Override public Expression<?> getSource() { return this; } @Override public Expression<? extends T> simplify() { return this; } @Override public boolean getAnd() { return true; } }