001package org.nuxeo.box.api.marshalling.dao; 002 003import com.fasterxml.jackson.annotation.JsonAnyGetter; 004import com.fasterxml.jackson.annotation.JsonAnySetter; 005import com.fasterxml.jackson.annotation.JsonIgnore; 006 007import org.nuxeo.box.api.marshalling.jsonentities.DefaultJSONStringEntity; 008import org.apache.commons.lang.builder.HashCodeBuilder; 009import org.apache.commons.logging.Log; 010import org.apache.commons.logging.LogFactory; 011 012import java.util.ArrayList; 013import java.util.HashMap; 014import java.util.Map; 015import java.util.Set; 016 017public class BoxObject extends DefaultJSONStringEntity { 018 019 private static final Log log = LogFactory.getLog(BoxObject.class); 020 021 private final Map<String, Object> extraMap = new HashMap<String, Object>(); 022 023 private final Map<String, Object> map = new HashMap<String, Object>(); 024 025 public BoxObject() { 026 } 027 028 /** 029 * Instantiate the object from a map. Each entry in the map reflects to a field. 030 * 031 * @param map 032 */ 033 public BoxObject(Map<String, Object> map) { 034 cloneMap(this.map, map); 035 } 036 037 /** 038 * Copy constructor, this does deep copy for all the fields. 039 * 040 * @param obj 041 */ 042 public BoxObject(BoxObject obj) { 043 cloneMap(map, obj.map); 044 cloneMap(extraMap, obj.extraMap); 045 } 046 047 /** 048 * Clone a map to another 049 * 050 * @param destination 051 * @param source 052 */ 053 @SuppressWarnings("unchecked") 054 private static void cloneMap(Map<String, Object> destination, Map<String, Object> source) { 055 for (Map.Entry<String, Object> entry : source.entrySet()) { 056 Object value = entry.getValue(); 057 if (value instanceof BoxObject) { 058 try { 059 destination.put(entry.getKey(), 060 value.getClass().getConstructor(value.getClass()).newInstance(value)); 061 } catch (ReflectiveOperationException e) { 062 log.error(e, e); 063 } 064 } else if (value instanceof ArrayList<?>) { 065 ArrayList<Object> list = new ArrayList<Object>(); 066 cloneArrayList(list, (ArrayList<Object>) value); 067 destination.put(entry.getKey(), list); 068 } else { 069 destination.put(entry.getKey(), value); 070 } 071 } 072 } 073 074 /** 075 * Clone an arraylist. 076 * 077 * @param destination 078 * @param source 079 */ 080 private static void cloneArrayList(ArrayList<Object> destination, ArrayList<Object> source) { 081 for (Object obj : source) { 082 if (obj instanceof BoxObject) { 083 try { 084 destination.add(obj.getClass().getConstructor(obj.getClass()).newInstance(obj)); 085 } catch (ReflectiveOperationException e) { 086 log.error(e, e); 087 } 088 } else { 089 destination.add(obj); 090 } 091 } 092 } 093 094 /** 095 * Whether the two objects are equal. This strictly compares all the fields in the two objects, if any fields are 096 * different this returns false. 097 * 098 * @param obj 099 * @return Whether the two objects are equal. 100 */ 101 @Override 102 public boolean equals(Object obj) { 103 if (obj == null || !(obj instanceof BoxObject)) { 104 return false; 105 } 106 107 if (this == obj) { 108 return true; 109 } 110 111 BoxObject bObj = (BoxObject) obj; 112 return map.equals(bObj.map) && extraMap.equals(bObj.extraMap); 113 } 114 115 @Override 116 public int hashCode() { 117 return new HashCodeBuilder().append(map).append(extraMap).toHashCode(); 118 } 119 120 public void put(String key, Object value) { 121 map.put(key, value); 122 } 123 124 public void putAll(Map<String, Object> newMap) { 125 map.putAll(newMap); 126 } 127 128 public Object getValue(String key) { 129 return map.get(key); 130 } 131 132 /** 133 * Get extra data. This could be extra unknown data passed back from api responses, the data is put in a Map. 134 * 135 * @param key 136 * @return extra object 137 */ 138 public Object getExtraData(String key) { 139 return extraMap.get(key); 140 } 141 142 @JsonAnyGetter 143 public Map<String, Object> properties() { 144 return extraMap; 145 } 146 147 /** 148 * Use this method to check whether the object contains certain field at all. This helps differentiate the case when 149 * the field is not returned from server at all, or is returned from server but value is null. For the first case, 150 * this method returns false, the later case returns true. 151 * 152 * @return whether the field exists 153 */ 154 public boolean contains(String key) { 155 return map.containsKey(key) || extraMap.containsKey(key); 156 } 157 158 @JsonAnySetter 159 public void handleUnknown(String key, Object value) { 160 if (value instanceof String) { 161 extraMap.put(key, value); 162 } 163 } 164 165 /** 166 * Added to introspect the map to update document 167 * 168 * @since 5.9.2 169 */ 170 @JsonIgnore 171 public Set<String> getKeySet() { 172 return map.keySet(); 173 } 174}