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