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}