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 * Invoked with a JSON member whenever this object is updated or created from a JSON object. 077 * 078 * <p>Subclasses should override this method in order to parse any JSON members it knows about. This method is a 079 * no-op by default.</p> 080 * 081 * @param member the JSON member to be parsed. 082 */ 083 void parseJSONMember(JsonObject.Member member) { } 084 085 /** 086 * Adds a pending field change that needs to be sent to the API. It will be included in the JSON string the next 087 * time {@link #getPendingChanges} is called. 088 * @param key the name of the field. 089 * @param value the new boolean value of the field. 090 */ 091 void addPendingChange(String key, boolean value) { 092 if (this.pendingChanges == null) { 093 this.pendingChanges = new JsonObject(); 094 } 095 096 this.pendingChanges.set(key, value); 097 } 098 099 /** 100 * Adds a pending field change that needs to be sent to the API. It will be included in the JSON string the next 101 * time {@link #getPendingChanges} is called. 102 * @param key the name of the field. 103 * @param value the new String value of the field. 104 */ 105 void addPendingChange(String key, String value) { 106 this.addPendingChange(key, JsonValue.valueOf(value)); 107 } 108 109 /** 110 * Adds a pending field change that needs to be sent to the API. It will be included in the JSON string the next 111 * time {@link #getPendingChanges} is called. 112 * @param key the name of the field. 113 * @param value the new long value of the field. 114 */ 115 void addPendingChange(String key, long value) { 116 this.addPendingChange(key, JsonValue.valueOf(value)); 117 } 118 119 /** 120 * Adds a pending field change that needs to be sent to the API. It will be included in the JSON string the next 121 * time {@link #getPendingChanges} is called. 122 * @param key the name of the field. 123 * @param value the new JsonArray value of the field. 124 */ 125 void addPendingChange(String key, JsonArray value) { 126 this.addPendingChange(key, (JsonValue) value); 127 } 128 129 void addChildObject(String fieldName, BoxJSONObject child) { 130 if (child == null) { 131 this.addPendingChange(fieldName, JsonValue.NULL); 132 } else { 133 this.children.put(fieldName, child); 134 } 135 } 136 137 void removeChildObject(String fieldName) { 138 this.children.remove(fieldName); 139 } 140 141 /** 142 * Adds a pending field change that needs to be sent to the API. It will be included in the JSON string the next 143 * time {@link #getPendingChanges} is called. 144 * @param key the name of the field. 145 * @param value the JsonValue of the field. 146 */ 147 private void addPendingChange(String key, JsonValue value) { 148 if (this.pendingChanges == null) { 149 this.pendingChanges = new JsonObject(); 150 } 151 152 this.pendingChanges.set(key, value); 153 } 154 155 void removePendingChange(String key) { 156 if (this.pendingChanges != null) { 157 this.pendingChanges.remove(key); 158 } 159 } 160 161 /** 162 * Updates this BoxJSONObject using the information in a JSON object. 163 * @param jsonObject the JSON object containing updated information. 164 */ 165 void update(JsonObject jsonObject) { 166 for (JsonObject.Member member : jsonObject) { 167 if (member.getValue().isNull()) { 168 continue; 169 } 170 171 this.parseJSONMember(member); 172 } 173 174 this.clearPendingChanges(); 175 } 176 177 /** 178 * Gets a JsonObject containing any pending changes to this object that can be sent back to the Box API. 179 * @return a JsonObject containing the pending changes. 180 */ 181 private JsonObject getPendingJSONObject() { 182 for (Map.Entry<String, BoxJSONObject> entry : this.children.entrySet()) { 183 BoxJSONObject child = entry.getValue(); 184 JsonObject jsonObject = child.getPendingJSONObject(); 185 if (jsonObject != null) { 186 if (this.pendingChanges == null) { 187 this.pendingChanges = new JsonObject(); 188 } 189 190 this.pendingChanges.set(entry.getKey(), jsonObject); 191 } 192 } 193 return this.pendingChanges; 194 } 195}