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 void addChildObject(String fieldName, BoxJSONObject child) { 109 if (child == null) { 110 this.addPendingChange(fieldName, JsonValue.NULL); 111 } else { 112 this.children.put(fieldName, child); 113 } 114 } 115 116 void removeChildObject(String fieldName) { 117 this.children.remove(fieldName); 118 } 119 120 /** 121 * Adds a pending field change that needs to be sent to the API. It will be included in the JSON string the next 122 * time {@link #getPendingChanges} is called. 123 * @param key the name of the field. 124 * @param value the JsonValue of the field. 125 */ 126 private void addPendingChange(String key, JsonValue value) { 127 if (this.pendingChanges == null) { 128 this.pendingChanges = new JsonObject(); 129 } 130 131 this.pendingChanges.set(key, value); 132 } 133 134 void removePendingChange(String key) { 135 if (this.pendingChanges != null) { 136 this.pendingChanges.remove(key); 137 } 138 } 139 140 /** 141 * Updates this BoxJSONObject using the information in a JSON object. 142 * @param jsonObject the JSON object containing updated information. 143 */ 144 void update(JsonObject jsonObject) { 145 for (JsonObject.Member member : jsonObject) { 146 if (member.getValue().isNull()) { 147 continue; 148 } 149 150 this.parseJSONMember(member); 151 } 152 153 this.clearPendingChanges(); 154 } 155 156 /** 157 * Gets a JsonObject containing any pending changes to this object that can be sent back to the Box API. 158 * @return a JsonObject containing the pending changes. 159 */ 160 private JsonObject getPendingJSONObject() { 161 for (Map.Entry<String, BoxJSONObject> entry : this.children.entrySet()) { 162 BoxJSONObject child = entry.getValue(); 163 JsonObject jsonObject = child.getPendingJSONObject(); 164 if (jsonObject != null) { 165 if (this.pendingChanges == null) { 166 this.pendingChanges = new JsonObject(); 167 } 168 169 this.pendingChanges.set(entry.getKey(), jsonObject); 170 } 171 } 172 return this.pendingChanges; 173 } 174}