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;
037
038
039
040import java.io.Serializable;
041import java.util.ArrayList;
042import java.util.Collection;
043import java.util.Collections;
044import java.util.Date;
045import java.util.Iterator;
046import java.util.List;
047import java.util.Set;
048
049import com.unboundid.util.ByteStringBuffer;
050import com.unboundid.util.NotMutable;
051import com.unboundid.util.NotNull;
052import com.unboundid.util.Nullable;
053import com.unboundid.util.ThreadSafety;
054import com.unboundid.util.ThreadSafetyLevel;
055import com.unboundid.util.Validator;
056
057
058
059/**
060 * This class provides a data structure that represents a compact version of an
061 * entry.  This is basically the same as an {@code Entry} object, except that
062 * it stores the information in a more compact form that requires less space in
063 * memory.  This may be useful in applications that need to hold a large number
064 * of entries in memory.  Note that performance of some methods in this class
065 * may be significantly worse than the performance of the corresponding methods
066 * in the {@code Entry} class.
067 *
068 * @see  Entry
069 */
070@NotMutable()
071@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
072public final class CompactEntry
073       implements Serializable
074{
075  /**
076   * The serial version UID for this serializable class.
077   */
078  private static final long serialVersionUID = 8067151651120794058L;
079
080
081
082  // The set of attributes for this entry.
083  @NotNull private final CompactAttribute[] attributes;
084
085  // The hash code for this entry, if it has been calculated.
086  private int hashCode;
087
088  // The DN for this entry.
089  @NotNull private final String dn;
090
091
092
093  /**
094   * Creates a new compact entry from the provided entry.
095   *
096   * @param  entry  The entry to use to create this compact entry.  It must not
097   *                be {@code null}.
098   */
099  public CompactEntry(@NotNull final Entry entry)
100  {
101    Validator.ensureNotNull(entry);
102
103    dn = entry.getDN();
104    hashCode = -1;
105
106    final Collection<Attribute> attrs = entry.getAttributes();
107    attributes = new CompactAttribute[attrs.size()];
108    final Iterator<Attribute> iterator = attrs.iterator();
109    for (int i=0; i < attributes.length; i++)
110    {
111      attributes[i] = new CompactAttribute(iterator.next());
112    }
113  }
114
115
116
117  /**
118   * Retrieves the DN for this entry.
119   *
120   * @return  The DN for this entry.
121   */
122  @NotNull()
123  public String getDN()
124  {
125    return dn;
126  }
127
128
129
130  /**
131   * Retrieves the parsed DN for this entry.
132   *
133   * @return  The parsed DN for this entry.
134   *
135   * @throws  LDAPException  If the DN string cannot be parsed as a valid DN.
136   */
137  @NotNull()
138  public DN getParsedDN()
139         throws LDAPException
140  {
141    return new DN(dn);
142  }
143
144
145
146  /**
147   * Retrieves the RDN for this entry.
148   *
149   * @return  The RDN for this entry, or {@code null} if the DN is the null DN.
150   *
151   * @throws  LDAPException  If the DN string cannot be parsed as a valid DN.
152   */
153  @Nullable()
154  public RDN getRDN()
155         throws LDAPException
156  {
157    return getParsedDN().getRDN();
158  }
159
160
161
162  /**
163   * Retrieves the parent DN for this entry.
164   *
165   * @return  The parent DN for this entry, or {@code null} if there is no
166   *          parent.
167   *
168   * @throws  LDAPException  If the DN string cannot be parsed as a valid DN.
169   */
170  @Nullable()
171  public DN getParentDN()
172         throws LDAPException
173  {
174    return getParsedDN().getParent();
175  }
176
177
178
179  /**
180   * Retrieves the parent DN for this entry as a string.
181   *
182   * @return  The parent DN for this entry as a string, or {@code null} if there
183   *          is no parent.
184   *
185   * @throws  LDAPException  If the DN string cannot be parsed as a valid DN.
186   */
187  @Nullable()
188  public String getParentDNString()
189         throws LDAPException
190  {
191    return getParsedDN().getParentString();
192  }
193
194
195
196  /**
197   * Indicates whether this entry contains the specified attribute.
198   *
199   * @param  attributeName  The name of the attribute for which to make the
200   *                        determination.  It must not be {@code null}.
201   *
202   * @return  {@code true} if this entry contains the specified attribute, or
203   *          {@code false} if not.
204   */
205  public boolean hasAttribute(@NotNull final String attributeName)
206  {
207    Validator.ensureNotNull(attributeName);
208
209    for (final CompactAttribute a : attributes)
210    {
211      if (a.getName().equalsIgnoreCase(attributeName))
212      {
213        return true;
214      }
215    }
216
217    return false;
218  }
219
220
221
222  /**
223   * Indicates whether this entry contains the specified attribute.  It will
224   * only return {@code true} if this entry contains an attribute with the same
225   * name and exact set of values.
226   *
227   * @param  attribute  The attribute for which to make the determination.  It
228   *                    must not be {@code null}.
229   *
230   * @return  {@code true} if this entry contains the specified attribute, or
231   *          {@code false}.
232   */
233  public boolean hasAttribute(@NotNull final Attribute attribute)
234  {
235    Validator.ensureNotNull(attribute);
236
237    for (final CompactAttribute a : attributes)
238    {
239      if (a.toAttribute().equals(attribute))
240      {
241        return true;
242      }
243    }
244
245    return false;
246  }
247
248
249
250  /**
251   * Indicates whether this entry contains an attribute with the given name and
252   * value.
253   *
254   * @param  attributeName   The name of the attribute for which to make the
255   *                         determination.  It must not be {@code null}.
256   * @param  attributeValue  The value for which to make the determination.  It
257   *                         must not be {@code null}.
258   *
259   * @return  {@code true} if this entry contains an attribute with the
260   *          specified name and value, or {@code false} if not.
261   */
262  public boolean hasAttributeValue(@NotNull final String attributeName,
263                                   @NotNull final String attributeValue)
264  {
265    Validator.ensureNotNull(attributeName, attributeValue);
266
267    for (final CompactAttribute a : attributes)
268    {
269      if (a.getName().equalsIgnoreCase(attributeName) &&
270          a.toAttribute().hasValue(attributeValue))
271      {
272        return true;
273      }
274    }
275
276    return false;
277  }
278
279
280
281  /**
282   * Indicates whether this entry contains an attribute with the given name and
283   * value.
284   *
285   * @param  attributeName   The name of the attribute for which to make the
286   *                         determination.  It must not be {@code null}.
287   * @param  attributeValue  The value for which to make the determination.  It
288   *                         must not be {@code null}.
289   *
290   * @return  {@code true} if this entry contains an attribute with the
291   *          specified name and value, or {@code false} if not.
292   */
293  public boolean hasAttributeValue(@NotNull final String attributeName,
294                                   @NotNull final byte[] attributeValue)
295  {
296    Validator.ensureNotNull(attributeName, attributeValue);
297
298    for (final CompactAttribute a : attributes)
299    {
300      if (a.getName().equalsIgnoreCase(attributeName) &&
301          a.toAttribute().hasValue(attributeValue))
302      {
303        return true;
304      }
305    }
306
307    return false;
308  }
309
310
311
312  /**
313   * Indicates whether this entry contains the specified object class.
314   *
315   * @param  objectClassName  The name of the object class for which to make the
316   *                          determination.  It must not be {@code null}.
317   *
318   * @return  {@code true} if this entry contains the specified object class, or
319   *          {@code false} if not.
320   */
321  public boolean hasObjectClass(@NotNull final String objectClassName)
322  {
323    return hasAttributeValue("objectClass", objectClassName);
324  }
325
326
327
328  /**
329   * Retrieves the set of attributes contained in this entry.
330   *
331   * @return  The set of attributes contained in this entry.
332   */
333  @NotNull()
334  public Collection<Attribute> getAttributes()
335  {
336    final ArrayList<Attribute> attrList =
337         new ArrayList<>(attributes.length);
338    for (final CompactAttribute a : attributes)
339    {
340      attrList.add(a.toAttribute());
341    }
342
343    return Collections.unmodifiableCollection(attrList);
344  }
345
346
347
348  /**
349   * Retrieves the attribute with the specified name.
350   *
351   * @param  attributeName  The name of the attribute to retrieve.  It must not
352   *                        be {@code null}.
353   *
354   * @return  The requested attribute from this entry, or {@code null} if the
355   *          specified attribute is not present in this entry.
356   */
357  @Nullable()
358  public Attribute getAttribute(@NotNull final String attributeName)
359  {
360    Validator.ensureNotNull(attributeName);
361
362    for (final CompactAttribute a : attributes)
363    {
364      if (a.getName().equalsIgnoreCase(attributeName))
365      {
366        return a.toAttribute();
367      }
368    }
369
370    return null;
371  }
372
373
374
375  /**
376   * Retrieves the list of attributes with the given base name and all of the
377   * specified options.
378   *
379   * @param  baseName  The base name (without any options) for the attribute to
380   *                   retrieve.  It must not be {@code null}.
381   * @param  options   The set of options that should be included in the
382   *                   attributes that are returned.  It may be empty or
383   *                   {@code null} if all attributes with the specified base
384   *                   name should be returned, regardless of the options that
385   *                   they contain (if any).
386   *
387   * @return  The list of attributes with the given base name and all of the
388   *          specified options.  It may be empty if there are no attributes
389   *          with the specified base name and set of options.
390   */
391  @NotNull()
392  public List<Attribute> getAttributesWithOptions(
393                              @NotNull final String baseName,
394                              @NotNull final Set<String> options)
395  {
396    return toEntry().getAttributesWithOptions(baseName, options);
397  }
398
399
400
401  /**
402   * Retrieves the value for the specified attribute, if available.  If the
403   * attribute has more than one value, then the first value will be returned.
404   *
405   * @param  attributeName  The name of the attribute for which to retrieve the
406   *                        value.  It must not be {@code null}.
407   *
408   * @return  The value for the specified attribute, or {@code null} if that
409   *          attribute is not available.
410   */
411  @Nullable()
412  public String getAttributeValue(@NotNull final String attributeName)
413  {
414    Validator.ensureNotNull(attributeName);
415
416    for (final CompactAttribute a : attributes)
417    {
418      if (a.getName().equalsIgnoreCase(attributeName))
419      {
420        final String[] values = a.getStringValues();
421        if (values.length > 0)
422        {
423          return values[0];
424        }
425        else
426        {
427          return null;
428        }
429      }
430    }
431
432    return null;
433  }
434
435
436
437  /**
438   * Retrieves the value for the specified attribute as a byte array, if
439   * available.  If the attribute has more than one value, then the first value
440   * will be returned.
441   *
442   * @param  attributeName  The name of the attribute for which to retrieve the
443   *                        value.  It must not be {@code null}.
444   *
445   * @return  The value for the specified attribute as a byte array, or
446   *          {@code null} if that attribute is not available.
447   */
448  @Nullable()
449  public byte[] getAttributeValueBytes(@NotNull final String attributeName)
450  {
451    Validator.ensureNotNull(attributeName);
452
453    for (final CompactAttribute a : attributes)
454    {
455      if (a.getName().equalsIgnoreCase(attributeName))
456      {
457        final byte[][] values = a.getByteValues();
458        if (values.length > 0)
459        {
460          return values[0];
461        }
462        else
463        {
464          return null;
465        }
466      }
467    }
468
469    return null;
470  }
471
472
473
474  /**
475   * Retrieves the value for the specified attribute as a Boolean, if available.
476   * If the attribute has more than one value, then the first value will be
477   * returned.  Values of "true", "t", "yes", "y", "on", and "1" will be
478   * interpreted as {@code TRUE}.  Values of "false", "f", "no", "n", "off", and
479   * "0" will be interpreted as {@code FALSE}.
480   *
481   * @param  attributeName  The name of the attribute for which to retrieve the
482   *                        value.  It must not be {@code null}.
483   *
484   * @return  The Boolean value parsed from the specified attribute, or
485   *          {@code null} if that attribute is not available or the value
486   *          cannot be parsed as a Boolean.
487   */
488  @Nullable()
489  public Boolean getAttributeValueAsBoolean(@NotNull final String attributeName)
490  {
491    Validator.ensureNotNull(attributeName);
492
493    final Attribute a = getAttribute(attributeName);
494    if (a == null)
495    {
496      return null;
497    }
498    else
499    {
500      return a.getValueAsBoolean();
501    }
502  }
503
504
505
506  /**
507   * Retrieves the value for the specified attribute as a Date, formatted using
508   * the generalized time syntax, if available.  If the attribute has more than
509   * one value, then the first value will be returned.
510   *
511   * @param  attributeName  The name of the attribute for which to retrieve the
512   *                        value.  It must not be {@code null}.
513   *
514   * @return  The Date value parsed from the specified attribute, or
515   *           {@code null} if that attribute is not available or the value
516   *           cannot be parsed as a Date.
517   */
518  @Nullable()
519  public Date getAttributeValueAsDate(@NotNull final String attributeName)
520  {
521    Validator.ensureNotNull(attributeName);
522
523    final Attribute a = getAttribute(attributeName);
524    if (a == null)
525    {
526      return null;
527    }
528    else
529    {
530      return a.getValueAsDate();
531    }
532  }
533
534
535
536  /**
537   * Retrieves the value for the specified attribute as a DN, if available.  If
538   * the attribute has more than one value, then the first value will be
539   * returned.
540   *
541   * @param  attributeName  The name of the attribute for which to retrieve the
542   *                        value.  It must not be {@code null}.
543   *
544   * @return  The Date value parsed from the specified attribute, or
545   *           {@code null} if that attribute is not available or the value
546   *           cannot be parsed as a DN.
547   */
548  @Nullable()
549  public DN getAttributeValueAsDN(@NotNull final String attributeName)
550  {
551    Validator.ensureNotNull(attributeName);
552
553    final Attribute a = getAttribute(attributeName);
554    if (a == null)
555    {
556      return null;
557    }
558    else
559    {
560      return a.getValueAsDN();
561    }
562  }
563
564
565
566  /**
567   * Retrieves the value for the specified attribute as an Integer, if
568   * available.  If the attribute has more than one value, then the first value
569   * will be returned.
570   *
571   * @param  attributeName  The name of the attribute for which to retrieve the
572   *                        value.  It must not be {@code null}.
573   *
574   * @return  The Integer value parsed from the specified attribute, or
575   *          {@code null} if that attribute is not available or the value
576   *          cannot be parsed as an Integer.
577   */
578  @Nullable()
579  public Integer getAttributeValueAsInteger(@NotNull final String attributeName)
580  {
581    Validator.ensureNotNull(attributeName);
582
583    final Attribute a = getAttribute(attributeName);
584    if (a == null)
585    {
586      return null;
587    }
588    else
589    {
590      return a.getValueAsInteger();
591    }
592  }
593
594
595
596  /**
597   * Retrieves the value for the specified attribute as a Long, if available.
598   * If the attribute has more than one value, then the first value will be
599   * returned.
600   *
601   * @param  attributeName  The name of the attribute for which to retrieve the
602   *                        value.  It must not be {@code null}.
603   *
604   * @return  The Long value parsed from the specified attribute, or
605   *          {@code null} if that attribute is not available or the value
606   *          cannot be parsed as a Long.
607   */
608  @Nullable()
609  public Long getAttributeValueAsLong(@NotNull final String attributeName)
610  {
611    Validator.ensureNotNull(attributeName);
612
613    final Attribute a = getAttribute(attributeName);
614    if (a == null)
615    {
616      return null;
617    }
618    else
619    {
620      return a.getValueAsLong();
621    }
622  }
623
624
625
626  /**
627   * Retrieves the set of values for the specified attribute, if available.
628   *
629   * @param  attributeName  The name of the attribute for which to retrieve the
630   *                        values.  It must not be {@code null}.
631   *
632   * @return  The set of values for the specified attribute, or {@code null} if
633   *          that attribute is not available.
634   */
635  @Nullable()
636  public String[] getAttributeValues(@NotNull final String attributeName)
637  {
638    Validator.ensureNotNull(attributeName);
639
640    for (final CompactAttribute a : attributes)
641    {
642      if (a.getName().equalsIgnoreCase(attributeName))
643      {
644        return a.getStringValues();
645      }
646    }
647
648    return null;
649  }
650
651
652
653  /**
654   * Retrieves the set of values for the specified attribute as byte arrays, if
655   * available.
656   *
657   * @param  attributeName  The name of the attribute for which to retrieve the
658   *                        values.  It must not be {@code null}.
659   *
660   * @return  The set of values for the specified attribute as byte arrays, or
661   *          {@code null} if that attribute is not available.
662   */
663  @Nullable()
664  public byte[][] getAttributeValueByteArrays(
665                       @NotNull final String attributeName)
666  {
667    Validator.ensureNotNull(attributeName);
668
669    for (final CompactAttribute a : attributes)
670    {
671      if (a.getName().equalsIgnoreCase(attributeName))
672      {
673        return a.getByteValues();
674      }
675    }
676
677    return null;
678  }
679
680
681
682  /**
683   * Retrieves the "objectClass" attribute from the entry, if available.
684   *
685   * @return  The "objectClass" attribute from the entry, or {@code null} if
686   *          that attribute not available.
687   */
688  @Nullable()
689  public Attribute getObjectClassAttribute()
690  {
691    return getAttribute("objectClass");
692  }
693
694
695
696  /**
697   * Retrieves the values of the "objectClass" attribute from the entry, if
698   * available.
699   *
700   * @return  The values of the "objectClass" attribute from the entry, or
701   *          {@code null} if that attribute is not available.
702   */
703  @Nullable()
704  public String[] getObjectClassValues()
705  {
706    return getAttributeValues("objectClass");
707  }
708
709
710
711  /**
712   * Converts this compact entry to a full entry.
713   *
714   * @return  The entry created from this compact entry.
715   */
716  @NotNull()
717  public Entry toEntry()
718  {
719    final Attribute[] attrs = new Attribute[attributes.length];
720    for (int i=0; i < attributes.length; i++)
721    {
722      attrs[i] = attributes[i].toAttribute();
723    }
724
725    return new Entry(dn, attrs);
726  }
727
728
729
730  /**
731   * Generates a hash code for this entry.
732   *
733   * @return  The generated hash code for this entry.
734   */
735  @Override()
736  public int hashCode()
737  {
738    if (hashCode == -1)
739    {
740      hashCode = toEntry().hashCode();
741    }
742
743    return hashCode;
744  }
745
746
747
748  /**
749   * Indicates whether the provided object is equal to this entry.  The provided
750   * object will only be considered equal to this entry if it is an entry with
751   * the same DN and set of attributes.
752   *
753   * @param  o  The object for which to make the determination.
754   *
755   * @return  {@code true} if the provided object is considered equal to this
756   *          entry, or {@code false} if not.
757   */
758  @Override()
759  public boolean equals(@Nullable final Object o)
760  {
761    if ((o == null) || (! (o instanceof CompactEntry)))
762    {
763      return false;
764    }
765
766    return toEntry().equals(((CompactEntry) o).toEntry());
767  }
768
769
770
771  /**
772   * Retrieves an LDIF representation of this entry, with each attribute value
773   * on a separate line.  Long lines will not be wrapped.
774   *
775   * @return  An LDIF representation of this entry.
776   */
777  @NotNull()
778  public String[] toLDIF()
779  {
780    return toLDIF(0);
781  }
782
783
784
785  /**
786   * Retrieves an LDIF representation of this entry, with each attribute value
787   * on a separate line.  Long lines will be wrapped at the specified column.
788   *
789   * @param  wrapColumn  The column at which long lines should be wrapped.  A
790   *                     value less than or equal to two indicates that no
791   *                     wrapping should be performed.
792   *
793   * @return  An LDIF representation of this entry.
794   */
795  @NotNull()
796  public String[] toLDIF(final int wrapColumn)
797  {
798    return toEntry().toLDIF(wrapColumn);
799  }
800
801
802
803  /**
804   * Appends an LDIF representation of this entry to the provided buffer.  Long
805   * lines will not be wrapped.
806   *
807   * @param  buffer The buffer to which the LDIF representation of this entry
808   *                should be written.
809   */
810  public void toLDIF(@NotNull final ByteStringBuffer buffer)
811  {
812    toLDIF(buffer, 0);
813  }
814
815
816
817  /**
818   * Appends an LDIF representation of this entry to the provided buffer.
819   *
820   * @param  buffer      The buffer to which the LDIF representation of this
821   *                     entry should be written.
822   * @param  wrapColumn  The column at which long lines should be wrapped.  A
823   *                     value less than or equal to two indicates that no
824   *                     wrapping should be performed.
825   */
826  public void toLDIF(@NotNull final ByteStringBuffer buffer,
827                     final int wrapColumn)
828  {
829    toEntry().toLDIF(buffer, wrapColumn);
830  }
831
832
833
834  /**
835   * Retrieves an LDIF-formatted string representation of this entry.  No
836   * wrapping will be performed, and no extra blank lines will be added.
837   *
838   * @return  An LDIF-formatted string representation of this entry.
839   */
840  @NotNull()
841  public String toLDIFString()
842  {
843    final StringBuilder buffer = new StringBuilder();
844    toLDIFString(buffer, 0);
845    return buffer.toString();
846  }
847
848
849
850  /**
851   * Retrieves an LDIF-formatted string representation of this entry.  No
852   * extra blank lines will be added.
853   *
854   * @param  wrapColumn  The column at which long lines should be wrapped.  A
855   *                     value less than or equal to two indicates that no
856   *                     wrapping should be performed.
857   *
858   * @return  An LDIF-formatted string representation of this entry.
859   */
860  @NotNull()
861  public String toLDIFString(final int wrapColumn)
862  {
863    final StringBuilder buffer = new StringBuilder();
864    toLDIFString(buffer, wrapColumn);
865    return buffer.toString();
866  }
867
868
869
870  /**
871   * Appends an LDIF-formatted string representation of this entry to the
872   * provided buffer.  No wrapping will be performed, and no extra blank lines
873   * will be added.
874   *
875   * @param  buffer  The buffer to which to append the LDIF representation of
876   *                 this entry.
877   */
878  public void toLDIFString(@NotNull final StringBuilder buffer)
879  {
880    toLDIFString(buffer, 0);
881  }
882
883
884
885  /**
886   * Appends an LDIF-formatted string representation of this entry to the
887   * provided buffer.  No extra blank lines will be added.
888   *
889   * @param  buffer      The buffer to which to append the LDIF representation
890   *                     of this entry.
891   * @param  wrapColumn  The column at which long lines should be wrapped.  A
892   *                     value less than or equal to two indicates that no
893   *                     wrapping should be performed.
894   */
895  public void toLDIFString(@NotNull final StringBuilder buffer,
896                           final int wrapColumn)
897  {
898    toEntry().toLDIFString(buffer, wrapColumn);
899  }
900
901
902
903  /**
904   * Retrieves a string representation of this entry.
905   *
906   * @return  A string representation of this entry.
907   */
908  @Override()
909  @NotNull()
910  public String toString()
911  {
912    final StringBuilder buffer = new StringBuilder();
913    toString(buffer);
914    return buffer.toString();
915  }
916
917
918
919  /**
920   * Appends a string representation of this entry to the provided buffer.
921   *
922   * @param  buffer  The buffer to which to append the string representation of
923   *                 this entry.
924   */
925  public void toString(@NotNull final StringBuilder buffer)
926  {
927    buffer.append("Entry(dn='");
928    buffer.append(dn);
929    buffer.append("', attributes={");
930
931    for (int i=0; i < attributes.length; i++)
932    {
933      if (i > 0)
934      {
935        buffer.append(", ");
936      }
937      attributes[i].toAttribute().toString(buffer);
938    }
939
940    buffer.append("})");
941  }
942}