001/* 002 * The MIT License 003 * Copyright (c) 2012 Microsoft Corporation 004 * 005 * Permission is hereby granted, free of charge, to any person obtaining a copy 006 * of this software and associated documentation files (the "Software"), to deal 007 * in the Software without restriction, including without limitation the rights 008 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 009 * copies of the Software, and to permit persons to whom the Software is 010 * furnished to do so, subject to the following conditions: 011 * 012 * The above copyright notice and this permission notice shall be included in 013 * all copies or substantial portions of the Software. 014 * 015 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 016 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 017 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 018 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 019 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 020 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 021 * THE SOFTWARE. 022 */ 023 024package microsoft.exchange.webservices.data.misc; 025 026import microsoft.exchange.webservices.data.core.EwsUtilities; 027import microsoft.exchange.webservices.data.core.ILazyMember; 028import microsoft.exchange.webservices.data.core.LazyMember; 029import microsoft.exchange.webservices.data.core.exception.misc.ArgumentException; 030import microsoft.exchange.webservices.data.core.exception.misc.ArgumentNullException; 031import microsoft.exchange.webservices.data.core.exception.misc.FormatException; 032import microsoft.exchange.webservices.data.core.exception.service.local.ServiceXmlDeserializationException; 033import org.apache.commons.lang3.StringUtils; 034import org.apache.commons.logging.Log; 035import org.apache.commons.logging.LogFactory; 036 037import java.text.DateFormat; 038import java.text.ParseException; 039import java.text.SimpleDateFormat; 040import java.util.ArrayList; 041import java.util.Date; 042import java.util.HashMap; 043import java.util.Map; 044import java.util.UUID; 045 046/** 047 * Represents an entry in the MapiTypeConverter map. 048 */ 049public class MapiTypeConverterMapEntry { 050 051 private static final Log LOG = LogFactory.getLog(MapiTypeConverterMapEntry.class); 052 053 /** 054 * Map CLR types used for MAPI property to matching default values. 055 */ 056 private static LazyMember<Map<Class<?>, Object>> defaultValueMap = new LazyMember<Map<Class<?>, Object>>( 057 new ILazyMember<Map<Class<?>, Object>>() { 058 public Map<Class<?>, Object> createInstance() { 059 060 Map<Class<?>, Object> map = new HashMap<Class<?>, Object>(); 061 062 map.put(Boolean.class, false); 063 map.put(Byte[].class, null); 064 map.put(Short.class, new Short((short) 0)); 065 map.put(Integer.class, 0); 066 map.put(Long.class, new Long(0L)); 067 map.put(Float.class, new Float(0.0)); 068 map.put(Double.class, new Double(0.0D)); 069 SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 070 try { 071 map.put(Date.class, formatter.parse("0001-01-01 12:00:00")); 072 } catch (ParseException e) { 073 LOG.error(e); 074 } 075 map.put(UUID.class, UUID.fromString("00000000-0000-0000-0000-000000000000")); 076 map.put(String.class, null); 077 078 return map; 079 080 } 081 }); 082 /** 083 * The is array. 084 */ 085 boolean isArray; 086 087 /** 088 * The type. 089 */ 090 Class<?> type; 091 092 /** 093 * The convert to string. 094 */ 095 IFunction<Object, String> convertToString; 096 097 /** 098 * The parse. 099 */ 100 IFunction<String, Object> parse; 101 102 /** 103 * Initializes a new instance of the MapiTypeConverterMapEntry class. 104 * 105 * @param type The type. y default, converting a type to string is done by 106 * calling value.ToString. Instances can override this behavior. 107 * <p/> 108 * By default, converting a string to the appropriate value type 109 * is done by calling Convert.ChangeType Instances may override 110 * this behavior. 111 */ 112 public MapiTypeConverterMapEntry(Class<?> type) { 113 EwsUtilities.ewsAssert(defaultValueMap.getMember().containsKey(type), "MapiTypeConverterMapEntry ctor", 114 "No default value entry for type " + type.getName()); 115 116 this.type = type; 117 this.convertToString = IFunctions.ToString.INSTANCE; 118 this.parse = IFunctions.StringToObject.INSTANCE; 119 } 120 121 /** 122 * Change value to a value of compatible type. 123 * <p/> 124 * The type of a simple value should match exactly or be convertible to the 125 * appropriate type. An array value has to be a single dimension (rank), 126 * contain at least one value and contain elements that exactly match the 127 * expected type. (We could relax this last requirement so that, for 128 * example, you could pass an array of Int32 that could be converted to an 129 * array of Double but that seems like overkill). 130 * 131 * @param value The value. 132 * @return New value. 133 * @throws Exception the exception 134 */ 135 public Object changeType(Object value) throws Exception { 136 if (this.getIsArray()) { 137 this.validateValueAsArray(value); 138 return value; 139 } else if (value.getClass() == this.getType()) { 140 return value; 141 } else { 142 try { 143 if (this.getType().isInstance(Integer.valueOf(0))) { 144 Object o = null; 145 o = Integer.parseInt(value + ""); 146 return o; 147 } else if (this.getType().isInstance(new Date())) { 148 DateFormat df = new SimpleDateFormat( 149 "yyyy-MM-dd'T'HH:mm:ss'Z'"); 150 return df.parse(value + ""); 151 } else if (this.getType().isInstance(Boolean.valueOf(false))) { 152 Object o = null; 153 o = Boolean.parseBoolean(value + ""); 154 return o; 155 } else if (this.getType().isInstance(String.class)) { 156 return value; 157 } 158 return null; 159 } catch (ClassCastException ex) { 160 throw new ArgumentException(String.format( 161 "The value '%s' of type %s can't be converted to a value of type %s.", "%s", "%s" 162 , this.getType()), ex); 163 } 164 } 165 } 166 167 /** 168 * Converts a string to value consistent with type. 169 * <p/> 170 * For array types, this method is called for each array element. 171 * 172 * @param stringValue String to convert to a value. 173 * @return value 174 * @throws ServiceXmlDeserializationException the service xml deserialization exception 175 * @throws FormatException the format exception 176 */ 177 public Object convertToValue(String stringValue) 178 throws ServiceXmlDeserializationException, FormatException { 179 try { 180 return this.getParse().func(stringValue); 181 } catch (ClassCastException ex) { 182 throw new ServiceXmlDeserializationException(String 183 .format("The value '%s' couldn't be converted to type %s.", stringValue, this 184 .getType()), ex); 185 } catch (NumberFormatException ex) { 186 throw new ServiceXmlDeserializationException(String 187 .format("The value '%s' couldn't be converted to type %s.", stringValue, this.getType()), ex); 188 } 189 190 } 191 192 /** 193 * Converts a string to value consistent with type (or uses the default value if the string is null or empty). 194 * 195 * @param stringValue to convert to a value. 196 * @return Value. 197 * @throws FormatException 198 * @throws ServiceXmlDeserializationException 199 */ 200 public Object convertToValueOrDefault(final String stringValue) 201 throws ServiceXmlDeserializationException, FormatException { 202 return (StringUtils.isEmpty(stringValue)) 203 ? getDefaultValue() : convertToValue(stringValue); 204 } 205 206 /** 207 * Validates array value. 208 * 209 * @param value the value 210 * @throws ArgumentException the argument exception 211 * @throws ArgumentNullException the argument exception 212 */ 213 private void validateValueAsArray(Object value) throws ArgumentException, ArgumentNullException { 214 if (value == null) { 215 throw new ArgumentNullException("value"); 216 } 217 218 if (value instanceof ArrayList) { 219 ArrayList<?> arrayList = (ArrayList<?>) value; 220 if (arrayList.isEmpty()) { 221 throw new ArgumentException("The Array value must have at least one element."); 222 } 223 224 if (arrayList.get(0).getClass() != this.getType()) { 225 throw new ArgumentException(String.format("Type %s can't be used as an array of type %s.", value.getClass(), 226 this.getType())); 227 } 228 } 229 } 230 231 /** 232 * Gets the dim. If `array' is an array object returns its dimensions; 233 * otherwise returns 0 234 * 235 * @param array the array 236 * @return the dim 237 */ 238 public static int getDim(Object array) { 239 int dim = 0; 240 Class<?> cls = array.getClass(); 241 while (cls.isArray()) { 242 dim++; 243 cls = cls.getComponentType(); 244 } 245 return dim; 246 } 247 248 /** 249 * Gets the type. 250 * 251 * @return the type 252 */ 253 254 public Class<?> getType() { 255 return this.type; 256 } 257 258 /** 259 * Sets the type. 260 * 261 * @param cls the new type 262 */ 263 public void setType(Class<?> cls) { 264 type = cls; 265 } 266 267 /** 268 * Gets a value indicating whether this instance is array. 269 * 270 * @return the checks if is array 271 */ 272 public boolean getIsArray() { 273 return isArray; 274 275 } 276 277 /** 278 * Sets the checks if is array. 279 * 280 * @param value the new checks if is array 281 */ 282 protected void setIsArray(boolean value) { 283 isArray = value; 284 } 285 286 /** 287 * Gets the string to object converter. For array types, this method is 288 * called for each array element. 289 * 290 * @return the convert to string 291 */ 292 protected IFunction<Object, String> getConvertToString() { 293 return convertToString; 294 } 295 296 /** 297 * Sets the string to object converter. 298 * 299 * @param value the value 300 */ 301 protected void setConvertToString(IFunction<Object, String> value) { 302 convertToString = value; 303 } 304 305 /** 306 * Gets the string parser. For array types, this method is called for each 307 * array element. 308 * 309 * @return the parses the 310 */ 311 protected IFunction<String, Object> getParse() { 312 return parse; 313 } 314 315 /** 316 * Sets the string parser. 317 * 318 * @param value the value 319 */ 320 protected void setParse(IFunction<String, Object> value) { 321 parse = value; 322 } 323 324 /** 325 * Gets the default value for the type. 326 * 327 * @return Type 328 */ 329 protected Object getDefaultValue() { 330 return defaultValueMap.getMember().get(this.type); 331 } 332}