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;
053
054import com.unboundid.asn1.ASN1OctetString;
055import com.unboundid.ldap.matchingrules.CaseIgnoreStringMatchingRule;
056import com.unboundid.ldap.matchingrules.MatchingRule;
057import com.unboundid.ldap.sdk.Attribute;
058import com.unboundid.ldap.sdk.DN;
059import com.unboundid.ldap.sdk.Filter;
060import com.unboundid.ldap.sdk.LDAPURL;
061import com.unboundid.ldap.sdk.RDN;
062import com.unboundid.ldap.sdk.LDAPException;
063import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition;
064import com.unboundid.ldap.sdk.schema.AttributeUsage;
065import com.unboundid.util.Debug;
066import com.unboundid.util.NotMutable;
067import com.unboundid.util.StaticUtils;
068import com.unboundid.util.ThreadSafety;
069import com.unboundid.util.ThreadSafetyLevel;
070
071import static com.unboundid.ldap.sdk.persist.PersistMessages.*;
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)
172public 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(StaticUtils.computeMapCapacity(size));
388    }
389    else if (t.equals(HashSet.class))
390    {
391      return new HashSet(StaticUtils.computeMapCapacity(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().isEmpty())
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().isEmpty())
470    {
471      attrName = StaticUtils.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, StaticUtils.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        Debug.debugException(e);
776        throw new LDAPPersistException(
777             ERR_DEFAULT_ENCODER_CANNOT_SERIALIZE.get(name,
778                  StaticUtils.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(StaticUtils.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          Debug.debugException(e);
890          throw new LDAPPersistException(
891               ERR_DEFAULT_ENCODER_CANNOT_SERIALIZE.get(attributeName,
892                    StaticUtils.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(StaticUtils.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          Debug.debugException(e);
1010          throw new LDAPPersistException(
1011               ERR_DEFAULT_ENCODER_CANNOT_SERIALIZE.get(attributeName,
1012                    StaticUtils.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 (final LDAPPersistException lpe)
1132    {
1133      Debug.debugException(lpe);
1134      throw lpe;
1135    }
1136    catch (final Exception e)
1137    {
1138      Debug.debugException(e);
1139      throw new LDAPPersistException(StaticUtils.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 (final LDAPPersistException lpe)
1247    {
1248      Debug.debugException(lpe);
1249      throw lpe;
1250    }
1251    catch (final Exception e)
1252    {
1253      Debug.debugException(e);
1254
1255      if (e instanceof InvocationTargetException)
1256      {
1257        final Throwable targetException =
1258             ((InvocationTargetException) e).getTargetException();
1259        throw new LDAPPersistException(
1260             StaticUtils.getExceptionMessage(targetException), targetException);
1261      }
1262      else
1263      {
1264        throw new LDAPPersistException(StaticUtils.getExceptionMessage(e), e);
1265      }
1266    }
1267  }
1268
1269
1270
1271  /**
1272   * Creates an object of the specified type from the given attribute value.
1273   *
1274   * @param  t  The type of object to create.
1275   * @param  a  The attribute to use to create the object.
1276   * @param  p  The position in the set of values for the object to create.
1277   *
1278   * @return  The created object, or {@code null} if the provided type is not
1279   *          supported.
1280   *
1281   * @throws  LDAPPersistException  If a problem occurs while creating the
1282   *                                object.
1283   */
1284  @SuppressWarnings("unchecked")
1285  private static Object getValue(final Class<?> t, final Attribute a,
1286                                 final int p)
1287          throws LDAPPersistException
1288  {
1289    final ASN1OctetString v = a.getRawValues()[p];
1290
1291    if (t.equals(AtomicInteger.class))
1292    {
1293      return new AtomicInteger(Integer.valueOf(v.stringValue()));
1294    }
1295    else if (t.equals(AtomicLong.class))
1296    {
1297      return new AtomicLong(Long.valueOf(v.stringValue()));
1298    }
1299    else if (t.equals(BigDecimal.class))
1300    {
1301      return new BigDecimal(v.stringValue());
1302    }
1303    else if (t.equals(BigInteger.class))
1304    {
1305      return new BigInteger(v.stringValue());
1306    }
1307    else if (t.equals(Double.class) || t.equals(Double.TYPE))
1308    {
1309      return Double.valueOf(v.stringValue());
1310    }
1311    else if (t.equals(Float.class) || t.equals(Float.TYPE))
1312    {
1313      return Float.valueOf(v.stringValue());
1314    }
1315    else if (t.equals(Integer.class) || t.equals(Integer.TYPE))
1316    {
1317      return Integer.valueOf(v.stringValue());
1318    }
1319    else if (t.equals(Long.class) || t.equals(Long.TYPE))
1320    {
1321      return Long.valueOf(v.stringValue());
1322    }
1323    else if (t.equals(Short.class) || t.equals(Short.TYPE))
1324    {
1325      return Short.valueOf(v.stringValue());
1326    }
1327    else if (t.equals(String.class))
1328    {
1329      return String.valueOf(v.stringValue());
1330    }
1331    else if (t.equals(StringBuffer.class))
1332    {
1333      return new StringBuffer(v.stringValue());
1334    }
1335    else if (t.equals(StringBuilder.class))
1336    {
1337      return new StringBuilder(v.stringValue());
1338    }
1339    else if (t.equals(URI.class))
1340    {
1341      try
1342      {
1343        return new URI(v.stringValue());
1344      }
1345      catch (final Exception e)
1346      {
1347        Debug.debugException(e);
1348        throw new LDAPPersistException(
1349             ERR_DEFAULT_ENCODER_VALUE_INVALID_URI.get(v.stringValue(),
1350                  StaticUtils.getExceptionMessage(e)), e);
1351      }
1352    }
1353    else if (t.equals(URL.class))
1354    {
1355      try
1356      {
1357        return new URL(v.stringValue());
1358      }
1359      catch (final Exception e)
1360      {
1361        Debug.debugException(e);
1362        throw new LDAPPersistException(
1363             ERR_DEFAULT_ENCODER_VALUE_INVALID_URL.get(v.stringValue(),
1364                  StaticUtils.getExceptionMessage(e)), e);
1365      }
1366    }
1367    else if (t.equals(UUID.class))
1368    {
1369      try
1370      {
1371        return UUID.fromString(v.stringValue());
1372      }
1373      catch (final Exception e)
1374      {
1375        Debug.debugException(e);
1376        throw new LDAPPersistException(
1377             ERR_DEFAULT_ENCODER_VALUE_INVALID_UUID.get(v.stringValue(),
1378                  StaticUtils.getExceptionMessage(e)), e);
1379      }
1380    }
1381    else if (t.equals(DN.class))
1382    {
1383      try
1384      {
1385        return new DN(v.stringValue());
1386      }
1387      catch (final LDAPException le)
1388      {
1389        Debug.debugException(le);
1390        throw new LDAPPersistException(le.getMessage(), le);
1391      }
1392    }
1393    else if (t.equals(Filter.class))
1394    {
1395      try
1396      {
1397        return Filter.create(v.stringValue());
1398      }
1399      catch (final LDAPException le)
1400      {
1401        Debug.debugException(le);
1402        throw new LDAPPersistException(le.getMessage(), le);
1403      }
1404    }
1405    else if (t.equals(LDAPURL.class))
1406    {
1407      try
1408      {
1409        return new LDAPURL(v.stringValue());
1410      }
1411      catch (final LDAPException le)
1412      {
1413        Debug.debugException(le);
1414        throw new LDAPPersistException(le.getMessage(), le);
1415      }
1416    }
1417    else if (t.equals(RDN.class))
1418    {
1419      try
1420      {
1421        return new RDN(v.stringValue());
1422      }
1423      catch (final LDAPException le)
1424      {
1425        Debug.debugException(le);
1426        throw new LDAPPersistException(le.getMessage(), le);
1427      }
1428    }
1429    else if (t.equals(Boolean.class) || t.equals(Boolean.TYPE))
1430    {
1431      final String s = v.stringValue();
1432      if (s.equalsIgnoreCase("TRUE"))
1433      {
1434        return Boolean.TRUE;
1435      }
1436      else if (s.equalsIgnoreCase("FALSE"))
1437      {
1438        return Boolean.FALSE;
1439      }
1440      else
1441      {
1442        throw new LDAPPersistException(
1443             ERR_DEFAULT_ENCODER_VALUE_INVALID_BOOLEAN.get(s));
1444      }
1445    }
1446    else if (t.equals(Date.class))
1447    {
1448      try
1449      {
1450        return StaticUtils.decodeGeneralizedTime(v.stringValue());
1451      }
1452      catch (final Exception e)
1453      {
1454        Debug.debugException(e);
1455        throw new LDAPPersistException(
1456             ERR_DEFAULT_ENCODER_VALUE_INVALID_DATE.get(v.stringValue(),
1457                  e.getMessage()), e);
1458      }
1459    }
1460    else if (t.isArray())
1461    {
1462      final Class<?> componentType = t.getComponentType();
1463      if (componentType.equals(Byte.TYPE))
1464      {
1465        return v.getValue();
1466      }
1467      else if (componentType.equals(Character.TYPE))
1468      {
1469        return v.stringValue().toCharArray();
1470      }
1471    }
1472    else if (t.isEnum())
1473    {
1474      try
1475      {
1476        @SuppressWarnings("rawtypes")
1477        final Class<? extends Enum> enumClass = (Class<? extends Enum>) t;
1478        return Enum.valueOf(enumClass, v.stringValue());
1479      }
1480      catch (final Exception e)
1481      {
1482        Debug.debugException(e);
1483        throw new LDAPPersistException(
1484             ERR_DEFAULT_ENCODER_VALUE_INVALID_ENUM.get(v.stringValue(),
1485                  StaticUtils.getExceptionMessage(e)), e);
1486      }
1487    }
1488    else if (Serializable.class.isAssignableFrom(t))
1489    {
1490      // We shouldn't attempt to work on arrays/collections themselves.  Return
1491      // null and then we'll work on each element.
1492      if (t.isArray() || Collection.class.isAssignableFrom(t))
1493      {
1494        return null;
1495      }
1496
1497      try
1498      {
1499        final ByteArrayInputStream bais =
1500             new ByteArrayInputStream(v.getValue());
1501        final ObjectInputStream ois = new ObjectInputStream(bais);
1502        final Object o = ois.readObject();
1503        ois.close();
1504        return o;
1505      }
1506      catch (final Exception e)
1507      {
1508        Debug.debugException(e);
1509        throw new LDAPPersistException(
1510             ERR_DEFAULT_ENCODER_CANNOT_DESERIALIZE.get(a.getName(),
1511                  StaticUtils.getExceptionMessage(e)),
1512             e);
1513      }
1514    }
1515
1516    return null;
1517  }
1518
1519
1520
1521  /**
1522   * Invokes the {@code add} method on the provided {@code List} or {@code Set}
1523   * object.
1524   *
1525   * @param  l  The list or set on which to invoke the {@code add} method.
1526   * @param  o  The object to add to the {@code List} or {@code Set} object.
1527   *
1528   * @throws  LDAPPersistException  If a problem occurs while attempting to
1529   *                                invoke the {@code add} method.
1530   */
1531  private static void invokeAdd(final Object l, final Object o)
1532          throws LDAPPersistException
1533  {
1534    final Class<?> c = l.getClass();
1535
1536    for (final Method m : c.getMethods())
1537    {
1538      if (m.getName().equals("add") &&
1539          (m.getGenericParameterTypes().length == 1))
1540      {
1541        try
1542        {
1543          m.invoke(l, o);
1544          return;
1545        }
1546        catch (final Exception e)
1547        {
1548          Debug.debugException(e);
1549          throw new LDAPPersistException(
1550               ERR_DEFAULT_ENCODER_CANNOT_ADD.get(
1551                    StaticUtils.getExceptionMessage(e)),
1552               e);
1553        }
1554      }
1555    }
1556
1557    throw new LDAPPersistException(
1558         ERR_DEFAULT_ENCODER_CANNOT_FIND_ADD_METHOD.get());
1559  }
1560}