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