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

com.almworks.jira.structure.api.util.MapObject Maven / Gradle / Ivy

The newest version!
package com.almworks.jira.structure.api.util;

import com.google.common.collect.ImmutableMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.json.*;

import java.util.*;
import java.util.function.BiFunction;

/**
 * A wrapper for a Map-based structure.
 *
 * It is a wrapper around JSONObject, which is a wrapper around Map. Compensating some of the JSONObject deficiencies. Made all methods optional.
 *
 * todo maybe get rid of using JSONObject - depends on the further development and usage
 *
 * Problems:
 * 1. If underlying map contains another Map as a value, JSONObject.getObject() won't return JSONObject.
 * 2. Awkward iteration of keys in JSONObject
 *
 */
public class MapObject {
  public static final MapObject EMPTY = new MapObject(Collections.emptyMap());

  private static final BiFunction LONG_ARRAY_EXTRACTOR = (jsonArray, index) -> {
    Object v = jsonArray.opt(index);
    return v instanceof Number ? ((Number) v).longValue() : null;
  };
  private static final BiFunction INT_ARRAY_EXTRACTOR = (jsonArray, index) -> {
    Object v = jsonArray.opt(index);
    return v instanceof Number ? ((Number) v).intValue() : null;
  };
  private static final BiFunction STRING_ARRAY_EXTRACTOR = (jsonArray, index) -> jsonArray.optString(index, null);
  private static final BiFunction RAW_OBJECT_ARRAY_EXTRACTOR = JSONArray::opt;
  public static final BiFunction MAP_OBJECT_ARRAY_EXTRACTOR = (array, i) -> from(array.opt(i));

  @NotNull
  protected final JSONObject myObject;
  protected Map myMapView;

  public MapObject(JSONObject object) {
    myObject = object == null ? new JSONObject() : object;
  }

  public MapObject(Map map) {
    myObject = new JSONObject(map);
  }

  public static MapObject from(Object obj) {
    if (obj instanceof JSONObject) return new MapObject((JSONObject) obj);
    if (obj instanceof Map) return new MapObject(TypeUtils.mapType().cast(obj));
    return null;
  }

  public static  List transform(JSONArray array, BiFunction extractor) {
    if (array == null || array.length() == 0) return Collections.emptyList();
    int length = array.length();
    ArrayList r = new ArrayList<>(length);
    for (int i = 0; i < length; i++) {
      T value = extractor.apply(array, i);
      if (value != null) r.add(value);
    }
    return r;    
  }

  public boolean isEmpty() {
    return myObject.length() == 0;
  }

  public boolean has(String key) {
    return myObject.has(key);
  }
  /**
   * @param name
   * @return sub-object at key {@code} or empty MapObject
   */
  @Nullable
  public MapObject getObject(String name) {
    Object value = myObject.opt(name);
    if (value == null) return null;
    if (value instanceof JSONObject) {
      return new MapObject((JSONObject) value);
    }
    if (value instanceof Map) {
      return new MapObject(TypeUtils.mapType().cast(value));
    }
    return null;
  }

  @NotNull
  public MapObject traverse(String name) {
    MapObject r = getObject(name);
    return r == null ? EMPTY : r;
  }

  @NotNull
  public Iterable keys() {
    return () -> myObject.keys();
  }

  @Nullable
  public Object get(String key) {
    return myObject.opt(key);
  }

  @Nullable
  public String getString(String key) {
    return myObject.optString(key, null);
  }

  public long getLong(String key) {
    return myObject.optLong(key, 0);
  }

  public long getLong(String key, long defaultValue) {
    return myObject.optLong(key, defaultValue);
  }

  public int getInt(String key) {
    return myObject.optInt(key, 0);
  }

  public int getInt(String key, int defaultValue) {
    return myObject.optInt(key, defaultValue);
  }

  public boolean getBoolean(String key) {
    return myObject.optBoolean(key);
  }

  public boolean getBoolean(String key, boolean defaultValue) {
    return myObject.optBoolean(key, defaultValue);
  }

  @NotNull
  public List getLongList(@Nullable String key) {
    return getList(key, LONG_ARRAY_EXTRACTOR);
  }

  @NotNull
  public List getIntList(@Nullable String key) {
    return getList(key, INT_ARRAY_EXTRACTOR);
  }

  @NotNull
  public List getStringList(@Nullable String key) {
    return getList(key, STRING_ARRAY_EXTRACTOR);
  }

  @NotNull
  public List getRawObjectList(@Nullable String key) {
    return getList(key, RAW_OBJECT_ARRAY_EXTRACTOR);
  }

  @NotNull
  public List getObjectList(@Nullable String key) {
    return getList(key, MAP_OBJECT_ARRAY_EXTRACTOR);
  }

  @NotNull
  public  List getList(@Nullable String key, BiFunction extractor) {
    if (key == null) return Collections.emptyList();

    // todo ListObject similar to MapObject
    JSONArray array;
    Object obj = myObject.opt(key);
    if (obj instanceof JSONArray) {
      array = (JSONArray) obj;
    } else if (obj instanceof Collection) {
      array = new JSONArray((Collection) obj);
    } else {
      array = null;
    }

    return transform(array, extractor);
  }

  public Map asImmutableMap() {
    if (myMapView == null) {
      try {
        myMapView = ImmutableMap.copyOf(JsonMapUtil.jsonToMap(myObject));
      } catch (JSONException e) {
        throw new IllegalArgumentException("failed to convert json object: " + e.getMessage());
      }
    }
    return myMapView;
  }

  @Override
  public String toString() {
    // todo account for inner MapObjects
    return myObject.toString();
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    // todo We can't use myObject.equals() because of the possibility of nested MapObjects
    MapObject mapObject = (MapObject) o;
    return myObject.equals(mapObject.myObject);
  }

  @Override
  public int hashCode() {
    return myObject.hashCode();
  }
}