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