All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.monitorjbl.json.JsonViewSerializer Maven / Gradle / Ivy

There is a newer version: 1.1.0
Show newest version
package com.monitorjbl.json;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;

public class JsonViewSerializer extends JsonSerializer {
  private final int cacheSize;

  public JsonViewSerializer() {
    this(1000);
  }

  public JsonViewSerializer(int cacheSize) {
    this.cacheSize = cacheSize;
  }

  @Override
  public void serialize(JsonView result, JsonGenerator jgen, SerializerProvider serializers) throws IOException {
    new JsonWriter(jgen, result, cacheSize).write(null, result.getValue());
  }

  static class JsonWriter {
    //caches the results of the @JsonIgnore test to cut down on expensive reflection calls
    static final Map hasJsonIgnoreCache = new ConcurrentHashMap<>();

    Stack path = new Stack<>();
    String currentPath = "";
    Match currentMatch = null;

    final JsonGenerator jgen;
    final JsonView result;
    final int cacheSize;

    JsonWriter(JsonGenerator jgen, JsonView result, int cacheSize) {
      this.jgen = jgen;
      this.result = result;
      this.cacheSize = cacheSize;
    }

    boolean writePrimitive(Object obj) throws IOException {
      if (obj instanceof String) {
        jgen.writeString((String) obj);
      } else if (obj instanceof Date) {
        jgen.writeNumber(((Date) obj).getTime());
      } else if (obj instanceof Integer) {
        jgen.writeNumber((Integer) obj);
      } else if (obj instanceof Long) {
        jgen.writeNumber((Long) obj);
      } else if (obj instanceof Short) {
        jgen.writeNumber((Short) obj);
      } else if (obj instanceof Double) {
        jgen.writeNumber((Double) obj);
      } else if (obj instanceof Float) {
        jgen.writeNumber((Float) obj);
      } else if (obj instanceof Character) {
        jgen.writeNumber((Character) obj);
      } else if (obj instanceof Byte) {
        jgen.writeNumber((Byte) obj);
      } else if (obj instanceof Boolean) {
        jgen.writeBoolean((Boolean) obj);
      } else {
        return false;
      }
      return true;
    }

    private boolean writeEnum(Object obj) throws IOException {
      if (obj.getClass().isEnum()) {
        jgen.writeString(obj.toString());
      } else {
        return false;
      }
      return true;
    }

    @SuppressWarnings("unchecked")
    boolean writeList(Object obj) throws IOException {
      if (obj instanceof List || obj instanceof Set || obj.getClass().isArray()) {
        Iterable iter;
        if (obj.getClass().isArray()) {
          if (obj instanceof byte[]) {
            jgen.writeBinary((byte[]) obj);
            return true;
          } else {
            iter = convertArray(obj);
          }
        } else {
          iter = (Iterable) obj;
        }

        jgen.writeStartArray();
        for (Object o : iter) {
          new JsonWriter(jgen, result, cacheSize).write(null, o);
        }
        jgen.writeEndArray();
      } else {
        return false;
      }
      return true;
    }

    @SuppressWarnings("unchecked")
    Iterable convertArray(Object obj) {
      Iterable iter;
      if (obj instanceof int[]) {
        int[] arr = (int[]) obj;
        iter = new ArrayList<>();
        for (int v : arr) {
          ((List) iter).add(v);
        }
      } else if (obj instanceof double[]) {
        double[] arr = (double[]) obj;
        iter = new ArrayList<>();
        for (double v : arr) {
          ((List) iter).add(v);
        }
      } else if (obj instanceof float[]) {
        float[] arr = (float[]) obj;
        iter = new ArrayList<>();
        for (float v : arr) {
          ((List) iter).add(v);
        }
      } else if (obj instanceof long[]) {
        long[] arr = (long[]) obj;
        iter = new ArrayList<>();
        for (long v : arr) {
          ((List) iter).add(v);
        }
      } else if (obj instanceof short[]) {
        short[] arr = (short[]) obj;
        iter = new ArrayList<>();
        for (short v : arr) {
          ((List) iter).add(v);
        }
      } else if (obj instanceof char[]) {
        char[] arr = (char[]) obj;
        iter = new ArrayList<>();
        for (char v : arr) {
          ((List) iter).add(v);
        }
      } else if (obj instanceof boolean[]) {
        boolean[] arr = (boolean[]) obj;
        iter = new ArrayList<>();
        for (boolean v : arr) {
          ((List) iter).add(v);
        }
      } else {
        iter = Arrays.asList((Object[]) obj);
      }
      return iter;
    }

    @SuppressWarnings("unchecked")
    boolean writeMap(Object obj) throws IOException {
      if (obj instanceof Map) {
        Map map = (Map) obj;

        jgen.writeStartObject();
        for (Object key : map.keySet()) {
          jgen.writeFieldName(key.toString());
          new JsonWriter(jgen, result, cacheSize).write(null, map.get(key));
        }
        jgen.writeEndObject();
      } else {
        return false;
      }
      return true;
    }

    void writeObject(Object obj) throws IOException {
      jgen.writeStartObject();

      Class cls = obj.getClass();
      while (!cls.equals(Object.class)) {
        Field[] fields = cls.getDeclaredFields();
        for (Field field : fields) {
          try {
            field.setAccessible(true);
            Object val = field.get(obj);

            if (val != null && fieldAllowed(field, obj.getClass())) {
              String name = field.getName();
              jgen.writeFieldName(name);
              write(name, val);
            }
          } catch (IllegalArgumentException | IllegalAccessException e) {
            e.printStackTrace();
          }
        }
        cls = cls.getSuperclass();
      }

      jgen.writeEndObject();
    }

    boolean fieldAllowed(Field field, Class declaringClass) {
      String name = field.getName();
      String prefix = currentPath.length() > 0 ? currentPath + "." : "";

      //search for matcher
      Match match = null;
      Class cls = declaringClass;
      while (!cls.equals(Object.class) && match == null) {
        match = result.getMatch(cls);
        cls = cls.getSuperclass();
      }
      if (match == null) {
        match = currentMatch;
      } else {
        prefix = "";
      }

      //if there is a match, respect it
      if (match != null) {
        currentMatch = match;
        int included = containsMatchingPattern(match.getIncludes(), prefix + name);
        int excluded = containsMatchingPattern(match.getExcludes(), prefix + name);
        /*
        The logic for this is a little complex. We're dealing with ternary logic to
        properly handle wildcard matches. We want matches made with wildcards to be
        overruled by matches without them.
         */

        if (included == 1) {
          return true;
        } else if (excluded == 1) {
          return false;
        } else if (included == 0) {
          return true;
        } else if (excluded == 0) {
          return false;
        } else {
          return !annotatedWithIgnore(field);
        }
      } else {
        //else, respect JsonIgnore only
        return !annotatedWithIgnore(field);
      }
    }

    boolean annotatedWithIgnore(Field f) {
      if (!hasJsonIgnoreCache.containsKey(f)) {
        JsonIgnore jsonIgnore = f.getAnnotation(JsonIgnore.class);
        JsonIgnoreProperties ignoreProperties = f.getDeclaringClass().getAnnotation(JsonIgnoreProperties.class);
        if (hasJsonIgnoreCache.size() > cacheSize) {
          hasJsonIgnoreCache.remove(hasJsonIgnoreCache.keySet().iterator().next());
        }
        hasJsonIgnoreCache.put(f, (jsonIgnore != null && jsonIgnore.value()) ||
            (ignoreProperties != null && Arrays.asList(ignoreProperties.value()).contains(f.getName())));
      }
      return hasJsonIgnoreCache.get(f);
    }

    /**
     * Returns one of the following values:
     * 
     * -1: No match found
     *  0: Wildcard-based match
     *  1: Non-wildcard match
     * 
* * @param values * @param pattern * @return */ int containsMatchingPattern(List values, String pattern) { for (String val : values) { String replaced = val.replaceAll("\\.", "\\\\.").replaceAll("\\*", ".*"); if (Pattern.compile(replaced).matcher(pattern).matches()) { return replaced.contains("*") ? 0 : 1; } } return -1; } void write(String fieldName, Object value) throws IOException { if (fieldName != null) { path.push(fieldName); updateCurrentPath(); } //try to handle all primitives before treating this as json object if (value != null && !writePrimitive(value) && !writeEnum(value) && !writeList(value) && !writeMap(value)) { writeObject(value); } if (fieldName != null) { path.pop(); updateCurrentPath(); } } void updateCurrentPath() { StringBuilder builder = new StringBuilder(); for (String s : path) { builder.append("."); builder.append(s); } currentPath = builder.length() > 0 ? builder.toString().substring(1) : ""; } } }