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