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);
  }