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