001package com.box.sdk; 002 003import com.eclipsesource.json.Json; 004import com.eclipsesource.json.JsonArray; 005import com.eclipsesource.json.JsonObject; 006import com.eclipsesource.json.JsonValue; 007import java.util.HashMap; 008import java.util.Map; 009 010/** 011 * The abstract base class for all types that contain JSON data returned by the Box API. The most 012 * common implementation of BoxJSONObject is {@link BoxResource.Info} and its subclasses. Changes 013 * made to a BoxJSONObject will be tracked locally until the pending changes are sent back to Box in 014 * order to avoid unnecessary network requests. 015 */ 016public abstract class BoxJSONObject { 017 /** 018 * A map of other BoxJSONObjects which will be lazily converted to a JsonObject once 019 * getPendingChanges is called. This allows changes to be made to a child BoxJSONObject and still 020 * have those changes reflected in the JSON string. 021 */ 022 private final Map<String, BoxJSONObject> children; 023 /** 024 * The JsonObject that contains any local pending changes. When getPendingChanges is called, this 025 * object will be encoded to a JSON string. 026 */ 027 private JsonObject pendingChanges; 028 /** The current JSON object. */ 029 private JsonObject jsonObject; 030 031 /** Constructs an empty BoxJSONObject. */ 032 public BoxJSONObject() { 033 this.children = new HashMap<String, BoxJSONObject>(); 034 } 035 036 /** 037 * Constructs a BoxJSONObject by decoding it from a JSON string. 038 * 039 * @param json the JSON string to decode. 040 */ 041 public BoxJSONObject(String json) { 042 this(Json.parse(json).asObject()); 043 } 044 045 /** 046 * Constructs a BoxJSONObject using an already parsed JSON object. 047 * 048 * @param jsonObject the parsed JSON object. 049 */ 050 BoxJSONObject(JsonObject jsonObject) { 051 this(); 052 053 this.update(jsonObject); 054 } 055 056 /** Clears any pending changes from this JSON object. */ 057 public void clearPendingChanges() { 058 this.pendingChanges = null; 059 } 060 061 /** 062 * Gets a JSON string containing any pending changes to this object that can be sent back to the 063 * Box API. 064 * 065 * @return a JSON string containing the pending changes. 066 */ 067 public String getPendingChanges() { 068 JsonObject jsonObject = this.getPendingJSONObject(); 069 if (jsonObject == null) { 070 return null; 071 } 072 073 return jsonObject.toString(); 074 } 075 076 /** 077 * Gets a JSON string containing any pending changes to this object that can be sent back to the 078 * Box API. 079 * 080 * @return a JSON string containing the pending changes. 081 */ 082 public JsonObject getPendingChangesAsJsonObject() { 083 return this.getPendingJSONObject(); 084 } 085 086 /** 087 * Invoked with a JSON member whenever this object is updated or created from a JSON object. 088 * 089 * <p>Subclasses should override this method in order to parse any JSON members it knows about. 090 * This method is a no-op by default. 091 * 092 * @param member the JSON member to be parsed. 093 */ 094 void parseJSONMember(JsonObject.Member member) {} 095 096 /** 097 * Adds a pending field change that needs to be sent to the API. It will be included in the JSON 098 * string the next time {@link #getPendingChanges} is called. 099 * 100 * @param key the name of the field. 101 * @param value the new boolean value of the field. 102 */ 103 void addPendingChange(String key, boolean value) { 104 if (this.pendingChanges == null) { 105 this.pendingChanges = new JsonObject(); 106 } 107 108 this.pendingChanges.set(key, value); 109 } 110 111 /** 112 * Adds a pending field change that needs to be sent to the API. It will be included in the JSON 113 * string the next time {@link #getPendingChanges} is called. 114 * 115 * @param key the name of the field. 116 * @param value the new String value of the field. 117 */ 118 void addPendingChange(String key, String value) { 119 this.addPendingChange(key, Json.value(value)); 120 } 121 122 /** 123 * Adds a pending field change that needs to be sent to the API. It will be included in the JSON 124 * string the next time {@link #getPendingChanges} is called. 125 * 126 * @param key the name of the field. 127 * @param value the new long value of the field. 128 */ 129 void addPendingChange(String key, long value) { 130 this.addPendingChange(key, Json.value(value)); 131 } 132 133 /** 134 * Adds a pending field change that needs to be sent to the API. It will be included in the JSON 135 * string the next time {@link #getPendingChanges} is called. 136 * 137 * @param key the name of the field. 138 * @param value the new JsonArray value of the field. 139 */ 140 void addPendingChange(String key, JsonArray value) { 141 this.addPendingChange(key, (JsonValue) value); 142 } 143 144 /** 145 * Adds a pending field change that needs to be sent to the API. It will be included in the JSON 146 * string the next time {@link #getPendingChanges} is called. 147 * 148 * @param key the name of the field. 149 * @param value the new JsonObject value of the field. 150 */ 151 void addPendingChange(String key, JsonObject value) { 152 this.addPendingChange(key, (JsonValue) value); 153 } 154 155 void addChildObject(String fieldName, BoxJSONObject child) { 156 if (child == null) { 157 this.addPendingChange(fieldName, Json.NULL); 158 } else { 159 this.children.put(fieldName, child); 160 } 161 } 162 163 void removeChildObject(String fieldName) { 164 this.children.remove(fieldName); 165 } 166 167 /** 168 * Adds a pending field change that needs to be sent to the API. It will be included in the JSON 169 * string the next time {@link #getPendingChanges} is called. 170 * 171 * @param key the name of the field. 172 * @param value the JsonValue of the field. 173 */ 174 private void addPendingChange(String key, JsonValue value) { 175 if (this.pendingChanges == null) { 176 this.pendingChanges = new JsonObject(); 177 } 178 179 this.pendingChanges.set(key, value); 180 } 181 182 void removePendingChange(String key) { 183 if (this.pendingChanges != null) { 184 this.pendingChanges.remove(key); 185 } 186 } 187 188 /** 189 * Updates this BoxJSONObject using the information in a JSON object and preserves the JSON 190 * object. 191 * 192 * @param jsonObject the JSON object containing updated information. 193 */ 194 void update(JsonObject jsonObject) { 195 this.jsonObject = jsonObject; 196 197 for (JsonObject.Member member : jsonObject) { 198 if (member.getValue().isNull()) { 199 continue; 200 } 201 202 this.parseJSONMember(member); 203 } 204 205 this.clearPendingChanges(); 206 } 207 208 /** 209 * Gets a JsonObject containing any pending changes to this object that can be sent back to the 210 * Box API. 211 * 212 * @return a JsonObject containing the pending changes. 213 */ 214 protected JsonObject getPendingJSONObject() { 215 for (Map.Entry<String, BoxJSONObject> entry : this.children.entrySet()) { 216 BoxJSONObject child = entry.getValue(); 217 JsonObject jsonObject = child.getPendingJSONObject(); 218 if (jsonObject != null) { 219 if (this.pendingChanges == null) { 220 this.pendingChanges = new JsonObject(); 221 } 222 223 this.pendingChanges.set(entry.getKey(), jsonObject); 224 } 225 } 226 return this.pendingChanges; 227 } 228 229 /** 230 * Converts the JSON object into a string literal. 231 * 232 * @return a string representation of the JSON object. 233 */ 234 public String getJson() { 235 return this.jsonObject.toString(); 236 } 237}