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