001    /*
002     * CDDL HEADER START
003     *
004     * The contents of this file are subject to the terms of the
005     * Common Development and Distribution License, Version 1.0 only
006     * (the "License").  You may not use this file except in compliance
007     * with the License.
008     *
009     * You can obtain a copy of the license at
010     * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011     * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012     * See the License for the specific language governing permissions
013     * and limitations under the License.
014     *
015     * When distributing Covered Code, include this CDDL HEADER in each
016     * file and include the License file at
017     * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
018     * add the following below this CDDL HEADER, with the fields enclosed
019     * by brackets "[]" replaced with your own identifying information:
020     *      Portions Copyright [yyyy] [name of copyright owner]
021     *
022     * CDDL HEADER END
023     *
024     *
025     *      Copyright 2006-2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.types;
028    
029    
030    
031    import java.util.ArrayList;
032    import java.util.Collection;
033    import java.util.LinkedHashSet;
034    import java.util.List;
035    import java.util.Set;
036    
037    import org.opends.server.api.ApproximateMatchingRule;
038    import org.opends.server.api.OrderingMatchingRule;
039    import org.opends.server.api.SubstringMatchingRule;
040    import org.opends.server.core.DirectoryServer;
041    import org.opends.server.util.Base64;
042    
043    import static org.opends.server.loggers.debug.DebugLogger.*;
044    import org.opends.server.loggers.debug.DebugTracer;
045    import static org.opends.server.util.ServerConstants.*;
046    import static org.opends.server.util.StaticUtils.*;
047    
048    
049    
050    /**
051     * This class defines a data structure for storing and interacting
052     * with an attribute that may be used in the Directory Server.
053     */
054    @org.opends.server.types.PublicAPI(
055         stability=org.opends.server.types.StabilityLevel.UNCOMMITTED,
056         mayInstantiate=true,
057         mayExtend=false,
058         mayInvoke=true)
059    public class Attribute
060    {
061      /**
062       * The tracer object for the debug logger.
063       */
064      private static final DebugTracer TRACER = getTracer();
065    
066      // The attribute type for this attribute.
067      private final AttributeType attributeType;
068    
069      // The set of values for this attribute.
070      private LinkedHashSet<AttributeValue> values;
071    
072      // The set of options for this attribute.
073      private final LinkedHashSet<String> options;
074    
075      // The set of options for this attribute, formatted in all lowercase
076      // characters.
077      private final LinkedHashSet<String> lowerOptions;
078    
079      // The name of this attribute as provided by the end user.
080      private final String name;
081    
082    
083    
084      /**
085       * Creates a new attribute with the specified type.  It will not
086       * have any values.
087       *
088       * @param  attributeType  The attribute type for this attribute.
089       */
090      public Attribute(AttributeType attributeType)
091      {
092        this.attributeType = attributeType;
093        this.name          = attributeType.getPrimaryName();
094        this.options       = new LinkedHashSet<String>(0);
095        this.values        = new LinkedHashSet<AttributeValue>();
096    
097        lowerOptions = options;
098      }
099    
100    
101    
102      /**
103       * Creates a new attribute with the specified type and user-provided
104       * name.  It will not have any values.
105       *
106       * @param  attributeType  The attribute type for this attribute.
107       * @param  name           The user-provided name for this attribute.
108       */
109      public Attribute(AttributeType attributeType, String name)
110      {
111        this.attributeType = attributeType;
112        this.name          = name;
113        this.options       = new LinkedHashSet<String>(0);
114        this.values        = new LinkedHashSet<AttributeValue>();
115    
116        lowerOptions = options;
117      }
118    
119    
120    
121      /**
122       * Creates a new attribute with the specified type, user-provided
123       * name, and set of values.
124       *
125       * @param  attributeType  The attribute type for this attribute.
126       * @param  name           The user-provided name for this attribute.
127       * @param  values         The set of values for this attribute.
128       */
129      public Attribute(AttributeType attributeType, String name,
130                       LinkedHashSet<AttributeValue> values)
131      {
132        this.attributeType = attributeType;
133        this.name          = name;
134        this.options       = new LinkedHashSet<String>(0);
135    
136        lowerOptions = options;
137    
138        if (values == null)
139        {
140          this.values = new LinkedHashSet<AttributeValue>();
141        }
142        else
143        {
144          this.values = values;
145        }
146      }
147    
148    
149    
150      /**
151       * Creates a new attribute with the specified name and value.
152       *
153       * @param  lowerName    The name or OID of the attribute type for
154       *                      this attribute, formatted in all lowercase
155       *                      characters.
156       * @param  valueString  The String representation of the attribute
157       *                      value.
158       */
159      public Attribute(String lowerName, String valueString)
160      {
161        this.attributeType =
162             DirectoryServer.getAttributeType(lowerName, true);
163        this.name = lowerName;
164        this.values = new LinkedHashSet<AttributeValue>();
165        this.values.add(new AttributeValue(this.attributeType,
166                                           valueString));
167        this.options = new LinkedHashSet<String>(0);
168    
169        lowerOptions = options;
170      }
171    
172    
173    
174      /**
175       * Creates a new attribute with the specified type, user-provided
176       * name, and set of values.
177       *
178       * @param  attributeType  The attribute type for this attribute.
179       * @param  name           The user-provided name for this attribute.
180       * @param  options        The set of options for this attribute.
181       * @param  values         The set of values for this attribute.
182       */
183      public Attribute(AttributeType attributeType, String name,
184                       LinkedHashSet<String> options,
185                       LinkedHashSet<AttributeValue> values)
186      {
187        this.attributeType = attributeType;
188        this.name          = name;
189    
190        if ((options == null) || options.isEmpty())
191        {
192          this.options = new LinkedHashSet<String>(0);
193          lowerOptions = options;
194        }
195        else
196        {
197          this.options = options;
198          lowerOptions = new LinkedHashSet<String>(options.size());
199          for (String option : options)
200          {
201            lowerOptions.add(toLowerCase(option));
202          }
203        }
204    
205        if (values == null)
206        {
207          this.values = new LinkedHashSet<AttributeValue>(0);
208        }
209        else
210        {
211          this.values = values;
212        }
213      }
214    
215    
216    
217      /**
218       * Retrieves the attribute type for this attribute.
219       *
220       * @return  The attribute type for this attribute.
221       */
222      public AttributeType getAttributeType()
223      {
224        return attributeType;
225      }
226    
227    
228    
229      /**
230       * Retrieves the user-provided name for this attribute.
231       *
232       * @return  The user-provided name for this attribute.
233       */
234      public String getName()
235      {
236        return name;
237      }
238    
239    
240    
241      /**
242       * Retrieves the user-provided name of the attribute, along with any
243       * options that might have been provided.
244       *
245       * @return  The user-provided name of the attribute, along with any
246       *          options that might have been provided.
247       */
248      public String getNameWithOptions()
249      {
250        if (options.isEmpty())
251        {
252          return name;
253        }
254        else
255        {
256          StringBuilder buffer = new StringBuilder();
257          buffer.append(name);
258          for (String option : options)
259          {
260            buffer.append(';');
261            buffer.append(option);
262          }
263          return buffer.toString();
264        }
265      }
266    
267    
268    
269      /**
270       * Retrieves the set of attribute options for this attribute.
271       *
272       * @return  The set of attribute options for this attribute.
273       */
274      public LinkedHashSet<String> getOptions()
275      {
276        return options;
277      }
278    
279    
280    
281      /**
282       * Indicates whether this attribute has the specified option.
283       *
284       * @param  option  The option for which to make the determination.
285       *
286       * @return  <CODE>true</CODE> if this attribute has the specified
287       *          option, or <CODE>false</CODE> if not.
288       */
289      public boolean hasOption(String option)
290      {
291        return lowerOptions.contains(toLowerCase(option));
292      }
293    
294    
295    
296      /**
297       * Indicates whether this attribute has any options at all.
298       *
299       * @return  <CODE>true</CODE> if this attribute has at least one
300       *          option, or <CODE>false</CODE> if not.
301       */
302      public boolean hasOptions()
303      {
304        return (! options.isEmpty());
305      }
306    
307    
308    
309      /**
310       * Indicates whether this attribute has all of the options in the
311       * provided collection.
312       *
313       * @param  options  The collection of options for which to make the
314       *                  determination.
315       *
316       * @return  <CODE>true</CODE> if this attribute has all of the
317       *          specified options, or <CODE>false</CODE> if it does not
318       *          have at least one of them.
319       */
320      public boolean hasOptions(Collection<String> options)
321      {
322        if (options == null)
323        {
324          return true;
325        }
326    
327        for (String option : options)
328        {
329          if (! lowerOptions.contains(toLowerCase(option)))
330          {
331            return false;
332          }
333        }
334    
335        return true;
336      }
337    
338    
339    
340      /**
341       * Indicates whether this attribute has exactly the set of options
342       * in the provided set.
343       *
344       * @param  options  The set of options for which to make the
345       *                  determination.
346       *
347       * @return  <CODE>true</CODE> if this attribute has exactly the
348       *          specified set of options, or <CODE>false</CODE> if the
349       *          set of options is different in any way.
350       */
351      public boolean optionsEqual(Set<String> options)
352      {
353        if (options == null)
354        {
355          return this.options == null || this.options.isEmpty();
356        }
357    
358        if (options.isEmpty() && this.options.isEmpty())
359        {
360          return true;
361        }
362    
363        if (options.size() != this.options.size())
364        {
365          return false;
366        }
367    
368        for (String s : options)
369        {
370          if (! lowerOptions.contains(toLowerCase(s)))
371          {
372            return false;
373          }
374        }
375    
376        return true;
377      }
378    
379    
380    
381      /**
382       * Retrieves the set of values for this attribute.  The returned set
383       * of values may be altered by the caller.
384       *
385       * @return  The set of values for this attribute.
386       */
387      public LinkedHashSet<AttributeValue> getValues()
388      {
389        return values;
390      }
391    
392    
393    
394      /**
395       * Specifies the set of values for this attribute.
396       *
397       * @param  values  The set of values for this attribute.
398       */
399      public void setValues(LinkedHashSet<AttributeValue> values)
400      {
401        if (values == null)
402        {
403          this.values = new LinkedHashSet<AttributeValue>();
404        }
405        else
406        {
407          this.values = values;
408        }
409      }
410    
411    
412    
413      /**
414       * Indicates whether this attribute contains one or more values.
415       *
416       * @return  <CODE>true</CODE> if this attribute contains one or more
417       *          values, or <CODE>false</CODE> if it does not.
418       */
419      public boolean hasValue()
420      {
421        return (! getValues().isEmpty());
422      }
423    
424    
425    
426      /**
427       * Indicates whether this attribute contains the specified value.
428       *
429       * @param  value  The value for which to make the determination.
430       *
431       * @return  <CODE>true</CODE> if this attribute has the specified
432       *          value, or <CODE>false</CODE> if not.
433       */
434      public boolean hasValue(AttributeValue value)
435      {
436        return getValues().contains(value);
437      }
438    
439    
440    
441      /**
442       * Indicates whether this attribute contains all the values in the
443       * collection.
444       *
445       * @param  values  The set of values for which to make the
446       *                 determination.
447       *
448       * @return  <CODE>true</CODE> if this attribute contains all the
449       *          values in the provided collection, or <CODE>false</CODE>
450       *          if it does not contain at least one of them.
451       */
452      public boolean hasAllValues(Collection<AttributeValue> values)
453      {
454        for (AttributeValue value : values)
455        {
456          if (! getValues().contains(value))
457          {
458            return false;
459          }
460        }
461    
462        return true;
463      }
464    
465    
466    
467      /**
468       * Indicates whether this attribute contains any of the values in
469       * the collection.
470       *
471       * @param  values  The set of values for which to make the
472       *                 determination.
473       *
474       * @return  <CODE>true</CODE> if this attribute contains at least
475       *          one of the values in the provided collection, or
476       *          <CODE>false</CODE> if it does not contain any of the
477       *          values.
478       */
479      public boolean hasAnyValue(Collection<AttributeValue> values)
480      {
481        for (AttributeValue value : values)
482        {
483          if (getValues().contains(value))
484          {
485            return true;
486          }
487        }
488    
489        return false;
490      }
491    
492    
493    
494      /**
495       * Indicates whether this attribute has any value(s) that match the
496       * provided substring.
497       *
498       * @param  subInitial  The subInitial component to use in the
499       *                     determination.
500       * @param  subAny      The subAny components to use in the
501       *                     determination.
502       * @param  subFinal    The subFinal component to use in the
503       *                     determination.
504       *
505       * @return  <CODE>UNDEFINED</CODE> if this attribute does not have a
506       *          substring matching rule, <CODE>TRUE</CODE> if at least
507       *          one value matches the provided substring, or
508       *          <CODE>FALSE</CODE> otherwise.
509       */
510      public ConditionResult matchesSubstring(ByteString subInitial,
511                                              List<ByteString> subAny,
512                                              ByteString subFinal)
513      {
514        SubstringMatchingRule matchingRule =
515             attributeType.getSubstringMatchingRule();
516        if (matchingRule == null)
517        {
518          return ConditionResult.UNDEFINED;
519        }
520    
521    
522        ByteString normalizedSubInitial;
523        if (subInitial == null)
524        {
525          normalizedSubInitial = null;
526        }
527        else
528        {
529          try
530          {
531            normalizedSubInitial =
532                 matchingRule.normalizeSubstring(subInitial);
533          }
534          catch (Exception e)
535          {
536            if (debugEnabled())
537            {
538              TRACER.debugCaught(DebugLogLevel.ERROR, e);
539            }
540    
541            // The substring couldn't be normalized.  We have to return
542            // "undefined".
543            return ConditionResult.UNDEFINED;
544          }
545        }
546    
547    
548        ArrayList<ByteString> normalizedSubAny;
549        if (subAny == null)
550        {
551          normalizedSubAny = null;
552        }
553        else
554        {
555          normalizedSubAny =
556               new ArrayList<ByteString>(subAny.size());
557          for (ByteString subAnyElement : subAny)
558          {
559            try
560            {
561              normalizedSubAny.add(matchingRule.normalizeSubstring(
562                                                     subAnyElement));
563            }
564            catch (Exception e)
565            {
566              if (debugEnabled())
567              {
568                TRACER.debugCaught(DebugLogLevel.ERROR, e);
569              }
570    
571              // The substring couldn't be normalized.  We have to return
572              // "undefined".
573              return ConditionResult.UNDEFINED;
574            }
575          }
576        }
577    
578    
579        ByteString normalizedSubFinal;
580        if (subFinal == null)
581        {
582          normalizedSubFinal = null;
583        }
584        else
585        {
586          try
587          {
588            normalizedSubFinal =
589                 matchingRule.normalizeSubstring(subFinal);
590          }
591          catch (Exception e)
592          {
593            if (debugEnabled())
594            {
595              TRACER.debugCaught(DebugLogLevel.ERROR, e);
596            }
597    
598            // The substring couldn't be normalized.  We have to return
599            // "undefined".
600            return ConditionResult.UNDEFINED;
601          }
602        }
603    
604    
605        ConditionResult result = ConditionResult.FALSE;
606        for (AttributeValue value : getValues())
607        {
608          try
609          {
610            if (matchingRule.valueMatchesSubstring(
611                                  value.getNormalizedValue(),
612                                  normalizedSubInitial,
613                                  normalizedSubAny,
614                                  normalizedSubFinal))
615            {
616              return ConditionResult.TRUE;
617            }
618          }
619          catch (Exception e)
620          {
621            if (debugEnabled())
622            {
623              TRACER.debugCaught(DebugLogLevel.ERROR, e);
624            }
625    
626            // The value couldn't be normalized.  If we can't find a
627            // definite match, then we should return "undefined".
628            result = ConditionResult.UNDEFINED;
629          }
630        }
631    
632        return result;
633      }
634    
635    
636    
637      /**
638       * Indicates whether this attribute has any value(s) that are
639       * greater than or equal to the provided value.
640       *
641       * @param  value  The value for which to make the determination.
642       *
643       * @return  <CODE>UNDEFINED</CODE> if this attribute does not have
644       *          an ordering matching rule, <CODE>TRUE</CODE> if at least
645       *          one value is greater than or equal to the provided
646       *          value, or <CODE>false</CODE> otherwise.
647       */
648      public ConditionResult greaterThanOrEqualTo(AttributeValue value)
649      {
650        OrderingMatchingRule matchingRule =
651             attributeType.getOrderingMatchingRule();
652        if (matchingRule == null)
653        {
654          return ConditionResult.UNDEFINED;
655        }
656    
657        ByteString normalizedValue;
658        try
659        {
660          normalizedValue = value.getNormalizedValue();
661        }
662        catch (Exception e)
663        {
664          if (debugEnabled())
665          {
666            TRACER.debugCaught(DebugLogLevel.ERROR, e);
667          }
668    
669          // We couldn't normalize the provided value.  We should return
670          // "undefined".
671          return ConditionResult.UNDEFINED;
672        }
673    
674        ConditionResult result = ConditionResult.FALSE;
675        for (AttributeValue v : getValues())
676        {
677          try
678          {
679            ByteString nv = v.getNormalizedValue();
680            int comparisonResult =
681                     matchingRule.compareValues(nv, normalizedValue);
682            if (comparisonResult >= 0)
683            {
684              return ConditionResult.TRUE;
685            }
686          }
687          catch (Exception e)
688          {
689            if (debugEnabled())
690            {
691              TRACER.debugCaught(DebugLogLevel.ERROR, e);
692            }
693    
694            // We couldn't normalize one of the attribute values.  If we
695            // can't find a definite match, then we should return
696            // "undefined".
697            result = ConditionResult.UNDEFINED;
698          }
699        }
700    
701        return result;
702      }
703    
704    
705    
706      /**
707       * Indicates whether this attribute has any value(s) that are less
708       * than or equal to the provided value.
709       *
710       * @param  value  The value for which to make the determination.
711       *
712       * @return  <CODE>UNDEFINED</CODE> if this attribute does not have
713       *          an ordering matching rule, <CODE>TRUE</CODE> if at least
714       *          one value is less than or equal to the provided value,
715       *          or <CODE>false</CODE> otherwise.
716       */
717      public ConditionResult lessThanOrEqualTo(AttributeValue value)
718      {
719        OrderingMatchingRule matchingRule =
720             attributeType.getOrderingMatchingRule();
721        if (matchingRule == null)
722        {
723          return ConditionResult.UNDEFINED;
724        }
725    
726        ByteString normalizedValue;
727        try
728        {
729          normalizedValue = value.getNormalizedValue();
730        }
731        catch (Exception e)
732        {
733          if (debugEnabled())
734          {
735            TRACER.debugCaught(DebugLogLevel.ERROR, e);
736          }
737    
738          // We couldn't normalize the provided value.  We should return
739          // "undefined".
740          return ConditionResult.UNDEFINED;
741        }
742    
743        ConditionResult result = ConditionResult.FALSE;
744        for (AttributeValue v : getValues())
745        {
746          try
747          {
748            ByteString nv = v.getNormalizedValue();
749            int comparisonResult =
750                     matchingRule.compareValues(nv, normalizedValue);
751            if (comparisonResult <= 0)
752            {
753              return ConditionResult.TRUE;
754            }
755          }
756          catch (Exception e)
757          {
758            if (debugEnabled())
759            {
760              TRACER.debugCaught(DebugLogLevel.ERROR, e);
761            }
762    
763            // We couldn't normalize one of the attribute values.  If we
764            // can't find a definite match, then we should return
765            // "undefined".
766            result = ConditionResult.UNDEFINED;
767          }
768        }
769    
770        return result;
771      }
772    
773    
774    
775      /**
776       * Indicates whether this attribute has any value(s) that are
777       * approximately equal to the provided value.
778       *
779       * @param  value  The value for which to make the determination.
780       *
781       * @return  <CODE>UNDEFINED</CODE> if this attribute does not have
782       *          an approximate matching rule, <CODE>TRUE</CODE> if at
783       *          least one value is approximately equal to the provided
784       *          value, or <CODE>false</CODE> otherwise.
785       */
786      public ConditionResult approximatelyEqualTo(AttributeValue value)
787      {
788        ApproximateMatchingRule matchingRule =
789             attributeType.getApproximateMatchingRule();
790        if (matchingRule == null)
791        {
792          return ConditionResult.UNDEFINED;
793        }
794    
795        ByteString normalizedValue;
796        try
797        {
798          normalizedValue = matchingRule.normalizeValue(value.getValue());
799        }
800        catch (Exception e)
801        {
802          if (debugEnabled())
803          {
804            TRACER.debugCaught(DebugLogLevel.ERROR, e);
805          }
806    
807          // We couldn't normalize the provided value.  We should return
808          // "undefined".
809          return ConditionResult.UNDEFINED;
810        }
811    
812        ConditionResult result = ConditionResult.FALSE;
813        for (AttributeValue v : getValues())
814        {
815          try
816          {
817            ByteString nv = matchingRule.normalizeValue(v.getValue());
818            if (matchingRule.approximatelyMatch(nv, normalizedValue))
819            {
820              return ConditionResult.TRUE;
821            }
822          }
823          catch (Exception e)
824          {
825            if (debugEnabled())
826            {
827              TRACER.debugCaught(DebugLogLevel.ERROR, e);
828            }
829    
830            // We couldn't normalize one of the attribute values.  If we
831            // can't find a definite match, then we should return
832            // "undefined".
833            result = ConditionResult.UNDEFINED;
834          }
835        }
836    
837        return result;
838      }
839    
840    
841    
842      /**
843       * Indicates whether this is a virtual attribute rather than a real
844       * attribute.
845       *
846       * @return  {@code true} if this is a virtual attribute, or
847       *          {@code false} if it is a real attribute.
848       */
849      public boolean isVirtual()
850      {
851        return false;
852      }
853    
854    
855    
856      /**
857       * Creates a duplicate of this attribute that can be modified
858       * without impacting this attribute.
859       *
860       * @return  A duplicate of this attribute that can be modified
861       *          without impacting this attribute.
862       */
863      public Attribute duplicate()
864      {
865        return duplicate(false);
866      }
867    
868    
869      /**
870       * Creates a duplicate of this attribute that can be modified
871       * without impacting this attribute.
872       *
873       * @param omitValues <CODE>true</CODE> if the values should be
874       *        omitted.
875       *
876       * @return  A duplicate of this attribute that can be modified
877       *          without impacting this attribute.
878       */
879      public Attribute duplicate(boolean omitValues)
880      {
881        LinkedHashSet<String> optionsCopy =
882             new LinkedHashSet<String>(options);
883    
884        if (omitValues)
885        {
886          return new Attribute(attributeType, name, optionsCopy, null);
887        }
888        else
889        {
890          LinkedHashSet<AttributeValue> valuesCopy =
891               new LinkedHashSet<AttributeValue>(getValues());
892    
893          return new Attribute(attributeType, name, optionsCopy,
894                               valuesCopy);
895        }
896      }
897    
898    
899      /**
900       * Indicates whether the provided object is an attribute that is
901       * equal to this attribute.  It will be considered equal if the
902       * attribute type, set of values, and set of options are equal.
903       *
904       * @param  o  The object for which to make the determination.
905       *
906       * @return  <CODE>true</CODE> if the provided object is an attribute
907       *          that is equal to this attribute, or <CODE>false</CODE>
908       *          if not.
909       */
910      public boolean equals(Object o)
911      {
912        if (this == o)
913        {
914          return true;
915        }
916    
917        if ((o == null) || (! (o instanceof Attribute)))
918        {
919          return false;
920        }
921    
922        Attribute a = (Attribute) o;
923        if (! attributeType.equals(a.attributeType))
924        {
925          return false;
926        }
927    
928        if (getValues().size() != a.getValues().size())
929        {
930          return false;
931        }
932    
933        if (! hasAllValues(a.getValues()))
934        {
935          return false;
936        }
937    
938        return optionsEqual(a.options);
939      }
940    
941    
942    
943      /**
944       * Retrieves the hash code for this attribute.  It will be
945       * calculated as the sum of the hash code for the attribute type and
946       * all values.
947       *
948       * @return  The hash code for this attribute.
949       */
950      public int hashCode()
951      {
952        int hashCode = attributeType.hashCode();
953        for (AttributeValue value : getValues())
954        {
955          hashCode += value.hashCode();
956        }
957    
958        return hashCode;
959      }
960    
961    
962    
963      /**
964       * Retrieves a one-line string representation of this attribute.
965       *
966       * @return  A one-line string representation of this attribute.
967       */
968      public String toString()
969      {
970        StringBuilder buffer = new StringBuilder();
971        toString(buffer);
972        return buffer.toString();
973      }
974    
975    
976    
977      /**
978       * Appends a one-line string representation of this attribute to the
979       * provided buffer.
980       *
981       * @param  buffer  The buffer to which the information should be
982       *                 appended.
983       */
984      public void toString(StringBuilder buffer)
985      {
986        buffer.append("Attribute(");
987        buffer.append(name);
988        buffer.append(", {");
989    
990        boolean firstValue = true;
991        for (AttributeValue value : getValues())
992        {
993          if (! firstValue)
994          {
995            buffer.append(", ");
996          }
997    
998          value.toString(buffer);
999          firstValue = false;
1000        }
1001    
1002        buffer.append("})");
1003      }
1004    
1005    
1006    
1007      /**
1008       * Retrieves a string representation of this attribute in LDIF form.
1009       *
1010       * @return  A string representation of this attribute in LDIF form.
1011       */
1012      public String toLDIF()
1013      {
1014        StringBuilder buffer = new StringBuilder();
1015        toLDIF(buffer);
1016        return buffer.toString();
1017      }
1018    
1019    
1020    
1021      /**
1022       * Appends a string representation of this attribute in LDIF form to
1023       * the provided buffer.
1024       *
1025       * @param  buffer  The buffer to which the information should be
1026       *                 appended.
1027       */
1028      public void toLDIF(StringBuilder buffer)
1029      {
1030        for (AttributeValue value : getValues())
1031        {
1032          buffer.append(name);
1033    
1034          if (needsBase64Encoding(value.getValueBytes()))
1035          {
1036            buffer.append("::");
1037            buffer.append(Base64.encode(value.getValueBytes()));
1038          }
1039          else
1040          {
1041            buffer.append(": ");
1042            buffer.append(value.getStringValue());
1043          }
1044    
1045          buffer.append(EOL);
1046        }
1047      }
1048    }
1049