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
024 package microsoft.exchange.webservices.data.misc;
025
026 import microsoft.exchange.webservices.data.core.EwsUtilities;
027 import microsoft.exchange.webservices.data.core.ILazyMember;
028 import microsoft.exchange.webservices.data.core.LazyMember;
029 import microsoft.exchange.webservices.data.core.exception.misc.ArgumentException;
030 import microsoft.exchange.webservices.data.core.exception.misc.ArgumentNullException;
031 import microsoft.exchange.webservices.data.core.exception.misc.FormatException;
032 import microsoft.exchange.webservices.data.core.exception.service.local.ServiceXmlDeserializationException;
033 import org.apache.commons.lang3.StringUtils;
034 import org.apache.commons.logging.Log;
035 import org.apache.commons.logging.LogFactory;
036
037 import java.text.DateFormat;
038 import java.text.ParseException;
039 import java.text.SimpleDateFormat;
040 import java.util.ArrayList;
041 import java.util.Date;
042 import java.util.HashMap;
043 import java.util.Map;
044 import java.util.UUID;
045
046 /**
047 * Represents an entry in the MapiTypeConverter map.
048 */
049 public 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 }