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 * A map of other BoxJSONObjects which will be lazily converted to a JsonObject once getPendingChanges is called. 025 * This allows changes to be made to a child BoxJSONObject and still have those changes reflected in the JSON 026 * string. 027 */ 028 private final Map<String, BoxJSONObject> children; 029 030 /** 031 * Constructs an empty BoxJSONObject. 032 */ 033 public BoxJSONObject() { 034 this.children = new HashMap<String, BoxJSONObject>(); 035 } 036 037 /** 038 * Constructs a BoxJSONObject by decoding it from a JSON string. 039 * @param json the JSON string to decode. 040 */ 041 public BoxJSONObject(String json) { 042 this(JsonObject.readFrom(json)); 043 } 044 045 /** 046 * Constructs a BoxJSONObject using an already parsed JSON object. 047 * @param jsonObject the parsed JSON object. 048 */ 049 BoxJSONObject(JsonObject jsonObject) { 050 this(); 051 052 this.update(jsonObject); 053 } 054 055 /** 056 * Clears any pending changes from this JSON object. 057 */ 058 public void clearPendingChanges() { 059 this.pendingChanges = null; 060 } 061 062 /** 063 * Gets a JSON string containing any pending changes to this object that can be sent back to the Box API. 064 * @return a JSON string containing the pending changes. 065 */ 066 public String getPendingChanges() { 067 JsonObject jsonObject = this.getPendingJSONObject(); 068 if (jsonObject == null) { 069 return null; 070 } 071 072 return jsonObject.toString(); 073 } 074 075 /** 076 * Gets a JSON string containing any pending changes to this object that can be sent back to the Box API. 077 * @return a JSON string containing the pending changes. 078 */ 079 public JsonObject getPendingChangesAsJsonObject() { 080 JsonObject jsonObject = this.getPendingJSONObject(); 081 if (jsonObject == null) { 082 return null; 083 } 084 return jsonObject; 085 } 086 087 /** 088 * Invoked with a JSON member whenever this object is updated or created from a JSON object. 089 * 090 * <p>Subclasses should override this method in order to parse any JSON members it knows about. This method is a 091 * no-op by default.</p> 092 * 093 * @param member the JSON member to be parsed. 094 */ 095 void parseJSONMember(JsonObject.Member member) { } 096 097 /** 098 * Adds a pending field change that needs to be sent to the API. It will be included in the JSON string the next 099 * time {@link #getPendingChanges} is called. 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 string the next 113 * time {@link #getPendingChanges} is called. 114 * @param key the name of the field. 115 * @param value the new String value of the field. 116 */ 117 void addPendingChange(String key, String value) { 118 this.addPendingChange(key, JsonValue.valueOf(value)); 119 } 120 121 /** 122 * Adds a pending field change that needs to be sent to the API. It will be included in the JSON string the next 123 * time {@link #getPendingChanges} is called. 124 * @param key the name of the field. 125 * @param value the new long value of the field. 126 */ 127 void addPendingChange(String key, long value) { 128 this.addPendingChange(key, JsonValue.valueOf(value)); 129 } 130 131 /** 132 * Adds a pending field change that needs to be sent to the API. It will be included in the JSON string the next 133 * time {@link #getPendingChanges} is called. 134 * @param key the name of the field. 135 * @param value the new JsonArray value of the field. 136 */ 137 void addPendingChange(String key, JsonArray value) { 138 this.addPendingChange(key, (JsonValue) value); 139 } 140 141 void addChildObject(String fieldName, BoxJSONObject child) { 142 if (child == null) { 143 this.addPendingChange(fieldName, JsonValue.NULL); 144 } else { 145 this.children.put(fieldName, child); 146 } 147 } 148 149 void removeChildObject(String fieldName) { 150 this.children.remove(fieldName); 151 } 152 153 /** 154 * Adds a pending field change that needs to be sent to the API. It will be included in the JSON string the next 155 * time {@link #getPendingChanges} is called. 156 * @param key the name of the field. 157 * @param value the JsonValue of the field. 158 */ 159 private void addPendingChange(String key, JsonValue value) { 160 if (this.pendingChanges == null) { 161 this.pendingChanges = new JsonObject(); 162 } 163 164 this.pendingChanges.set(key, value); 165 } 166 167 void removePendingChange(String key) { 168 if (this.pendingChanges != null) { 169 this.pendingChanges.remove(key); 170 } 171 } 172 173 /** 174 * Updates this BoxJSONObject using the information in a JSON object. 175 * @param jsonObject the JSON object containing updated information. 176 */ 177 void update(JsonObject jsonObject) { 178 for (JsonObject.Member member : jsonObject) { 179 if (member.getValue().isNull()) { 180 continue; 181 } 182 183 this.parseJSONMember(member); 184 } 185 186 this.clearPendingChanges(); 187 } 188 189 /** 190 * Gets a JsonObject containing any pending changes to this object that can be sent back to the Box API. 191 * @return a JsonObject containing the pending changes. 192 */ 193 private JsonObject getPendingJSONObject() { 194 for (Map.Entry<String, BoxJSONObject> entry : this.children.entrySet()) { 195 BoxJSONObject child = entry.getValue(); 196 JsonObject jsonObject = child.getPendingJSONObject(); 197 if (jsonObject != null) { 198 if (this.pendingChanges == null) { 199 this.pendingChanges = new JsonObject(); 200 } 201 202 this.pendingChanges.set(entry.getKey(), jsonObject); 203 } 204 } 205 return this.pendingChanges; 206 } 207}