001/*
002 * Copyright 2009-2019 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2009-2019 Ping Identity Corporation
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021package com.unboundid.ldap.sdk.persist;
022
023
024
025import java.io.ByteArrayInputStream;
026import java.io.ByteArrayOutputStream;
027import java.io.ObjectInputStream;
028import java.io.ObjectOutputStream;
029import java.io.Serializable;
030import java.lang.reflect.Array;
031import java.lang.reflect.Field;
032import java.lang.reflect.InvocationTargetException;
033import java.lang.reflect.Method;
034import java.lang.reflect.Type;
035import java.math.BigDecimal;
036import java.math.BigInteger;
037import java.net.URI;
038import java.net.URL;
039import java.util.ArrayList;
040import java.util.Collection;
041import java.util.Date;
042import java.util.HashSet;
043import java.util.LinkedHashSet;
044import java.util.LinkedList;
045import java.util.List;
046import java.util.Set;
047import java.util.TreeSet;
048import java.util.UUID;
049import java.util.concurrent.CopyOnWriteArrayList;
050import java.util.concurrent.CopyOnWriteArraySet;
051import java.util.concurrent.atomic.AtomicInteger;
052import java.util.concurrent.atomic.AtomicLong;
053import java.util.concurrent.atomic.AtomicReference;
054
055import com.unboundid.asn1.ASN1OctetString;
056import com.unboundid.ldap.matchingrules.BooleanMatchingRule;
057import com.unboundid.ldap.matchingrules.CaseIgnoreStringMatchingRule;
058import com.unboundid.ldap.matchingrules.GeneralizedTimeMatchingRule;
059import com.unboundid.ldap.matchingrules.MatchingRule;
060import com.unboundid.ldap.matchingrules.OctetStringMatchingRule;
061import com.unboundid.ldap.sdk.Attribute;
062import com.unboundid.ldap.sdk.DN;
063import com.unboundid.ldap.sdk.Filter;
064import com.unboundid.ldap.sdk.LDAPURL;
065import com.unboundid.ldap.sdk.RDN;
066import com.unboundid.ldap.sdk.LDAPException;
067import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition;
068import com.unboundid.ldap.sdk.schema.AttributeUsage;
069import com.unboundid.util.Debug;
070import com.unboundid.util.NotMutable;
071import com.unboundid.util.StaticUtils;
072import com.unboundid.util.ThreadSafety;
073import com.unboundid.util.ThreadSafetyLevel;
074
075import static com.unboundid.ldap.sdk.persist.PersistMessages.*;
076
077
078
079/**
080 * This class provides the default implementation of an {@link ObjectEncoder}
081 * object that will be used when encoding and decoding fields to be written to
082 * or read from an LDAP directory server.
083 * <BR><BR>
084 * The following basic types will be supported, with the following encodings:
085 * <UL>
086 *   <LI>Any kind of enumeration -- Encoded using the name of the enum
087 *       value</LI>
088 *   <LI>{@code java.util.concurrent.atomic.AtomicInteger} -- Encoded using the
089 *       string representation of the value</LI>
090 *   <LI>{@code java.util.concurrent.atomic.AtomicLong} -- Encoded using the
091 *       string representation of the value</LI>
092 *   <LI>{@code java.math.BigDecimal} -- Encoded using the string representation
093 *       of the value</LI>
094 *   <LI>{@code java.math.BigInteger} -- Encoded using the string representation
095 *       of the value</LI>
096 *   <LI>{@code boolean} -- Encoded as either "TRUE" or "FALSE"</LI>
097 *   <LI>{@code java.lang.Boolean} -- Encoded as either "TRUE" or "FALSE"</LI>
098 *   <LI>{@code byte[]} -- Encoded as the raw bytes contained in the array</LI>
099 *   <LI>{@code char[]} -- Encoded as a string containing the characters in the
100 *       array</LI>
101 *   <LI>{@code java.util.Date} -- Encoded using the generalized time
102 *       syntax</LI>
103 *   <LI>{@code com.unboundid.ldap.sdk.DN} -- Encoded using the string
104 *       representation of the value</LI>
105 *   <LI>{@code double} -- Encoded using the string representation of the
106 *       value</LI>
107 *   <LI>{@code java.lang.Double} -- Encoded using the string representation of
108 *       the value</LI>
109 *   <LI>{@code com.unboundid.ldap.sdk.Filter} -- Encoded using the string
110 *       representation of the value</LI>
111 *   <LI>{@code float} -- Encoded using the string representation of the
112 *       value</LI>
113 *   <LI>{@code java.lang.Float} -- Encoded using the string representation of
114 *       the value</LI>
115 *   <LI>{@code int} -- Encoded using the string representation of the
116 *       value</LI>
117 *   <LI>{@code java.lang.Integer} -- Encoded using the string representation of
118 *       the value</LI>
119 *   <LI>{@code com.unboundid.ldap.sdk.LDAPURL} -- Encoded using the string
120 *       representation of the value</LI>
121 *   <LI>{@code long} -- Encoded using the string representation of the
122 *       value</LI>
123 *   <LI>{@code java.lang.Long} -- Encoded using the string representation of
124 *       the value</LI>
125 *   <LI>{@code com.unboundid.ldap.sdk.RDN} -- Encoded using the string
126 *       representation of the value</LI>
127 *   <LI>{@code short} -- Encoded using the string representation of the
128 *       value</LI>
129 *   <LI>{@code java.lang.Short} -- Encoded using the string representation of
130 *       the value</LI>
131 *   <LI>{@code java.lang.String} -- Encoded using the value</LI>
132 *   <LI>{@code java.lang.StringBuffer} -- Encoded using the string
133 *       representation of the value</LI>
134 *   <LI>{@code java.lang.StringBuilder} -- Encoded using the string
135 *       representation of the value</LI>
136 *   <LI>{@code java.net.URI} -- Encoded using the string representation of the
137 *       value.</LI>
138 *   <LI>{@code java.net.URL} -- Encoded using the string representation of the
139 *       value.</LI>
140 *   <LI>{@code java.util.UUID} -- Encoded using the string representation of
141 *       the value</LI>
142 * </UL>
143 * Serializable objects are also supported, in which case the raw bytes that
144 * comprise the serialized representation will be used.  This may be
145 * undesirable, because the value may only be interpretable by Java-based
146 * clients.  If you wish to better control the encoding for serialized objects,
147 * have them implement custom {@code writeObject}, {@code readObject}, and
148 * {@code readObjectNoData} methods that use the desired encoding.  Alternately,
149 * you may create a custom {@link ObjectEncoder} implementation for that object
150 * type, or use getter/setter methods that convert between string/byte[]
151 * representations and the desired object types.
152 * <BR><BR>
153 * In addition, arrays of all of the above types are also supported, in which
154 * case each element of the array will be a separate value in the corresponding
155 * LDAP attribute.  Lists (including {@code ArrayList}, {@code LinkedList}, and
156 * {@code CopyOnWriteArrayList}) and sets (including {@code HashSet},
157 * {@code LinkedHashSet}, {@code TreeSet}, and {@code CopyOnWriteArraySet}) of
158 * the above types are also supported.
159 * <BR><BR>
160 * Note that you should be careful when using primitive types, since they cannot
161 * be unassigned and therefore will always have a value.  When using an LDAP
162 * entry to initialize an object any fields with primitive types which are
163 * associated with LDAP attributes not present in the entry will have the
164 * default value assigned to them in the zero-argument constructor, or will have
165 * the JVM-supplied default value if no value was assigned to it in the
166 * constructor.  If the associated object is converted back to an LDAP entry,
167 * then those fields will be included in the entry that is generated, even if
168 * they were not present in the original entry.  To avoid this problem, you can
169 * use the object types rather than the primitive types (e.g.,
170 * {@code java.lang.Boolean} instead of the {@code boolean} primitive), in which
171 * case any fields associated with attributes that are not present in the entry
172 * being de-serialized will be explicitly set to {@code null}.
173 */
174@NotMutable()
175@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
176public final class DefaultObjectEncoder
177       extends ObjectEncoder
178{
179  /**
180   * The serial version UID for this serializable class.
181   */
182  private static final long serialVersionUID = -4566874784628920022L;
183
184
185
186  /**
187   * Creates a new instance of this encoder.
188   */
189  public DefaultObjectEncoder()
190  {
191    super();
192  }
193
194
195
196  /**
197   * {@inheritDoc}
198   */
199  @Override()
200  public boolean supportsType(final Type t)
201  {
202    final TypeInfo typeInfo = new TypeInfo(t);
203    if (! typeInfo.isSupported())
204    {
205      return false;
206    }
207
208    final Class<?> baseClass = typeInfo.getBaseClass();
209
210    if (supportsTypeInternal(baseClass))
211    {
212      return true;
213    }
214
215    final Class<?> componentType = typeInfo.getComponentType();
216    if (componentType == null)
217    {
218      return false;
219    }
220
221    if (typeInfo.isArray())
222    {
223      return supportsTypeInternal(componentType);
224    }
225
226    if (typeInfo.isList())
227    {
228      return (isSupportedListType(baseClass) &&
229           supportsTypeInternal(componentType));
230    }
231
232    if (typeInfo.isSet())
233    {
234      return (isSupportedSetType(baseClass) &&
235           supportsTypeInternal(componentType));
236    }
237
238    return false;
239  }
240
241
242
243  /**
244   * Indicates whether this object encoder supports objects of the specified
245   * type.
246   *
247   * @param  c  The object type class for which to make the determination.
248   *
249   * @return  {@code true} if this object supports objects of the specified
250   *          type, or {@code false} if not.
251   */
252  private static boolean supportsTypeInternal(final Class<?> c)
253  {
254    if (c.equals(AtomicInteger.class) ||
255        c.equals(AtomicLong.class) ||
256        c.equals(BigDecimal.class) ||
257        c.equals(BigInteger.class) ||
258        c.equals(Boolean.class) ||
259        c.equals(Boolean.TYPE) ||
260        c.equals(Date.class) ||
261        c.equals(DN.class) ||
262        c.equals(Double.class) ||
263        c.equals(Double.TYPE) ||
264        c.equals(Filter.class) ||
265        c.equals(Float.class) ||
266        c.equals(Float.TYPE) ||
267        c.equals(Integer.class) ||
268        c.equals(Integer.TYPE) ||
269        c.equals(LDAPURL.class) ||
270        c.equals(Long.class) ||
271        c.equals(Long.TYPE) ||
272        c.equals(RDN.class) ||
273        c.equals(Short.class) ||
274        c.equals(Short.TYPE) ||
275        c.equals(String.class) ||
276        c.equals(StringBuffer.class) ||
277        c.equals(StringBuilder.class) ||
278        c.equals(URI.class) ||
279        c.equals(URL.class) ||
280        c.equals(UUID.class))
281    {
282      return true;
283    }
284
285    if (c.isArray())
286    {
287      final Class<?> t = c.getComponentType();
288      if (t.equals(Byte.TYPE) ||
289          t.equals(Character.TYPE))
290      {
291        return true;
292      }
293    }
294
295    if (c.isEnum())
296    {
297      return true;
298    }
299
300    if (Serializable.class.isAssignableFrom(c))
301    {
302      return (! (c.isArray() || Collection.class.isAssignableFrom(c)));
303    }
304
305    return false;
306  }
307
308
309
310  /**
311   * Indicates whether the provided type is a supported list type.
312   *
313   * @param  t  The type for which to make the determination.
314   *
315   * @return  {@code true} if the provided type is a supported list type, or
316   *          or {@code false}.
317   */
318  private static boolean isSupportedListType(final Class<?> t)
319  {
320    return (t.equals(List.class) ||
321            t.equals(ArrayList.class) ||
322            t.equals(LinkedList.class) ||
323            t.equals(CopyOnWriteArrayList.class));
324  }
325
326
327
328  /**
329   * Creates a new list of the specified type.
330   *
331   * @param  t     The type of list to create.
332   * @param  size  The number of values that will be included in the list.
333   *
334   * @return  The created list, or {@code null} if it is not a supported list
335   *          type.
336   */
337  @SuppressWarnings("rawtypes")
338  private static List<?> createList(final Class<?> t, final int size)
339  {
340    if (t.equals(List.class) || t.equals(ArrayList.class))
341    {
342      return new ArrayList(size);
343    }
344    else if (t.equals(LinkedList.class))
345    {
346      return new LinkedList();
347    }
348    else if (t.equals(CopyOnWriteArrayList.class))
349    {
350      return new CopyOnWriteArrayList();
351    }
352
353    return null;
354  }
355
356
357
358  /**
359   * Indicates whether the provided type is a supported set type.
360   *
361   * @param  t  The type for which to make the determination.
362   *
363   * @return  {@code true} if the provided type is a supported set type, or
364   *          or {@code false}.
365   */
366  private static boolean isSupportedSetType(final Class<?> t)
367  {
368    return (t.equals(Set.class) ||
369            t.equals(HashSet.class) ||
370            t.equals(LinkedHashSet.class) ||
371            t.equals(TreeSet.class) ||
372            t.equals(CopyOnWriteArraySet.class));
373  }
374
375
376
377  /**
378   * Creates a new set of the specified type.
379   *
380   * @param  t     The type of set to create.
381   * @param  size  The number of values that will be included in the set.
382   *
383   * @return  The created list, or {@code null} if it is not a supported set
384   *          type.
385   */
386  @SuppressWarnings("rawtypes")
387  private static Set<?> createSet(final Class<?> t, final int size)
388  {
389    if (t.equals(Set.class) || t.equals(LinkedHashSet.class))
390    {
391      return new LinkedHashSet(StaticUtils.computeMapCapacity(size));
392    }
393    else if (t.equals(HashSet.class))
394    {
395      return new HashSet(StaticUtils.computeMapCapacity(size));
396    }
397    else if (t.equals(TreeSet.class))
398    {
399      return new TreeSet();
400    }
401    else if (t.equals(CopyOnWriteArraySet.class))
402    {
403      return new CopyOnWriteArraySet();
404    }
405
406    return null;
407  }
408
409
410
411  /**
412   * {@inheritDoc}
413   */
414  @Override()
415  public AttributeTypeDefinition constructAttributeType(final Field f,
416                                      final OIDAllocator a)
417         throws LDAPPersistException
418  {
419    final LDAPField at = f.getAnnotation(LDAPField.class);
420
421    final String attrName;
422    if (at.attribute().isEmpty())
423    {
424      attrName = f.getName();
425    }
426    else
427    {
428      attrName = at.attribute();
429    }
430
431    final String oid = a.allocateAttributeTypeOID(attrName);
432
433    final TypeInfo typeInfo = new TypeInfo(f.getGenericType());
434    if (! typeInfo.isSupported())
435    {
436      throw new LDAPPersistException(ERR_DEFAULT_ENCODER_UNSUPPORTED_TYPE.get(
437           String.valueOf(typeInfo.getType())));
438    }
439
440    final boolean isSingleValued = (! supportsMultipleValues(typeInfo));
441
442    final String syntaxOID;
443    if (isSingleValued)
444    {
445      syntaxOID = getSyntaxOID(typeInfo.getBaseClass());
446    }
447    else
448    {
449      syntaxOID = getSyntaxOID(typeInfo.getComponentType());
450    }
451
452    final MatchingRule mr = MatchingRule.selectMatchingRuleForSyntax(syntaxOID);
453    return new AttributeTypeDefinition(oid, new String[] { attrName }, null,
454         false, null, mr.getEqualityMatchingRuleNameOrOID(),
455         mr.getOrderingMatchingRuleNameOrOID(),
456         mr.getSubstringMatchingRuleNameOrOID(), syntaxOID, isSingleValued,
457         false, false, AttributeUsage.USER_APPLICATIONS, null);
458  }
459
460
461
462  /**
463   * {@inheritDoc}
464   */
465  @Override()
466  public AttributeTypeDefinition constructAttributeType(final Method m,
467                                      final OIDAllocator a)
468         throws LDAPPersistException
469  {
470    final LDAPGetter at = m.getAnnotation(LDAPGetter.class);
471
472    final String attrName;
473    if (at.attribute().isEmpty())
474    {
475      attrName = StaticUtils.toInitialLowerCase(m.getName().substring(3));
476    }
477    else
478    {
479      attrName = at.attribute();
480    }
481
482    final String oid = a.allocateAttributeTypeOID(attrName);
483
484    final TypeInfo typeInfo = new TypeInfo(m.getGenericReturnType());
485    if (! typeInfo.isSupported())
486    {
487      throw new LDAPPersistException(ERR_DEFAULT_ENCODER_UNSUPPORTED_TYPE.get(
488           String.valueOf(typeInfo.getType())));
489    }
490
491    final boolean isSingleValued = (! supportsMultipleValues(typeInfo));
492
493    final String syntaxOID;
494    if (isSingleValued)
495    {
496      syntaxOID = getSyntaxOID(typeInfo.getBaseClass());
497    }
498    else
499    {
500      syntaxOID = getSyntaxOID(typeInfo.getComponentType());
501    }
502
503    return new AttributeTypeDefinition(oid, new String[] { attrName }, null,
504         false, null, null, null, null, syntaxOID, isSingleValued, false, false,
505         AttributeUsage.USER_APPLICATIONS, null);
506  }
507
508
509
510  /**
511   * Retrieves the syntax that should be used for the specified object type.
512   *
513   * @param  t  The type for which to make the determination.
514   *
515   * @return  The syntax that should be used for the specified object type, or
516   *          {@code null} if it cannot be determined.
517   */
518  private static String getSyntaxOID(final Class<?> t)
519  {
520    if (t.equals(BigDecimal.class) ||
521        t.equals(Double.class) ||
522        t.equals(Double.TYPE) ||
523        t.equals(Float.class) ||
524        t.equals(Float.TYPE) ||
525        t.equals(String.class) ||
526        t.equals(StringBuffer.class) ||
527        t.equals(StringBuilder.class) ||
528        t.equals(URI.class) ||
529        t.equals(URL.class) ||
530        t.equals(Filter.class) ||
531        t.equals(LDAPURL.class))
532    {
533      return "1.3.6.1.4.1.1466.115.121.1.15";
534    }
535    else if (t.equals(AtomicInteger.class) ||
536        t.equals(AtomicLong.class) ||
537        t.equals(BigInteger.class) ||
538        t.equals(Integer.class) ||
539        t.equals(Integer.TYPE) ||
540        t.equals(Long.class) ||
541        t.equals(Long.TYPE) ||
542        t.equals(Short.class) ||
543        t.equals(Short.TYPE))
544    {
545      return "1.3.6.1.4.1.1466.115.121.1.27";
546    }
547    else if (t.equals(UUID.class))
548    {
549      // Although "1.3.6.1.1.16.1" (which is the UUID syntax as defined in RFC
550      // 4530) might be more correct, some servers may not support this syntax
551      // since it is relatively new, so we'll fall back on the more
552      // widely-supported directory string syntax.
553      return "1.3.6.1.4.1.1466.115.121.1.15";
554    }
555    else if (t.equals(DN.class) ||
556             t.equals(RDN.class))
557    {
558      return "1.3.6.1.4.1.1466.115.121.1.12";
559    }
560    else if (t.equals(Boolean.class) ||
561             t.equals(Boolean.TYPE))
562    {
563      return "1.3.6.1.4.1.1466.115.121.1.7";
564    }
565    else if (t.equals(Date.class))
566    {
567      return "1.3.6.1.4.1.1466.115.121.1.24";
568    }
569    else if (t.isArray())
570    {
571      final Class<?> ct = t.getComponentType();
572      if (ct.equals(Byte.TYPE))
573      {
574        return "1.3.6.1.4.1.1466.115.121.1.40";
575      }
576      else if (ct.equals(Character.TYPE))
577      {
578        return "1.3.6.1.4.1.1466.115.121.1.15";
579      }
580    }
581    else if (t.isEnum())
582    {
583      return "1.3.6.1.4.1.1466.115.121.1.15";
584    }
585    else if (Serializable.class.isAssignableFrom(t))
586    {
587      return "1.3.6.1.4.1.1466.115.121.1.40";
588    }
589
590    return null;
591  }
592
593
594
595  /**
596   * {@inheritDoc}
597   */
598  @Override()
599  public boolean supportsMultipleValues(final Field field)
600  {
601    return supportsMultipleValues(new TypeInfo(field.getGenericType()));
602  }
603
604
605
606  /**
607   * {@inheritDoc}
608   */
609  @Override()
610  public boolean supportsMultipleValues(final Method method)
611  {
612    final Type[] paramTypes = method.getGenericParameterTypes();
613    if (paramTypes.length != 1)
614    {
615      return false;
616    }
617
618    return supportsMultipleValues(new TypeInfo(paramTypes[0]));
619  }
620
621
622
623  /**
624   * Indicates whether the provided object type supports multiple values.
625   *
626   * @param  t  The type for which to make the determination.
627   *
628   * @return  {@code true} if the provided object type supports multiple values,
629   *          or {@code false} if not.
630   */
631  private static boolean supportsMultipleValues(final TypeInfo t)
632  {
633    if (t.isArray())
634    {
635      final Class<?> componentType = t.getComponentType();
636      return (! (componentType.equals(Byte.TYPE) ||
637                 componentType.equals(Character.TYPE)));
638    }
639    else
640    {
641      return t.isMultiValued();
642    }
643  }
644
645
646
647  /**
648   * {@inheritDoc}
649   */
650  @Override()
651  public Attribute encodeFieldValue(final Field field, final Object value,
652                                    final String name)
653         throws LDAPPersistException
654  {
655    return encodeValue(field.getGenericType(), value, name);
656  }
657
658
659
660  /**
661   * {@inheritDoc}
662   */
663  @Override()
664  public Attribute encodeMethodValue(final Method method, final Object value,
665                                     final String name)
666         throws LDAPPersistException
667  {
668    return encodeValue(method.getGenericReturnType(), value, name);
669  }
670
671
672
673  /**
674   * Encodes the provided value to an LDAP attribute.
675   *
676   * @param  type   The type for the provided value.
677   * @param  value  The value for the field in the object to be encoded.
678   * @param  name   The name to use for the constructed attribute.
679   *
680   * @return  The attribute containing the encoded representation of the
681   *          provided field.
682   *
683   * @throws  LDAPPersistException  If a problem occurs while attempting to
684   *                                construct an attribute for the field.
685   */
686  private static Attribute encodeValue(final Type type, final Object value,
687                                       final String name)
688         throws LDAPPersistException
689  {
690    final TypeInfo typeInfo = new TypeInfo(type);
691
692    final Class<?> c = typeInfo.getBaseClass();
693    if (c.equals(AtomicInteger.class) ||
694        c.equals(AtomicLong.class) ||
695        c.equals(BigDecimal.class) ||
696        c.equals(BigInteger.class) ||
697        c.equals(Double.class) ||
698        c.equals(Double.TYPE) ||
699        c.equals(Float.class) ||
700        c.equals(Float.TYPE) ||
701        c.equals(Integer.class) ||
702        c.equals(Integer.TYPE) ||
703        c.equals(Long.class) ||
704        c.equals(Long.TYPE) ||
705        c.equals(Short.class) ||
706        c.equals(Short.TYPE) ||
707        c.equals(String.class) ||
708        c.equals(StringBuffer.class) ||
709        c.equals(StringBuilder.class) ||
710        c.equals(UUID.class) ||
711        c.equals(DN.class) ||
712        c.equals(Filter.class) ||
713        c.equals(LDAPURL.class) ||
714        c.equals(RDN.class))
715    {
716      final String syntaxOID = getSyntaxOID(c);
717      final MatchingRule matchingRule =
718           MatchingRule.selectMatchingRuleForSyntax(syntaxOID);
719      return new Attribute(name, matchingRule, String.valueOf(value));
720    }
721    else if (value instanceof URI)
722    {
723      final URI uri = (URI) value;
724      return new Attribute(name, uri.toASCIIString());
725    }
726    else if (value instanceof URL)
727    {
728      final URL url = (URL) value;
729      return new Attribute(name, url.toExternalForm());
730    }
731    else if (value instanceof byte[])
732    {
733      return new Attribute(name, OctetStringMatchingRule.getInstance(),
734           (byte[]) value);
735    }
736    else if (value instanceof char[])
737    {
738      return new Attribute(name, new String((char[]) value));
739    }
740    else if (c.equals(Boolean.class) || c.equals(Boolean.TYPE))
741    {
742      final Boolean b = (Boolean) value;
743      final MatchingRule matchingRule = BooleanMatchingRule.getInstance();
744      if (b)
745      {
746        return new Attribute(name, matchingRule, "TRUE");
747      }
748      else
749      {
750        return new Attribute(name, matchingRule, "FALSE");
751      }
752    }
753    else if (c.equals(Date.class))
754    {
755      final Date d = (Date) value;
756      return new Attribute(name, GeneralizedTimeMatchingRule.getInstance(),
757           StaticUtils.encodeGeneralizedTime(d));
758    }
759    else if (typeInfo.isArray())
760    {
761      return encodeArray(typeInfo.getComponentType(), value, name);
762    }
763    else if (typeInfo.isEnum())
764    {
765      final Enum<?> e = (Enum<?>) value;
766      return new Attribute(name, e.name());
767    }
768    else if (Collection.class.isAssignableFrom(c))
769    {
770      return encodeCollection(typeInfo.getComponentType(),
771           (Collection<?>) value, name);
772    }
773    else if (Serializable.class.isAssignableFrom(c))
774    {
775      try
776      {
777        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
778        final ObjectOutputStream oos = new ObjectOutputStream(baos);
779        oos.writeObject(value);
780        oos.close();
781        return new Attribute(name, OctetStringMatchingRule.getInstance(),
782             baos.toByteArray());
783      }
784      catch (final Exception e)
785      {
786        Debug.debugException(e);
787        throw new LDAPPersistException(
788             ERR_DEFAULT_ENCODER_CANNOT_SERIALIZE.get(name,
789                  StaticUtils.getExceptionMessage(e)),
790             e);
791      }
792    }
793
794    throw new LDAPPersistException(ERR_DEFAULT_ENCODER_UNSUPPORTED_TYPE.get(
795         String.valueOf(type)));
796  }
797
798
799
800  /**
801   * Encodes the contents of the provided array object.
802   *
803   * @param  arrayType      The component type of the array.
804   * @param  arrayObject    The array object to process.
805   * @param  attributeName  The name to use for the attribute to create.
806   *
807   * @return  The attribute containing the encoded array contents.
808   *
809   * @throws  LDAPPersistException  If a problem occurs while trying to create
810   *                                the attribute.
811   */
812  private static Attribute encodeArray(final Class<?> arrayType,
813                                       final Object arrayObject,
814                                       final String attributeName)
815          throws LDAPPersistException
816  {
817    final ASN1OctetString[] values =
818         new ASN1OctetString[Array.getLength(arrayObject)];
819    final AtomicReference<MatchingRule> matchingRule = new AtomicReference<>();
820    for (int i=0; i < values.length; i++)
821    {
822      final Object o = Array.get(arrayObject, i);
823      if (arrayType.equals(AtomicInteger.class) ||
824          arrayType.equals(AtomicLong.class) ||
825          arrayType.equals(BigDecimal.class) ||
826          arrayType.equals(BigInteger.class) ||
827          arrayType.equals(Double.class) ||
828          arrayType.equals(Double.TYPE) ||
829          arrayType.equals(Float.class) ||
830          arrayType.equals(Float.TYPE) ||
831          arrayType.equals(Integer.class) ||
832          arrayType.equals(Integer.TYPE) ||
833          arrayType.equals(Long.class) ||
834          arrayType.equals(Long.TYPE) ||
835          arrayType.equals(Short.class) ||
836          arrayType.equals(Short.TYPE) ||
837          arrayType.equals(String.class) ||
838          arrayType.equals(StringBuffer.class) ||
839          arrayType.equals(StringBuilder.class) ||
840          arrayType.equals(UUID.class) ||
841          arrayType.equals(DN.class) ||
842          arrayType.equals(Filter.class) ||
843          arrayType.equals(LDAPURL.class) ||
844          arrayType.equals(RDN.class))
845      {
846        if (matchingRule.get() == null)
847        {
848          final String syntaxOID = getSyntaxOID(arrayType);
849          matchingRule.set(MatchingRule.selectMatchingRuleForSyntax(syntaxOID));
850        }
851
852        values[i] = new ASN1OctetString(String.valueOf(o));
853      }
854      else if (arrayType.equals(URI.class))
855      {
856        final URI uri = (URI) o;
857        values[i] = new ASN1OctetString(uri.toASCIIString());
858      }
859      else if (arrayType.equals(URL.class))
860      {
861        final URL url = (URL) o;
862        values[i] = new ASN1OctetString(url.toExternalForm());
863      }
864      else if (o instanceof byte[])
865      {
866        matchingRule.compareAndSet(null, OctetStringMatchingRule.getInstance());
867        values[i] = new ASN1OctetString((byte[]) o);
868      }
869      else if (o instanceof char[])
870      {
871        values[i] = new ASN1OctetString(new String((char[]) o));
872      }
873      else if (arrayType.equals(Boolean.class) ||
874               arrayType.equals(Boolean.TYPE))
875      {
876        matchingRule.compareAndSet(null, BooleanMatchingRule.getInstance());
877
878        final Boolean b = (Boolean) o;
879        if (b)
880        {
881          values[i] = new ASN1OctetString("TRUE");
882        }
883        else
884        {
885          values[i] = new ASN1OctetString("FALSE");
886        }
887      }
888      else if (arrayType.equals(Date.class))
889      {
890        matchingRule.compareAndSet(null,
891             GeneralizedTimeMatchingRule.getInstance());
892
893        final Date d = (Date) o;
894        values[i] = new ASN1OctetString(StaticUtils.encodeGeneralizedTime(d));
895      }
896      else if (arrayType.isEnum())
897      {
898        final Enum<?> e = (Enum<?>) o;
899        values[i] = new ASN1OctetString(e.name());
900      }
901      else if (Serializable.class.isAssignableFrom(arrayType))
902      {
903        matchingRule.compareAndSet(null, OctetStringMatchingRule.getInstance());
904
905        try
906        {
907          final ByteArrayOutputStream baos = new ByteArrayOutputStream();
908          final ObjectOutputStream oos = new ObjectOutputStream(baos);
909          oos.writeObject(o);
910          oos.close();
911          values[i] = new ASN1OctetString(baos.toByteArray());
912        }
913        catch (final Exception e)
914        {
915          Debug.debugException(e);
916          throw new LDAPPersistException(
917               ERR_DEFAULT_ENCODER_CANNOT_SERIALIZE.get(attributeName,
918                    StaticUtils.getExceptionMessage(e)),
919               e);
920        }
921      }
922      else
923      {
924        throw new LDAPPersistException(ERR_DEFAULT_ENCODER_UNSUPPORTED_TYPE.get(
925             arrayType.getName()));
926      }
927    }
928
929    matchingRule.compareAndSet(null,
930         CaseIgnoreStringMatchingRule.getInstance());
931    return new Attribute(attributeName, matchingRule.get(), values);
932  }
933
934
935
936  /**
937   * Encodes the contents of the provided collection.
938   *
939   * @param  genericType    The generic type of the collection.
940   * @param  collection     The collection to process.
941   * @param  attributeName  The name to use for the attribute to create.
942   *
943   * @return  The attribute containing the encoded collection contents.
944   *
945   * @throws  LDAPPersistException  If a problem occurs while trying to create
946   *                                the attribute.
947   */
948  private static Attribute encodeCollection(final Class<?> genericType,
949                                            final Collection<?> collection,
950                                            final String attributeName)
951          throws LDAPPersistException
952  {
953    final ASN1OctetString[] values = new ASN1OctetString[collection.size()];
954    final AtomicReference<MatchingRule> matchingRule = new AtomicReference<>();
955
956    int i=0;
957    for (final Object o : collection)
958    {
959      if (genericType.equals(AtomicInteger.class) ||
960          genericType.equals(AtomicLong.class) ||
961          genericType.equals(BigDecimal.class) ||
962          genericType.equals(BigInteger.class) ||
963          genericType.equals(Double.class) ||
964          genericType.equals(Double.TYPE) ||
965          genericType.equals(Float.class) ||
966          genericType.equals(Float.TYPE) ||
967          genericType.equals(Integer.class) ||
968          genericType.equals(Integer.TYPE) ||
969          genericType.equals(Long.class) ||
970          genericType.equals(Long.TYPE) ||
971          genericType.equals(Short.class) ||
972          genericType.equals(Short.TYPE) ||
973          genericType.equals(String.class) ||
974          genericType.equals(StringBuffer.class) ||
975          genericType.equals(StringBuilder.class) ||
976          genericType.equals(UUID.class) ||
977          genericType.equals(DN.class) ||
978          genericType.equals(Filter.class) ||
979          genericType.equals(LDAPURL.class) ||
980          genericType.equals(RDN.class))
981      {
982        if (matchingRule.get() == null)
983        {
984          final String syntaxOID = getSyntaxOID(genericType);
985          matchingRule.set(MatchingRule.selectMatchingRuleForSyntax(syntaxOID));
986        }
987
988        values[i] = new ASN1OctetString(String.valueOf(o));
989      }
990      else if (genericType.equals(URI.class))
991      {
992        final URI uri = (URI) o;
993        values[i] = new ASN1OctetString(uri.toASCIIString());
994      }
995      else if (genericType.equals(URL.class))
996      {
997        final URL url = (URL) o;
998        values[i] = new ASN1OctetString(url.toExternalForm());
999      }
1000      else if (o instanceof byte[])
1001      {
1002        matchingRule.compareAndSet(null, OctetStringMatchingRule.getInstance());
1003        values[i] = new ASN1OctetString((byte[]) o);
1004      }
1005      else if (o instanceof char[])
1006      {
1007        values[i] = new ASN1OctetString(new String((char[]) o));
1008      }
1009      else if (genericType.equals(Boolean.class) ||
1010               genericType.equals(Boolean.TYPE))
1011      {
1012        matchingRule.compareAndSet(null, BooleanMatchingRule.getInstance());
1013
1014        final Boolean b = (Boolean) o;
1015        if (b)
1016        {
1017          values[i] = new ASN1OctetString("TRUE");
1018        }
1019        else
1020        {
1021          values[i] = new ASN1OctetString("FALSE");
1022        }
1023      }
1024      else if (genericType.equals(Date.class))
1025      {
1026        matchingRule.compareAndSet(null,
1027             GeneralizedTimeMatchingRule.getInstance());
1028
1029        final Date d = (Date) o;
1030        values[i] = new ASN1OctetString(StaticUtils.encodeGeneralizedTime(d));
1031      }
1032      else if (genericType.isEnum())
1033      {
1034        final Enum<?> e = (Enum<?>) o;
1035        values[i] = new ASN1OctetString(e.name());
1036      }
1037      else if (Serializable.class.isAssignableFrom(genericType))
1038      {
1039        matchingRule.compareAndSet(null, OctetStringMatchingRule.getInstance());
1040
1041        try
1042        {
1043          final ByteArrayOutputStream baos = new ByteArrayOutputStream();
1044          final ObjectOutputStream oos = new ObjectOutputStream(baos);
1045          oos.writeObject(o);
1046          oos.close();
1047          values[i] = new ASN1OctetString(baos.toByteArray());
1048        }
1049        catch (final Exception e)
1050        {
1051          Debug.debugException(e);
1052          throw new LDAPPersistException(
1053               ERR_DEFAULT_ENCODER_CANNOT_SERIALIZE.get(attributeName,
1054                    StaticUtils.getExceptionMessage(e)),
1055               e);
1056        }
1057      }
1058      else
1059      {
1060        throw new LDAPPersistException(ERR_DEFAULT_ENCODER_UNSUPPORTED_TYPE.get(
1061             genericType.getName()));
1062      }
1063
1064      i++;
1065    }
1066
1067    matchingRule.compareAndSet(null,
1068         CaseIgnoreStringMatchingRule.getInstance());
1069    return new Attribute(attributeName, matchingRule.get(), values);
1070  }
1071
1072
1073
1074  /**
1075   * {@inheritDoc}
1076   */
1077  @Override()
1078  public void decodeField(final Field field, final Object object,
1079                          final Attribute attribute)
1080         throws LDAPPersistException
1081  {
1082    field.setAccessible(true);
1083    final TypeInfo typeInfo = new TypeInfo(field.getGenericType());
1084
1085    try
1086    {
1087      final Class<?> baseClass = typeInfo.getBaseClass();
1088      final Object newValue = getValue(baseClass, attribute, 0);
1089      if (newValue != null)
1090      {
1091        field.set(object, newValue);
1092        return;
1093      }
1094
1095      if (typeInfo.isArray())
1096      {
1097        final Class<?> componentType = typeInfo.getComponentType();
1098        final ASN1OctetString[] values = attribute.getRawValues();
1099        final Object arrayObject =
1100             Array.newInstance(componentType, values.length);
1101        for (int i=0; i < values.length; i++)
1102        {
1103          final Object o = getValue(componentType, attribute, i);
1104          if (o == null)
1105          {
1106            throw new LDAPPersistException(
1107                 ERR_DEFAULT_ENCODER_UNSUPPORTED_TYPE.get(
1108                      componentType.getName()));
1109          }
1110          Array.set(arrayObject, i, o);
1111        }
1112
1113        field.set(object, arrayObject);
1114        return;
1115      }
1116      else if (typeInfo.isList() && isSupportedListType(baseClass))
1117      {
1118        final Class<?> componentType = typeInfo.getComponentType();
1119        if (componentType == null)
1120        {
1121          throw new LDAPPersistException(
1122               ERR_DEFAULT_ENCODER_UNSUPPORTED_TYPE.get(baseClass.getName()));
1123        }
1124
1125        final ASN1OctetString[] values = attribute.getRawValues();
1126        final List<?> l = createList(baseClass, values.length);
1127        for (int i=0; i < values.length; i++)
1128        {
1129          final Object o = getValue(componentType, attribute, i);
1130          if (o == null)
1131          {
1132            throw new LDAPPersistException(
1133                 ERR_DEFAULT_ENCODER_UNSUPPORTED_TYPE.get(
1134                      componentType.getName()));
1135          }
1136
1137          invokeAdd(l, o);
1138        }
1139
1140        field.set(object, l);
1141        return;
1142      }
1143      else if (typeInfo.isSet() && isSupportedSetType(baseClass))
1144      {
1145        final Class<?> componentType = typeInfo.getComponentType();
1146        if (componentType == null)
1147        {
1148          throw new LDAPPersistException(
1149               ERR_DEFAULT_ENCODER_UNSUPPORTED_TYPE.get(baseClass.getName()));
1150        }
1151
1152        final ASN1OctetString[] values = attribute.getRawValues();
1153        final Set<?> l = createSet(baseClass, values.length);
1154        for (int i=0; i < values.length; i++)
1155        {
1156          final Object o = getValue(componentType, attribute, i);
1157          if (o == null)
1158          {
1159            throw new LDAPPersistException(
1160                 ERR_DEFAULT_ENCODER_UNSUPPORTED_TYPE.get(
1161                      componentType.getName()));
1162          }
1163
1164          invokeAdd(l, o);
1165        }
1166
1167        field.set(object, l);
1168        return;
1169      }
1170
1171      throw new LDAPPersistException(ERR_DEFAULT_ENCODER_UNSUPPORTED_TYPE.get(
1172           baseClass.getName()));
1173    }
1174    catch (final LDAPPersistException lpe)
1175    {
1176      Debug.debugException(lpe);
1177      throw lpe;
1178    }
1179    catch (final Exception e)
1180    {
1181      Debug.debugException(e);
1182      throw new LDAPPersistException(StaticUtils.getExceptionMessage(e), e);
1183    }
1184  }
1185
1186
1187
1188  /**
1189   * {@inheritDoc}
1190   */
1191  @Override()
1192  public void invokeSetter(final Method method, final Object object,
1193                           final Attribute attribute)
1194         throws LDAPPersistException
1195  {
1196    final TypeInfo typeInfo =
1197         new TypeInfo(method.getGenericParameterTypes()[0]);
1198    final Class<?> baseClass = typeInfo.getBaseClass();
1199    method.setAccessible(true);
1200
1201    try
1202    {
1203      final Object newValue = getValue(baseClass, attribute, 0);
1204      if (newValue != null)
1205      {
1206        method.invoke(object, newValue);
1207        return;
1208      }
1209
1210      if (typeInfo.isArray())
1211      {
1212        final Class<?> componentType = typeInfo.getComponentType();
1213        final ASN1OctetString[] values = attribute.getRawValues();
1214        final Object arrayObject =
1215             Array.newInstance(componentType, values.length);
1216        for (int i=0; i < values.length; i++)
1217        {
1218          final Object o = getValue(componentType, attribute, i);
1219          if (o == null)
1220          {
1221            throw new LDAPPersistException(
1222                 ERR_DEFAULT_ENCODER_UNSUPPORTED_TYPE.get(
1223                      componentType.getName()));
1224          }
1225          Array.set(arrayObject, i, o);
1226        }
1227
1228        method.invoke(object, arrayObject);
1229        return;
1230      }
1231      else if (typeInfo.isList() && isSupportedListType(baseClass))
1232      {
1233        final Class<?> componentType = typeInfo.getComponentType();
1234        if (componentType == null)
1235        {
1236          throw new LDAPPersistException(
1237               ERR_DEFAULT_ENCODER_UNSUPPORTED_TYPE.get(baseClass.getName()));
1238        }
1239
1240        final ASN1OctetString[] values = attribute.getRawValues();
1241        final List<?> l = createList(baseClass, values.length);
1242        for (int i=0; i < values.length; i++)
1243        {
1244          final Object o = getValue(componentType, attribute, i);
1245          if (o == null)
1246          {
1247            throw new LDAPPersistException(
1248                 ERR_DEFAULT_ENCODER_UNSUPPORTED_TYPE.get(
1249                      componentType.getName()));
1250          }
1251
1252          invokeAdd(l, o);
1253        }
1254
1255        method.invoke(object, l);
1256        return;
1257      }
1258      else if (typeInfo.isSet() && isSupportedSetType(baseClass))
1259      {
1260        final Class<?> componentType = typeInfo.getComponentType();
1261        if (componentType == null)
1262        {
1263          throw new LDAPPersistException(
1264               ERR_DEFAULT_ENCODER_UNSUPPORTED_TYPE.get(baseClass.getName()));
1265        }
1266
1267        final ASN1OctetString[] values = attribute.getRawValues();
1268        final Set<?> s = createSet(baseClass, values.length);
1269        for (int i=0; i < values.length; i++)
1270        {
1271          final Object o = getValue(componentType, attribute, i);
1272          if (o == null)
1273          {
1274            throw new LDAPPersistException(
1275                 ERR_DEFAULT_ENCODER_UNSUPPORTED_TYPE.get(
1276                      componentType.getName()));
1277          }
1278
1279          invokeAdd(s, o);
1280        }
1281
1282        method.invoke(object, s);
1283        return;
1284      }
1285
1286      throw new LDAPPersistException(ERR_DEFAULT_ENCODER_UNSUPPORTED_TYPE.get(
1287           baseClass.getName()));
1288    }
1289    catch (final LDAPPersistException lpe)
1290    {
1291      Debug.debugException(lpe);
1292      throw lpe;
1293    }
1294    catch (final Exception e)
1295    {
1296      Debug.debugException(e);
1297
1298      if (e instanceof InvocationTargetException)
1299      {
1300        final Throwable targetException =
1301             ((InvocationTargetException) e).getTargetException();
1302        throw new LDAPPersistException(
1303             StaticUtils.getExceptionMessage(targetException), targetException);
1304      }
1305      else
1306      {
1307        throw new LDAPPersistException(StaticUtils.getExceptionMessage(e), e);
1308      }
1309    }
1310  }
1311
1312
1313
1314  /**
1315   * Creates an object of the specified type from the given attribute value.
1316   *
1317   * @param  t  The type of object to create.
1318   * @param  a  The attribute to use to create the object.
1319   * @param  p  The position in the set of values for the object to create.
1320   *
1321   * @return  The created object, or {@code null} if the provided type is not
1322   *          supported.
1323   *
1324   * @throws  LDAPPersistException  If a problem occurs while creating the
1325   *                                object.
1326   */
1327  @SuppressWarnings("unchecked")
1328  private static Object getValue(final Class<?> t, final Attribute a,
1329                                 final int p)
1330          throws LDAPPersistException
1331  {
1332    final ASN1OctetString v = a.getRawValues()[p];
1333
1334    if (t.equals(AtomicInteger.class))
1335    {
1336      return new AtomicInteger(Integer.valueOf(v.stringValue()));
1337    }
1338    else if (t.equals(AtomicLong.class))
1339    {
1340      return new AtomicLong(Long.valueOf(v.stringValue()));
1341    }
1342    else if (t.equals(BigDecimal.class))
1343    {
1344      return new BigDecimal(v.stringValue());
1345    }
1346    else if (t.equals(BigInteger.class))
1347    {
1348      return new BigInteger(v.stringValue());
1349    }
1350    else if (t.equals(Double.class) || t.equals(Double.TYPE))
1351    {
1352      return Double.valueOf(v.stringValue());
1353    }
1354    else if (t.equals(Float.class) || t.equals(Float.TYPE))
1355    {
1356      return Float.valueOf(v.stringValue());
1357    }
1358    else if (t.equals(Integer.class) || t.equals(Integer.TYPE))
1359    {
1360      return Integer.valueOf(v.stringValue());
1361    }
1362    else if (t.equals(Long.class) || t.equals(Long.TYPE))
1363    {
1364      return Long.valueOf(v.stringValue());
1365    }
1366    else if (t.equals(Short.class) || t.equals(Short.TYPE))
1367    {
1368      return Short.valueOf(v.stringValue());
1369    }
1370    else if (t.equals(String.class))
1371    {
1372      return String.valueOf(v.stringValue());
1373    }
1374    else if (t.equals(StringBuffer.class))
1375    {
1376      return new StringBuffer(v.stringValue());
1377    }
1378    else if (t.equals(StringBuilder.class))
1379    {
1380      return new StringBuilder(v.stringValue());
1381    }
1382    else if (t.equals(URI.class))
1383    {
1384      try
1385      {
1386        return new URI(v.stringValue());
1387      }
1388      catch (final Exception e)
1389      {
1390        Debug.debugException(e);
1391        throw new LDAPPersistException(
1392             ERR_DEFAULT_ENCODER_VALUE_INVALID_URI.get(v.stringValue(),
1393                  StaticUtils.getExceptionMessage(e)), e);
1394      }
1395    }
1396    else if (t.equals(URL.class))
1397    {
1398      try
1399      {
1400        return new URL(v.stringValue());
1401      }
1402      catch (final Exception e)
1403      {
1404        Debug.debugException(e);
1405        throw new LDAPPersistException(
1406             ERR_DEFAULT_ENCODER_VALUE_INVALID_URL.get(v.stringValue(),
1407                  StaticUtils.getExceptionMessage(e)), e);
1408      }
1409    }
1410    else if (t.equals(UUID.class))
1411    {
1412      try
1413      {
1414        return UUID.fromString(v.stringValue());
1415      }
1416      catch (final Exception e)
1417      {
1418        Debug.debugException(e);
1419        throw new LDAPPersistException(
1420             ERR_DEFAULT_ENCODER_VALUE_INVALID_UUID.get(v.stringValue(),
1421                  StaticUtils.getExceptionMessage(e)), e);
1422      }
1423    }
1424    else if (t.equals(DN.class))
1425    {
1426      try
1427      {
1428        return new DN(v.stringValue());
1429      }
1430      catch (final LDAPException le)
1431      {
1432        Debug.debugException(le);
1433        throw new LDAPPersistException(le.getMessage(), le);
1434      }
1435    }
1436    else if (t.equals(Filter.class))
1437    {
1438      try
1439      {
1440        return Filter.create(v.stringValue());
1441      }
1442      catch (final LDAPException le)
1443      {
1444        Debug.debugException(le);
1445        throw new LDAPPersistException(le.getMessage(), le);
1446      }
1447    }
1448    else if (t.equals(LDAPURL.class))
1449    {
1450      try
1451      {
1452        return new LDAPURL(v.stringValue());
1453      }
1454      catch (final LDAPException le)
1455      {
1456        Debug.debugException(le);
1457        throw new LDAPPersistException(le.getMessage(), le);
1458      }
1459    }
1460    else if (t.equals(RDN.class))
1461    {
1462      try
1463      {
1464        return new RDN(v.stringValue());
1465      }
1466      catch (final LDAPException le)
1467      {
1468        Debug.debugException(le);
1469        throw new LDAPPersistException(le.getMessage(), le);
1470      }
1471    }
1472    else if (t.equals(Boolean.class) || t.equals(Boolean.TYPE))
1473    {
1474      final String s = v.stringValue();
1475      if (s.equalsIgnoreCase("TRUE"))
1476      {
1477        return Boolean.TRUE;
1478      }
1479      else if (s.equalsIgnoreCase("FALSE"))
1480      {
1481        return Boolean.FALSE;
1482      }
1483      else
1484      {
1485        throw new LDAPPersistException(
1486             ERR_DEFAULT_ENCODER_VALUE_INVALID_BOOLEAN.get(s));
1487      }
1488    }
1489    else if (t.equals(Date.class))
1490    {
1491      try
1492      {
1493        return StaticUtils.decodeGeneralizedTime(v.stringValue());
1494      }
1495      catch (final Exception e)
1496      {
1497        Debug.debugException(e);
1498        throw new LDAPPersistException(
1499             ERR_DEFAULT_ENCODER_VALUE_INVALID_DATE.get(v.stringValue(),
1500                  e.getMessage()), e);
1501      }
1502    }
1503    else if (t.isArray())
1504    {
1505      final Class<?> componentType = t.getComponentType();
1506      if (componentType.equals(Byte.TYPE))
1507      {
1508        return v.getValue();
1509      }
1510      else if (componentType.equals(Character.TYPE))
1511      {
1512        return v.stringValue().toCharArray();
1513      }
1514    }
1515    else if (t.isEnum())
1516    {
1517      try
1518      {
1519        @SuppressWarnings("rawtypes")
1520        final Class<? extends Enum> enumClass = (Class<? extends Enum>) t;
1521        return Enum.valueOf(enumClass, v.stringValue());
1522      }
1523      catch (final Exception e)
1524      {
1525        Debug.debugException(e);
1526        throw new LDAPPersistException(
1527             ERR_DEFAULT_ENCODER_VALUE_INVALID_ENUM.get(v.stringValue(),
1528                  StaticUtils.getExceptionMessage(e)), e);
1529      }
1530    }
1531    else if (Serializable.class.isAssignableFrom(t))
1532    {
1533      // We shouldn't attempt to work on arrays/collections themselves.  Return
1534      // null and then we'll work on each element.
1535      if (t.isArray() || Collection.class.isAssignableFrom(t))
1536      {
1537        return null;
1538      }
1539
1540      try
1541      {
1542        final ByteArrayInputStream bais =
1543             new ByteArrayInputStream(v.getValue());
1544        final ObjectInputStream ois = new ObjectInputStream(bais);
1545        final Object o = ois.readObject();
1546        ois.close();
1547        return o;
1548      }
1549      catch (final Exception e)
1550      {
1551        Debug.debugException(e);
1552        throw new LDAPPersistException(
1553             ERR_DEFAULT_ENCODER_CANNOT_DESERIALIZE.get(a.getName(),
1554                  StaticUtils.getExceptionMessage(e)),
1555             e);
1556      }
1557    }
1558
1559    return null;
1560  }
1561
1562
1563
1564  /**
1565   * Invokes the {@code add} method on the provided {@code List} or {@code Set}
1566   * object.
1567   *
1568   * @param  l  The list or set on which to invoke the {@code add} method.
1569   * @param  o  The object to add to the {@code List} or {@code Set} object.
1570   *
1571   * @throws  LDAPPersistException  If a problem occurs while attempting to
1572   *                                invoke the {@code add} method.
1573   */
1574  private static void invokeAdd(final Object l, final Object o)
1575          throws LDAPPersistException
1576  {
1577    final Class<?> c = l.getClass();
1578
1579    for (final Method m : c.getMethods())
1580    {
1581      if (m.getName().equals("add") &&
1582          (m.getGenericParameterTypes().length == 1))
1583      {
1584        try
1585        {
1586          m.invoke(l, o);
1587          return;
1588        }
1589        catch (final Exception e)
1590        {
1591          Debug.debugException(e);
1592          throw new LDAPPersistException(
1593               ERR_DEFAULT_ENCODER_CANNOT_ADD.get(
1594                    StaticUtils.getExceptionMessage(e)),
1595               e);
1596        }
1597      }
1598    }
1599
1600    throw new LDAPPersistException(
1601         ERR_DEFAULT_ENCODER_CANNOT_FIND_ADD_METHOD.get());
1602  }
1603}