001package com.box.sdk; 002 003import java.util.HashMap; 004import java.util.Map; 005 006import com.eclipsesource.json.JsonArray; 007import com.eclipsesource.json.JsonObject; 008import com.eclipsesource.json.JsonValue; 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 * 015 */ 016public abstract class BoxJSONObject { 017 /** 018 * The JsonObject that contains any local pending changes. When getPendingChanges is called, this object will be 019 * encoded to a JSON string. 020 */ 021 private JsonObject pendingChanges; 022 023 /** 024 * The current JSON object. 025 */ 026 private JsonObject jsonObject; 027 028 /** 029 * A map of other BoxJSONObjects which will be lazily converted to a JsonObject once getPendingChanges is called. 030 * This allows changes to be made to a child BoxJSONObject and still have those changes reflected in the JSON 031 * string. 032 */ 033 private final Map<String, BoxJSONObject> children; 034 035 /** 036 * Constructs an empty BoxJSONObject. 037 */ 038 public BoxJSONObject() { 039 this.children = new HashMap<String, BoxJSONObject>(); 040 } 041 042 /** 043 * Constructs a BoxJSONObject by decoding it from a JSON string. 044 * @param json the JSON string to decode. 045 */ 046 public BoxJSONObject(String json) { 047 this(JsonObject.readFrom(json)); 048 } 049 050 /** 051 * Constructs a BoxJSONObject using an already parsed JSON object. 052 * @param jsonObject the parsed JSON object. 053 */ 054 BoxJSONObject(JsonObject jsonObject) { 055 this(); 056 057 this.update(jsonObject); 058 } 059 060 /** 061 * Clears any pending changes from this JSON object. 062 */ 063 public void clearPendingChanges() { 064 this.pendingChanges = null; 065 } 066 067 /** 068 * Gets a JSON string containing any pending changes to this object that can be sent back to the Box API. 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 * @return a JSON string containing the pending changes. 083 */ 084 public JsonObject getPendingChangesAsJsonObject() { 085 JsonObject jsonObject = this.getPendingJSONObject(); 086 if (jsonObject == null) { 087 return null; 088 } 089 return jsonObject; 090 } 091 092 /** 093 * Invoked with a JSON member whenever this object is updated or created from a JSON object. 094 * 095 * <p>Subclasses should override this method in order to parse any JSON members it knows about. This method is a 096 * no-op by default.</p> 097 * 098 * @param member the JSON member to be parsed. 099 */ 100 void parseJSONMember(JsonObject.Member member) { } 101 102 /** 103 * Adds a pending field change that needs to be sent to the API. It will be included in the JSON string the next 104 * time {@link #getPendingChanges} is called. 105 * @param key the name of the field. 106 * @param value the new boolean value of the field. 107 */ 108 void addPendingChange(String key, boolean value) { 109 if (this.pendingChanges == null) { 110 this.pendingChanges = new JsonObject(); 111 } 112 113 this.pendingChanges.set(key, value); 114 } 115 116 /** 117 * Adds a pending field change that needs to be sent to the API. It will be included in the JSON string the next 118 * time {@link #getPendingChanges} is called. 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, JsonValue.valueOf(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 * @param key the name of the field. 130 * @param value the new long value of the field. 131 */ 132 void addPendingChange(String key, long value) { 133 this.addPendingChange(key, JsonValue.valueOf(value)); 134 } 135 136 /** 137 * Adds a pending field change that needs to be sent to the API. It will be included in the JSON string the next 138 * time {@link #getPendingChanges} is called. 139 * @param key the name of the field. 140 * @param value the new JsonArray value of the field. 141 */ 142 void addPendingChange(String key, JsonArray value) { 143 this.addPendingChange(key, (JsonValue) value); 144 } 145 146 /** 147 * Adds a pending field change that needs to be sent to the API. It will be included in the JSON string the next 148 * time {@link #getPendingChanges} is called. 149 * @param key the name of the field. 150 * @param value the new JsonObject value of the field. 151 */ 152 void addPendingChange(String key, JsonObject value) { 153 this.addPendingChange(key, (JsonValue) value); 154 } 155 156 void addChildObject(String fieldName, BoxJSONObject child) { 157 if (child == null) { 158 this.addPendingChange(fieldName, JsonValue.NULL); 159 } else { 160 this.children.put(fieldName, child); 161 } 162 } 163 164 void removeChildObject(String fieldName) { 165 this.children.remove(fieldName); 166 } 167 168 /** 169 * Adds a pending field change that needs to be sent to the API. It will be included in the JSON string the next 170 * time {@link #getPendingChanges} is called. 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 object. 190 * @param jsonObject the JSON object containing updated information. 191 */ 192 void update(JsonObject jsonObject) { 193 this.jsonObject = jsonObject; 194 195 for (JsonObject.Member member : jsonObject) { 196 if (member.getValue().isNull()) { 197 continue; 198 } 199 200 this.parseJSONMember(member); 201 } 202 203 this.clearPendingChanges(); 204 } 205 206 /** 207 * Gets a JsonObject containing any pending changes to this object that can be sent back to the Box API. 208 * @return a JsonObject containing the pending changes. 209 */ 210 private JsonObject getPendingJSONObject() { 211 for (Map.Entry<String, BoxJSONObject> entry : this.children.entrySet()) { 212 BoxJSONObject child = entry.getValue(); 213 JsonObject jsonObject = child.getPendingJSONObject(); 214 if (jsonObject != null) { 215 if (this.pendingChanges == null) { 216 this.pendingChanges = new JsonObject(); 217 } 218 219 this.pendingChanges.set(entry.getKey(), jsonObject); 220 } 221 } 222 return this.pendingChanges; 223 } 224 225 /** 226 * Converts the JSON object into a string literal. 227 * @return a string representation of the JSON object. 228 */ 229 public String getJson() { 230 return this.jsonObject.toString(); 231 } 232}