Entry encode(final T o, final String parentDN) throws LDAPPersistException {
    // Get the attributes that should be included in the entry.
    final LinkedHashMap<String, Attribute> attrMap = new LinkedHashMap<String, Attribute>();
    attrMap.put("objectClass", objectClassAttribute);

    for (final Map.Entry<String, FieldInfo> e : fieldMap.entrySet()) {
      final FieldInfo i = e.getValue();
      if (!i.includeInAdd()) {
        continue;
      }

      final Attribute a = i.encode(o, false);
      if (a != null) {
        attrMap.put(e.getKey(), a);
      }
    }

    for (final Map.Entry<String, GetterInfo> e : getterMap.entrySet()) {
      final GetterInfo i = e.getValue();
      if (!i.includeInAdd()) {
        continue;
      }

      final Attribute a = i.encode(o);
      if (a != null) {
        attrMap.put(e.getKey(), a);
      }
    }

    final String dn = constructDN(o, parentDN, attrMap);
    final Entry entry = new Entry(dn, attrMap.values());

    if (postEncodeMethod != null) {
      try {
        postEncodeMethod.invoke(o, entry);
      } catch (Throwable t) {
        debugException(t);

        if (t instanceof InvocationTargetException) {
          t = ((InvocationTargetException) t).getTargetException();
        }

        throw new LDAPPersistException(
            ERR_OBJECT_HANDLER_ERROR_INVOKING_POST_ENCODE_METHOD.get(
                postEncodeMethod.getName(), type.getName(), getExceptionMessage(t)),
            t);
      }
    }

    setDNAndEntryFields(o, entry);

    if (superclassHandler != null) {
      final Entry e = superclassHandler.encode(o, parentDN);
      for (final Attribute a : e.getAttributes()) {
        entry.addAttribute(a);
      }
    }

    return entry;
  }
  List<ObjectClassDefinition> constructObjectClasses(final OIDAllocator a)
      throws LDAPPersistException {
    final LinkedHashMap<String, ObjectClassDefinition> ocMap =
        new LinkedHashMap<String, ObjectClassDefinition>(1 + auxiliaryClasses.length);

    if (superclassHandler != null) {
      for (final ObjectClassDefinition d : superclassHandler.constructObjectClasses(a)) {
        ocMap.put(toLowerCase(d.getNameOrOID()), d);
      }
    }

    final String lowerStructuralClass = toLowerCase(structuralClass);
    if (!ocMap.containsKey(lowerStructuralClass)) {
      if (superclassHandler == null) {
        ocMap.put(
            lowerStructuralClass,
            constructObjectClass(structuralClass, "top", ObjectClassType.STRUCTURAL, a));
      } else {
        ocMap.put(
            lowerStructuralClass,
            constructObjectClass(
                structuralClass,
                superclassHandler.getStructuralClass(),
                ObjectClassType.STRUCTURAL,
                a));
      }
    }

    for (final String s : auxiliaryClasses) {
      final String lowerName = toLowerCase(s);
      if (!ocMap.containsKey(lowerName)) {
        ocMap.put(lowerName, constructObjectClass(s, "top", ObjectClassType.AUXILIARY, a));
      }
    }

    return Collections.unmodifiableList(new ArrayList<ObjectClassDefinition>(ocMap.values()));
  }
  void decode(final T o, final Entry e) throws LDAPPersistException {
    if (superclassHandler != null) {
      superclassHandler.decode(o, e);
    }

    setDNAndEntryFields(o, e);

    final ArrayList<String> failureReasons = new ArrayList<String>(5);
    boolean successful = true;

    for (final FieldInfo i : fieldMap.values()) {
      successful &= i.decode(o, e, failureReasons);
    }

    for (final SetterInfo i : setterMap.values()) {
      successful &= i.invokeSetter(o, e, failureReasons);
    }

    Throwable cause = null;
    if (postDecodeMethod != null) {
      try {
        postDecodeMethod.invoke(o);
      } catch (final Throwable t) {
        debugException(t);

        if (t instanceof InvocationTargetException) {
          cause = ((InvocationTargetException) t).getTargetException();
        } else {
          cause = t;
        }

        successful = false;
        failureReasons.add(
            ERR_OBJECT_HANDLER_ERROR_INVOKING_POST_DECODE_METHOD.get(
                postDecodeMethod.getName(), type.getName(), getExceptionMessage(t)));
      }
    }

    if (!successful) {
      throw new LDAPPersistException(concatenateStrings(failureReasons), o, cause);
    }
  }
  List<Modification> getModifications(
      final T o, final boolean deleteNullValues, final String... attributes)
      throws LDAPPersistException {
    final ReadOnlyEntry originalEntry;
    if (entryField != null) {
      originalEntry = getEntry(o);
    } else {
      originalEntry = null;
    }
    if (originalEntry != null) {
      try {
        final T decodedOrig = decode(originalEntry);
        final Entry reEncodedOriginal = encode(decodedOrig, originalEntry.getParentDNString());

        final Entry newEntry = encode(o, originalEntry.getParentDNString());
        final List<Modification> mods =
            Entry.diff(reEncodedOriginal, newEntry, true, false, attributes);
        if (!deleteNullValues) {
          final Iterator<Modification> iterator = mods.iterator();
          while (iterator.hasNext()) {
            final Modification m = iterator.next();
            if (m.getRawValues().length == 0) {
              iterator.remove();
            }
          }
        }

        HashSet<String> stripAttrs = null;
        for (final FieldInfo i : fieldMap.values()) {
          if (!i.includeInModify()) {
            if (stripAttrs == null) {
              stripAttrs = new HashSet<String>(10);
            }
            stripAttrs.add(toLowerCase(i.getAttributeName()));
          }
        }

        for (final GetterInfo i : getterMap.values()) {
          if (!i.includeInModify()) {
            if (stripAttrs == null) {
              stripAttrs = new HashSet<String>(10);
            }
            stripAttrs.add(toLowerCase(i.getAttributeName()));
          }
        }

        if (stripAttrs != null) {
          final Iterator<Modification> iterator = mods.iterator();
          while (iterator.hasNext()) {
            final Modification m = iterator.next();
            if (stripAttrs.contains(toLowerCase(m.getAttributeName()))) {
              iterator.remove();
            }
          }
        }

        return mods;
      } catch (final Exception e) {
        debugException(e);
      } finally {
        setDNAndEntryFields(o, originalEntry);
      }
    }

    final HashSet<String> attrSet;
    if ((attributes == null) || (attributes.length == 0)) {
      attrSet = null;
    } else {
      attrSet = new HashSet<String>(attributes.length);
      for (final String s : attributes) {
        attrSet.add(toLowerCase(s));
      }
    }

    final ArrayList<Modification> mods = new ArrayList<Modification>(5);

    for (final Map.Entry<String, FieldInfo> e : fieldMap.entrySet()) {
      final String attrName = toLowerCase(e.getKey());
      if ((attrSet != null) && (!attrSet.contains(attrName))) {
        continue;
      }

      final FieldInfo i = e.getValue();
      if (!i.includeInModify()) {
        continue;
      }

      final Attribute a = i.encode(o, false);
      if (a == null) {
        if (!deleteNullValues) {
          continue;
        }

        if ((originalEntry != null) && (!originalEntry.hasAttribute(attrName))) {
          continue;
        }

        mods.add(new Modification(ModificationType.REPLACE, i.getAttributeName()));
        continue;
      }

      if (originalEntry != null) {
        final Attribute originalAttr = originalEntry.getAttribute(attrName);
        if ((originalAttr != null) && originalAttr.equals(a)) {
          continue;
        }
      }

      mods.add(new Modification(ModificationType.REPLACE, i.getAttributeName(), a.getRawValues()));
    }

    for (final Map.Entry<String, GetterInfo> e : getterMap.entrySet()) {
      final String attrName = toLowerCase(e.getKey());
      if ((attrSet != null) && (!attrSet.contains(attrName))) {
        continue;
      }

      final GetterInfo i = e.getValue();
      if (!i.includeInModify()) {
        continue;
      }

      final Attribute a = i.encode(o);
      if (a == null) {
        if (!deleteNullValues) {
          continue;
        }

        if ((originalEntry != null) && (!originalEntry.hasAttribute(attrName))) {
          continue;
        }

        mods.add(new Modification(ModificationType.REPLACE, i.getAttributeName()));
        continue;
      }

      if (originalEntry != null) {
        final Attribute originalAttr = originalEntry.getAttribute(attrName);
        if ((originalAttr != null) && originalAttr.equals(a)) {
          continue;
        }
      }

      mods.add(new Modification(ModificationType.REPLACE, i.getAttributeName(), a.getRawValues()));
    }

    if (superclassHandler != null) {
      final List<Modification> superMods =
          superclassHandler.getModifications(o, deleteNullValues, attributes);
      final ArrayList<Modification> modsToAdd = new ArrayList<Modification>(superMods.size());
      for (final Modification sm : superMods) {
        boolean add = true;
        for (final Modification m : mods) {
          if (m.getAttributeName().equalsIgnoreCase(sm.getAttributeName())) {
            add = false;
            break;
          }
        }
        if (add) {
          modsToAdd.add(sm);
        }
      }
      mods.addAll(modsToAdd);
    }

    return Collections.unmodifiableList(mods);
  }
  private Filter createFilter(final T o, final AtomicBoolean addedRequiredOrAllowed)
      throws LDAPPersistException {
    final ArrayList<Attribute> attrs = new ArrayList<Attribute>(5);
    attrs.add(objectClassAttribute);

    for (final FieldInfo i : requiredFilterFields) {
      final Attribute a = i.encode(o, true);
      if (a == null) {
        throw new LDAPPersistException(
            ERR_OBJECT_HANDLER_FILTER_MISSING_REQUIRED_FIELD.get(i.getField().getName()));
      } else {
        attrs.add(a);
        addedRequiredOrAllowed.set(true);
      }
    }

    for (final GetterInfo i : requiredFilterGetters) {
      final Attribute a = i.encode(o);
      if (a == null) {
        throw new LDAPPersistException(
            ERR_OBJECT_HANDLER_FILTER_MISSING_REQUIRED_GETTER.get(i.getMethod().getName()));
      } else {
        attrs.add(a);
        addedRequiredOrAllowed.set(true);
      }
    }

    for (final FieldInfo i : alwaysAllowedFilterFields) {
      final Attribute a = i.encode(o, true);
      if (a != null) {
        attrs.add(a);
        addedRequiredOrAllowed.set(true);
      }
    }

    for (final GetterInfo i : alwaysAllowedFilterGetters) {
      final Attribute a = i.encode(o);
      if (a != null) {
        attrs.add(a);
        addedRequiredOrAllowed.set(true);
      }
    }

    for (final FieldInfo i : conditionallyAllowedFilterFields) {
      final Attribute a = i.encode(o, true);
      if (a != null) {
        attrs.add(a);
      }
    }

    for (final GetterInfo i : conditionallyAllowedFilterGetters) {
      final Attribute a = i.encode(o);
      if (a != null) {
        attrs.add(a);
      }
    }

    final ArrayList<Filter> comps = new ArrayList<Filter>(attrs.size());
    for (final Attribute a : attrs) {
      for (final ASN1OctetString v : a.getRawValues()) {
        comps.add(Filter.createEqualityFilter(a.getName(), v.getValue()));
      }
    }

    if (superclassHandler != null) {
      final Filter f = superclassHandler.createFilter(o, addedRequiredOrAllowed);
      if (f.getFilterType() == Filter.FILTER_TYPE_AND) {
        comps.addAll(Arrays.asList(f.getComponents()));
      } else {
        comps.add(f);
      }
    }

    return Filter.createANDFilter(comps);
  }