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}