BungeeChatContext.java

package dev.aura.bungeechat.api.placeholder;

import dev.aura.bungeechat.api.account.BungeeChatAccount;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.Predicate;
import lombok.Data;
import lombok.experimental.Tolerate;

/**
 * This class represents a context for a message or other chat related action.<br>
 * It may contain the acting player (sender), the receiver (target), the message and possibly more
 * in the future.
 */
@Data
public class BungeeChatContext {
  /**
   * Predefined Predicate to check if a context has a sender.
   *
   * @see BungeeChatContext#require(Predicate...)
   */
  public static final Predicate<BungeeChatContext> HAS_SENDER = BungeeChatContext::hasSender;
  /**
   * Predefined Predicate to check if a context has a target.
   *
   * @see BungeeChatContext#require(Predicate...)
   */
  public static final Predicate<BungeeChatContext> HAS_TARGET = BungeeChatContext::hasTarget;
  /**
   * Predefined Predicate to check if a context has a message.
   *
   * @see BungeeChatContext#require(Predicate...)
   */
  public static final Predicate<BungeeChatContext> HAS_MESSAGE = BungeeChatContext::hasMessage;
  /**
   * Predefined Predicate to check if a context has a channel.
   *
   * @see BungeeChatContext#require(Predicate...)
   */
  public static final Predicate<BungeeChatContext> HAS_CHANNEL = BungeeChatContext::hasChannel;
  /**
   * Predefined Predicate to check if a context has a server.
   *
   * @see BungeeChatContext#require(Predicate...)
   */
  public static final Predicate<BungeeChatContext> HAS_SERVER = BungeeChatContext::hasServer;

  /**
   * Predefined Predicate to check if a context does not have a sender.
   *
   * @see BungeeChatContext#require(Predicate...)
   */
  public static final Predicate<BungeeChatContext> HAS_NO_SENDER = HAS_SENDER.negate();
  /**
   * Predefined Predicate to check if a context does not have a target.
   *
   * @see BungeeChatContext#require(Predicate...)
   */
  public static final Predicate<BungeeChatContext> HAS_NO_TARGET = HAS_TARGET.negate();
  /**
   * Predefined Predicate to check if a context does not have a message.
   *
   * @see BungeeChatContext#require(Predicate...)
   */
  public static final Predicate<BungeeChatContext> HAS_NO_MESSAGE = HAS_MESSAGE.negate();
  /**
   * Predefined Predicate to check if a context does not have a channel.
   *
   * @see BungeeChatContext#require(Predicate...)
   */
  public static final Predicate<BungeeChatContext> HAS_NO_CHANNEL = HAS_CHANNEL.negate();
  /**
   * Predefined Predicate to check if a context does not have a server.
   *
   * @see BungeeChatContext#require(Predicate...)
   */
  public static final Predicate<BungeeChatContext> HAS_NO_SERVER = HAS_SERVER.negate();

  private static final Map<Predicate<BungeeChatContext>, String> requirementsNameCache =
      new HashMap<>(8);

  private Optional<BungeeChatAccount> sender;
  private Optional<BungeeChatAccount> target;
  private Optional<String> message;
  private Optional<String> channel;
  private Optional<String> server;

  public BungeeChatContext() {
    sender = Optional.empty();
    target = Optional.empty();
    message = Optional.empty();
    channel = Optional.empty();
    server = Optional.empty();
  }

  public BungeeChatContext(BungeeChatAccount sender) {
    this();

    this.sender = Optional.ofNullable(sender);
  }

  public BungeeChatContext(String message) {
    this();

    this.message = Optional.ofNullable(message);
  }

  public BungeeChatContext(BungeeChatAccount sender, String message) {
    this(sender);

    this.message = Optional.ofNullable(message);
  }

  public BungeeChatContext(BungeeChatAccount sender, BungeeChatAccount target) {
    this(sender);

    this.target = Optional.ofNullable(target);
  }

  public BungeeChatContext(BungeeChatAccount sender, BungeeChatAccount target, String message) {
    this(sender, target);

    this.message = Optional.ofNullable(message);
  }

  public BungeeChatContext(BungeeChatAccount sender, String message, String server) {
    this(sender, message);

    this.server = Optional.ofNullable(server);
  }

  /**
   * This method is used to verify if a context is valid. All passed requirements must be true in
   * order for this test to pass. If it fails an {@link InvalidContextError} is thrown.<br>
   * It is recommended to use the static predefined {@link Predicate}s like {@link
   * BungeeChatContext#HAS_SENDER}.
   *
   * @param requirements An array of requirements which all must be true for this context to be
   *     valid.
   * @throws InvalidContextError This assertion error gets thrown when one (or more) requirements
   *     are not met. If it is a predefined {@link Predicate} from {@link BungeeChatContext} the
   *     name will be included in the error message. If not a generic message will be put.
   * @see BungeeChatContext#HAS_SENDER
   * @see BungeeChatContext#HAS_TARGET
   * @see BungeeChatContext#HAS_MESSAGE
   * @see BungeeChatContext#HAS_CHANNEL
   * @see BungeeChatContext#HAS_NO_SENDER
   * @see BungeeChatContext#HAS_NO_TARGET
   * @see BungeeChatContext#HAS_NO_MESSAGE
   * @see BungeeChatContext#HAS_NO_CHANNEL
   */
  @SafeVarargs
  public final void require(Predicate<? super BungeeChatContext>... requirements)
      throws InvalidContextError {
    for (Predicate<? super BungeeChatContext> requirement : requirements) {
      if (!requirement.test(this)) {
        if (requirementsNameCache.containsKey(requirement))
          throw new InvalidContextError(requirementsNameCache.get(requirement));

        throw new InvalidContextError();
      }
    }
  }

  public boolean hasSender() {
    return sender.isPresent();
  }

  public boolean hasTarget() {
    return target.isPresent();
  }

  public boolean hasMessage() {
    return message.isPresent();
  }

  public boolean hasChannel() {
    return channel.isPresent();
  }

  public boolean hasServer() {
    return server.isPresent();
  }

  @Tolerate
  public void setSender(BungeeChatAccount sender) {
    setSender(Optional.ofNullable(sender));
  }

  @Tolerate
  public void setTarget(BungeeChatAccount target) {
    setTarget(Optional.ofNullable(target));
  }

  @Tolerate
  public void setMessage(String message) {
    setMessage(Optional.ofNullable(message));
  }

  @Tolerate
  public void setChannel(String channel) {
    setChannel(Optional.ofNullable(channel));
  }

  @Tolerate
  public void setServer(String server) {
    setServer(Optional.ofNullable(server));
  }

  // Fill the requirementsNameCache
  static {
    final int modifiers = Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL;

    for (Field field : BungeeChatContext.class.getDeclaredFields()) {
      try {
        if ((field.getModifiers() & modifiers) == modifiers) {
          @SuppressWarnings("unchecked")
          Predicate<BungeeChatContext> filter = (Predicate<BungeeChatContext>) field.get(null);

          requirementsNameCache.put(
              filter, "Context does not meet requirement " + field.getName() + "!");
        }

      } catch (IllegalArgumentException | IllegalAccessException e) {
        e.printStackTrace();
      }
    }
  }
}