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}