001/*
002 * Copyright 2009-2014 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2009-2014 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 */
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.NotMutable;
066import com.unboundid.util.ThreadSafety;
067import com.unboundid.util.ThreadSafetyLevel;
068
069import static com.unboundid.ldap.sdk.persist.PersistMessages.*;
070import static com.unboundid.util.Debug.*;
071import 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)
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(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        final Class<? extends Enum> enumClass = (Class<? extends Enum>) t;
1472        return Enum.valueOf(enumClass, v.stringValue());
1473      }
1474      catch (final Exception e)
1475      {
1476        debugException(e);
1477        throw new LDAPPersistException(
1478             ERR_DEFAULT_ENCODER_VALUE_INVALID_ENUM.get(v.stringValue(),
1479                  getExceptionMessage(e)), e);
1480      }
1481    }
1482    else if (Serializable.class.isAssignableFrom(t))
1483    {
1484      // We shouldn't attempt to work on arrays/collections themselves.  Return
1485      // null and then we'll work on each element.
1486      if (t.isArray() || Collection.class.isAssignableFrom(t))
1487      {
1488        return null;
1489      }
1490
1491      try
1492      {
1493        final ByteArrayInputStream bais =
1494             new ByteArrayInputStream(v.getValue());
1495        final ObjectInputStream ois = new ObjectInputStream(bais);
1496        final Object o = ois.readObject();
1497        ois.close();
1498        return o;
1499      }
1500      catch (final Exception e)
1501      {
1502        debugException(e);
1503        throw new LDAPPersistException(
1504             ERR_DEFAULT_ENCODER_CANNOT_DESERIALIZE.get(a.getName(),
1505                  getExceptionMessage(e)),
1506             e);
1507      }
1508    }
1509
1510    return null;
1511  }
1512
1513
1514
1515  /**
1516   * Invokes the {@code add} method on the provided {@code List} or {@code Set}
1517   * object.
1518   *
1519   * @param  l  The list or set on which to invoke the {@code add} method.
1520   * @param  o  The object to add to the {@code List} or {@code Set} object.
1521   *
1522   * @throws  LDAPPersistException  If a problem occurs while attempting to
1523   *                                invoke the {@code add} method.
1524   */
1525  private static void invokeAdd(final Object l, final Object o)
1526          throws LDAPPersistException
1527  {
1528    final Class<?> c = l.getClass();
1529
1530    for (final Method m : c.getMethods())
1531    {
1532      if (m.getName().equals("add") &&
1533          (m.getGenericParameterTypes().length == 1))
1534      {
1535        try
1536        {
1537          m.invoke(l, o);
1538          return;
1539        }
1540        catch (final Exception e)
1541        {
1542          debugException(e);
1543          throw new LDAPPersistException(
1544               ERR_DEFAULT_ENCODER_CANNOT_ADD.get(getExceptionMessage(e)), e);
1545        }
1546      }
1547    }
1548
1549    throw new LDAPPersistException(
1550         ERR_DEFAULT_ENCODER_CANNOT_FIND_ADD_METHOD.get());
1551  }
1552}