AuditLogChangeDeserializer.java

package com.github.princesslana.eriscasper.data.util;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.github.princesslana.eriscasper.data.Snowflake;
import com.github.princesslana.eriscasper.data.resource.AuditLogChange;
import com.github.princesslana.eriscasper.data.resource.ImmutableAuditLogChange;
import com.github.princesslana.eriscasper.data.resource.Overwrite;
import com.github.princesslana.eriscasper.data.resource.Role;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.util.Iterator;
import java.util.Optional;

public class AuditLogChangeDeserializer extends StdDeserializer<AuditLogChange> {

  public AuditLogChangeDeserializer() {
    super((Class<?>) null);
  }

  @Override
  public AuditLogChange deserialize(JsonParser json, DeserializationContext context)
      throws IOException {
    JsonNode node = json.getCodec().readTree(json);
    return parseJson(node, context, json.getCodec());
  }

  private AuditLogChange parseJson(JsonNode node, DeserializationContext context, ObjectCodec codec)
      throws JsonProcessingException {
    String key = node.get("key").textValue();
    switch (key.toLowerCase()) {
      case "$add":
      case "$remove":
        return newArrayInstance(node, Role.class, key, codec);
      case "permission_overwrites":
        return newArrayInstance(node, Overwrite.class, key, codec);
      case "owner_id":
      case "afk_channel_id":
      case "widget_channel_id":
      case "system_channel_id":
      case "channel_id":
      case "inviter_id":
      case "id":
      case "application_id":
        return newInstance(node, Snowflake.class, key, codec);
      case "widget_enabled":
      case "nsfw":
      case "temporary":
      case "deaf":
      case "mute":
      case "hoist":
      case "mentionable":
        return newInstance(node, Boolean.class, key, codec);
      case "permissions":
      case "color":
      case "allow":
      case "deny":
      case "max_uses":
      case "uses":
      case "max_age":
      case "afk_timeout":
      case "mfa_level":
      case "verification_level":
      case "explicit_content_filter":
      case "default_message_notifications":
      case "position":
      case "bitrate":
      case "prune_delete_days":
        return newInstance(node, Integer.class, key, codec);
      case "type":
      case "nick":
      case "avatar_hash":
      case "name":
      case "icon_hash":
      case "splash_hash":
      case "code":
      case "region":
      case "vanity_url_code":
      case "topic":
        return newInstance(node, String.class, key, codec);
      default:
        // just for stuff not represented by discord (like system_channel_id wasn't)
        // we will catch them as they're reported, can't predict stuff with no documentation for it
        throw context.instantiationException(
            AuditLogChange.class, "Failed to load " + key + " from " + node.asText());
    }
  }

  private <T> AuditLogChange newInstance(
      JsonNode node, Class<T> clazz, String key, ObjectCodec codec) throws JsonProcessingException {
    T newValue = null;
    T oldValue = null;
    if (node.has("new_value")) {
      newValue = parse(node.get("new_value"), clazz, codec);
    }
    if (node.has("old_value")) {
      oldValue = parse(node.get("old_value"), clazz, codec);
    }
    return ImmutableAuditLogChange.builder()
        .key(key)
        .newValue(Optional.ofNullable(newValue))
        .oldValue(Optional.ofNullable(oldValue))
        .build();
  }

  private <T> AuditLogChange newArrayInstance(
      JsonNode node, Class<T> clazz, String key, ObjectCodec codec) throws JsonProcessingException {
    ImmutableList<T> newValue = ImmutableList.of();
    ImmutableList<T> oldValue = ImmutableList.of();
    if (node.has("new_value")) {
      newValue = parseArray(node.get("new_value"), clazz, codec);
    }
    if (node.has("old_value")) {
      oldValue = parseArray(node.get("old_value"), clazz, codec);
    }
    return ImmutableAuditLogChange.builder().key(key).newValue(newValue).oldValue(oldValue).build();
  }

  private <T> ImmutableList<T> parseArray(JsonNode node, Class<T> clazz, ObjectCodec codec)
      throws JsonProcessingException {
    ArrayNode array = (ArrayNode) node;
    ImmutableList.Builder<T> builder = ImmutableList.builder();
    Iterator<JsonNode> arrayTree = array.elements();
    while (arrayTree.hasNext()) {
      builder.add(parse(arrayTree.next(), clazz, codec));
    }
    return builder.build();
  }

  private <T> T parse(JsonNode node, Class<T> clazz, ObjectCodec codec)
      throws JsonProcessingException {
    return codec.treeToValue(node, clazz);
  }
}