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    }