View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *  https://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.directory.api.ldap.model.entry;
20  
21  
22  import java.io.IOException;
23  import java.io.ObjectInput;
24  import java.io.ObjectOutput;
25  import java.util.Iterator;
26  import java.util.LinkedHashSet;
27  import java.util.Set;
28  
29  import org.apache.directory.api.asn1.util.Oid;
30  import org.apache.directory.api.i18n.I18n;
31  import org.apache.directory.api.ldap.model.exception.LdapException;
32  import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException;
33  import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
34  import org.apache.directory.api.ldap.model.schema.AttributeType;
35  import org.apache.directory.api.ldap.model.schema.LdapSyntax;
36  import org.apache.directory.api.ldap.model.schema.SyntaxChecker;
37  import org.apache.directory.api.util.Strings;
38  import org.slf4j.Logger;
39  import org.slf4j.LoggerFactory;
40  
41  
42  /**
43   * An LDAP attribute.<p>
44   * To define the kind of data stored, the client must set the isHR flag, or inject an AttributeType.
45   *
46   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
47   */
48  public class DefaultAttribute implements Attribute, Cloneable
49  {
50      /** logger for reporting errors that might not be handled properly upstream */
51      private static final Logger LOG = LoggerFactory.getLogger( DefaultAttribute.class );
52  
53      /** The associated AttributeType */
54      private AttributeType attributeType;
55  
56      /** The set of contained values */
57      private Set<Value> values = new LinkedHashSet<>();
58  
59      /** The User provided ID */
60      private String upId;
61  
62      /** The normalized ID (will be the OID if we have a AttributeType) */
63      private String id;
64  
65      /** Tells if the attribute is Human Readable or not. When not set,
66       * this flag is null. */
67      private Boolean isHR;
68  
69      /** The computed hashcode. We don't want to compute it each time the hashcode() method is called */
70      private volatile int h;
71      
72      
73      //-------------------------------------------------------------------------
74      // Constructors
75      //-------------------------------------------------------------------------
76      // maybe have some additional convenience constructors which take
77      // an initial value as a string or a byte[]
78      /**
79       * Create a new instance of a Attribute, without ID nor value.
80       * Used by the serializer
81       */
82      /* No protection*/DefaultAttribute()
83      {
84      }
85  
86  
87      /**
88       * Create a new instance of a schema aware Attribute.
89       * Used by the serializer.
90       * 
91       * @param attributeType The associated AttributeType
92       * @param upId The user provided ID
93       * @param normId the normalized ID
94       * @param isHR tells if the attributeType is human readable
95       * @param hashCode The Attribute pre-computed hashcode
96       * @param values The associated values
97       */
98      /* No protection*/DefaultAttribute( AttributeType attributeType, String upId, String normId, boolean isHR,
99          int hashCode, Value... values )
100     {
101         this.attributeType = attributeType;
102         this.upId = upId;
103         this.id = normId;
104         this.isHR = isHR;
105         this.h = hashCode;
106 
107         if ( values != null )
108         {
109             for ( Value value : values )
110             {
111                 this.values.add( value );
112             }
113         }
114     }
115 
116 
117     /**
118      * Create a new instance of a schema aware Attribute, without ID nor value.
119      * 
120      * @param attributeType the attributeType for the empty attribute added into the entry
121      */
122     public DefaultAttribute( AttributeType attributeType )
123     {
124         if ( attributeType != null )
125         {
126             try
127             {
128                 apply( attributeType );
129             }
130             catch ( LdapInvalidAttributeValueException liave )
131             {
132                 // Do nothing, it can't happen, there is no value
133                 liave.printStackTrace();
134             }
135         }
136     }
137 
138 
139     /**
140      * Create a new instance of an Attribute, without value.
141      * @param upId The user provided ID
142      */
143     public DefaultAttribute( String upId )
144     {
145         setUpId( upId );
146     }
147 
148 
149     /**
150      * Create a new instance of an Attribute, without value.
151      * @param upId The user provided ID
152      */
153     public DefaultAttribute( byte[] upId )
154     {
155         setUpId( upId );
156     }
157 
158 
159     /**
160      * Create a new instance of a schema aware Attribute, without value.
161      * 
162      * @param upId the ID for the added attributeType
163      * @param attributeType the added AttributeType
164      */
165     public DefaultAttribute( String upId, AttributeType attributeType )
166     {
167         if ( attributeType == null )
168         {
169             String message = I18n.err( I18n.ERR_13203_ATTRIBUTE_TYPE_NULL_NOT_ALLOWED );
170             LOG.error( message );
171             throw new IllegalArgumentException( message );
172         }
173 
174         try
175         {
176             apply( attributeType );
177         }
178         catch ( LdapInvalidAttributeValueException liave )
179         {
180             // Do nothing, it can't happen, there is no value
181         }
182 
183         setUpId( upId, attributeType );
184     }
185 
186 
187     /**
188      * Create a new instance of an Attribute, with some values, and a user provided ID.<br>
189      * If the value does not correspond to the same attributeType, then it's
190      * wrapped value is copied into a new ClientValue which uses the specified
191      * attributeType.
192      * <p>
193      * Otherwise, the value is stored, but as a reference. It's not a copy.
194      * </p>
195      * @param upId the attributeType ID
196      * @param vals an initial set of values for this attribute
197      */
198     public DefaultAttribute( String upId, Value... vals )
199     {
200         // The value can be null, this is a valid value.
201         if ( vals[0] == null )
202         {
203             add( new Value( ( String ) null ) );
204         }
205         else
206         {
207             for ( Value val : vals )
208             {
209                 add( val );
210             }
211         }
212 
213         setUpId( upId );
214     }
215 
216 
217     /**
218      * Create a new instance of a schema aware Attribute, without ID but with some values.
219      * 
220      * @param attributeType The attributeType added on creation
221      * @param vals The added value for this attribute
222      * @throws LdapInvalidAttributeValueException If any of the
223      * added values is not valid
224      */
225     public DefaultAttribute( AttributeType attributeType, String... vals ) throws LdapInvalidAttributeValueException
226     {
227         this( null, attributeType, vals );
228     }
229 
230 
231     /**
232      * Create a new instance of a schema aware Attribute, with some values, and a user provided ID.
233      * 
234      * @param upId the ID for the created attribute
235      * @param attributeType The attributeType added on creation
236      * @param vals the added values for this attribute
237      * @throws LdapInvalidAttributeValueException If any of the
238      * added values is not valid
239      */
240     public DefaultAttribute( String upId, AttributeType attributeType, String... vals )
241         throws LdapInvalidAttributeValueException
242     {
243         if ( attributeType == null )
244         {
245             String message = I18n.err( I18n.ERR_13203_ATTRIBUTE_TYPE_NULL_NOT_ALLOWED );
246             LOG.error( message );
247             throw new IllegalArgumentException( message );
248         }
249 
250         apply( attributeType );
251 
252         if ( ( vals != null ) && ( vals.length > 0 ) )
253         {
254             add( vals );
255         }
256 
257         setUpId( upId, attributeType );
258     }
259 
260 
261     /**
262      * Create a new instance of a schema aware Attribute, with some values, and a user provided ID.<br>
263      * If the value does not correspond to the same attributeType, then it's
264      * wrapped value is copied into a new Value which uses the specified
265      * attributeType.
266      * <p>
267      * Otherwise, the value is stored, but as a reference. It's not a copy.
268      * </p>
269      * @param upId the ID of the created attribute
270      * @param attributeType the attribute type according to the schema
271      * @param vals an initial set of values for this attribute
272      * @throws LdapInvalidAttributeValueException If any of the
273      * added values is not valid
274      */
275     public DefaultAttribute( String upId, AttributeType attributeType, Value... vals )
276         throws LdapInvalidAttributeValueException
277     {
278         if ( attributeType == null )
279         {
280             String message = I18n.err( I18n.ERR_13203_ATTRIBUTE_TYPE_NULL_NOT_ALLOWED );
281             LOG.error( message );
282             throw new IllegalArgumentException( message );
283         }
284 
285         apply( attributeType );
286         setUpId( upId, attributeType );
287         add( vals );
288     }
289 
290 
291     /**
292      * Create a new instance of a schema aware Attribute, with some values.
293      * <p>
294      * If the value does not correspond to the same attributeType, then it's
295      * wrapped value is copied into a new Value which uses the specified
296      * attributeType.
297      * </p>
298      * @param attributeType the attribute type according to the schema
299      * @param vals an initial set of values for this attribute
300      * @throws LdapInvalidAttributeValueException If one of the value is invalid
301      */
302     public DefaultAttribute( AttributeType attributeType, Value... vals ) throws LdapInvalidAttributeValueException
303     {
304         this( null, attributeType, vals );
305     }
306 
307 
308     /**
309      * Create a new instance of an Attribute, with some String values, and a user provided ID.
310      * 
311      * @param upId the ID of the created attribute
312      * @param vals an initial set of String values for this attribute
313      */
314     public DefaultAttribute( String upId, String... vals )
315     {
316         try
317         {
318             add( vals );
319         }
320         catch ( LdapInvalidAttributeValueException liave )
321         {
322             // Do nothing, it can't happen
323         }
324 
325         setUpId( upId );
326     }
327 
328 
329     /**
330      * Create a new instance of an Attribute, with some binary values, and a user provided ID.
331      * 
332      * @param upId the ID of the created attribute
333      * @param vals an initial set of binary values for this attribute
334      */
335     public DefaultAttribute( String upId, byte[]... vals )
336     {
337         try
338         {
339             add( vals );
340         }
341         catch ( LdapInvalidAttributeValueException liave )
342         {
343             // Do nothing, this can't happen
344         }
345 
346         setUpId( upId );
347     }
348 
349 
350     /**
351      * Create a new instance of a schema aware Attribute, with some byte[] values.
352      * 
353      * @param attributeType The attributeType added on creation
354      * @param vals The added binary values
355      * @throws LdapInvalidAttributeValueException If any of the
356      * added values is not valid
357      */
358     public DefaultAttribute( AttributeType attributeType, byte[]... vals ) throws LdapInvalidAttributeValueException
359     {
360         this( null, attributeType, vals );
361     }
362 
363 
364     /**
365      * Create a new instance of a schema aware Attribute, with some byte[] values, and
366      * a user provided ID.
367      * 
368      * @param upId the ID for the added attribute
369      * @param attributeType the AttributeType to be added
370      * @param vals the binary values for the added attribute
371      * @throws LdapInvalidAttributeValueException If any of the
372      * added values is not valid
373      */
374     public DefaultAttribute( String upId, AttributeType attributeType, byte[]... vals )
375         throws LdapInvalidAttributeValueException
376     {
377         if ( attributeType == null )
378         {
379             throw new IllegalArgumentException( I18n.err( I18n.ERR_13203_ATTRIBUTE_TYPE_NULL_NOT_ALLOWED ) );
380         }
381 
382         apply( attributeType );
383         add( vals );
384         setUpId( upId, attributeType );
385     }
386 
387 
388     /**
389      * Creates a new instance of schema aware Attribute, by copying another attribute.
390      * If the initial Attribute is not schema aware, the copy will be if the attributeType
391      * argument is not null.
392      *
393      * @param attributeType The attribute's type
394      * @param attribute The attribute to be copied
395      * @throws LdapException If the attribute can't be created
396      */
397     public DefaultAttribute( AttributeType attributeType, Attribute attribute ) throws LdapException
398     {
399         // Copy the common values. isHR is only available on a ServerAttribute
400         this.attributeType = attributeType;
401         this.id = attribute.getId();
402         this.upId = attribute.getUpId();
403 
404         if ( attributeType == null )
405         {
406             isHR = attribute.isHumanReadable();
407 
408             // Copy all the values
409             for ( Value value : attribute )
410             {
411                 add( value.clone() );
412             }
413 
414             if ( attribute.getAttributeType() != null )
415             {
416                 apply( attribute.getAttributeType() );
417             }
418         }
419         else
420         {
421 
422             isHR = attributeType.getSyntax().isHumanReadable();
423 
424             // Copy all the values
425             for ( Value clientValue : attribute )
426             {
427                 Value serverValue = null;
428 
429                 if ( isHR )
430                 {
431                     serverValue = new Value( attributeType, clientValue.getString() );
432                 }
433                 else
434                 {
435                     // We have to convert the value to a binary value first
436                     serverValue = new Value( attributeType,
437                         clientValue.getBytes() );
438                 }
439 
440                 add( serverValue );
441             }
442         }
443     }
444 
445 
446     //-------------------------------------------------------------------------
447     // Helper methods
448     //-------------------------------------------------------------------------
449     private Value createStringValue( AttributeType attributeType, String value )
450     {
451         Value newValue;
452 
453         if ( attributeType != null )
454         {
455             try
456             {
457                 newValue = new Value( attributeType, value );
458             }
459             catch ( LdapInvalidAttributeValueException iae )
460             {
461                 return null;
462             }
463         }
464         else
465         {
466             newValue = new Value( value );
467         }
468 
469         return newValue;
470     }
471 
472 
473     private Value createBinaryValue( AttributeType attributeType, byte[] value )
474         throws LdapInvalidAttributeValueException
475     {
476         Value binaryValue;
477 
478         if ( attributeType != null )
479         {
480             binaryValue = new Value( attributeType, value );
481         }
482         else
483         {
484             binaryValue = new Value( value );
485         }
486 
487         return binaryValue;
488     }
489 
490 
491     /**
492      * {@inheritDoc}
493      */
494     @Override
495     public byte[] getBytes() throws LdapInvalidAttributeValueException
496     {
497         Value value = get();
498 
499         if ( !isHumanReadable() && ( value != null ) )
500         {
501             return value.getBytes();
502         }
503 
504         String message = I18n.err( I18n.ERR_13214_VALUE_EXPECT_BYTES );
505         LOG.error( message );
506         throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, message );
507     }
508 
509 
510     /**
511      * {@inheritDoc}
512      */
513     @Override
514     public String getString() throws LdapInvalidAttributeValueException
515     {
516         Value value = get();
517 
518         if ( isHumanReadable() )
519         {
520             if ( value != null )
521             {
522                 return value.getString();
523             }
524             else
525             {
526                 return "";
527             }
528         }
529         
530         if ( attributeType == null )
531         {
532             // Special case : the Attribute is not schema aware.
533             // The value is binary, we will try to convert it to a String
534             return Strings.utf8ToString( value.getBytes() );
535         }
536 
537         String message = I18n.err( I18n.ERR_13215_VALUE_EXPECT_STRING );
538         LOG.error( message );
539         throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, message );
540     }
541 
542 
543     /**
544      * {@inheritDoc}
545      */
546     @Override
547     public String getId()
548     {
549         return id;
550     }
551 
552 
553     /**
554      * {@inheritDoc}
555      */
556     @Override
557     public String getUpId()
558     {
559         return upId;
560     }
561 
562 
563     /**
564      * {@inheritDoc}
565      */
566     @Override
567     public void setUpId( String upId )
568     {
569         setUpId( upId, attributeType );
570     }
571 
572 
573     /**
574      * Sets the User Provided ID as a byte[]
575      * 
576      * @param upId The User Provided ID
577      */
578     public void setUpId( byte[] upId )
579     {
580         setUpId( upId, attributeType );
581     }
582 
583 
584     /**
585      * Check that the id is either a name or the OID of a given AT
586      * 
587      * @param id The ID to check
588      * @param attributeType The attributeType to verify
589      * @return <tt>true</tt> if the id is a name or an OID
590      */
591     private boolean areCompatible( String id, AttributeType attributeType )
592     {
593         // First, get rid of the options, if any
594         int optPos = id.indexOf( ';' );
595         String idNoOption = id;
596 
597         if ( optPos != -1 )
598         {
599             idNoOption = id.substring( 0, optPos );
600         }
601 
602         // Check that we find the ID in the AT names
603         for ( String name : attributeType.getNames() )
604         {
605             if ( name.equalsIgnoreCase( idNoOption ) )
606             {
607                 return true;
608             }
609         }
610 
611         // Not found in names, check the OID
612         return Oid.isOid( id ) && attributeType.getOid().equals( id );
613     }
614 
615 
616     /**
617      * {@inheritDoc}
618      */
619     @Override
620     public void setUpId( String upId, AttributeType attributeType )
621     {
622         String trimmed = Strings.trim( upId );
623 
624         if ( Strings.isEmpty( trimmed ) && ( attributeType == null ) )
625         {
626             throw new IllegalArgumentException( I18n.err( I18n.ERR_13235_NULL_ID_WITH_NULL_AT_NOT_ALLOWED ) );
627         }
628 
629         String newId = Strings.toLowerCaseAscii( trimmed );
630 
631         setUpIdInternal( upId, newId, attributeType );
632     }
633 
634 
635     /**
636      * Sets the User Provided ID as a byte[]
637      * 
638      * @param upId The User Provided ID
639      * @param attributeType The asscoiated AttributeType
640      */
641     public void setUpId( byte[] upId, AttributeType attributeType )
642     {
643         byte[] trimmed = Strings.trim( upId );
644 
645         if ( Strings.isEmpty( trimmed ) && ( attributeType == null ) )
646         {
647             throw new IllegalArgumentException( I18n.err( I18n.ERR_13235_NULL_ID_WITH_NULL_AT_NOT_ALLOWED ) );
648         }
649 
650         String newId = Strings.toLowerCase( trimmed );
651 
652         setUpIdInternal( Strings.utf8ToString( upId ), newId, attributeType );
653     }
654 
655 
656     private void setUpIdInternal( String upId, String newId, AttributeType attributeType )
657     {
658         if ( attributeType == null )
659         {
660             if ( this.attributeType == null )
661             {
662                 this.upId = upId;
663                 this.id = newId;
664 
665                 // Compute the hashCode
666                 rehash();
667 
668                 return;
669             }
670             else
671             {
672                 if ( areCompatible( newId, this.attributeType ) )
673                 {
674                     this.upId = upId;
675                     this.id = this.attributeType.getOid();
676 
677                     // Compute the hashCode
678                     rehash();
679 
680                     return;
681                 }
682                 else
683                 {
684                     return;
685                 }
686             }
687         }
688 
689         if ( Strings.isEmpty( newId ) )
690         {
691             this.attributeType = attributeType;
692             this.upId = attributeType.getName();
693             this.id = attributeType.getOid();
694 
695             // Compute the hashCode
696             rehash();
697 
698             return;
699         }
700 
701         if ( areCompatible( newId, attributeType ) )
702         {
703             this.upId = upId;
704             this.id = attributeType.getOid();
705             this.attributeType = attributeType;
706 
707             // Compute the hashCode
708             rehash();
709 
710             return;
711         }
712 
713         throw new IllegalArgumentException( I18n.err( I18n.ERR_13244_ID_AT_NOT_COMPATIBLE, id, attributeType.getName() ) );
714     }
715 
716 
717     /**
718      * {@inheritDoc}
719      */
720     @Override
721     public boolean isHumanReadable()
722     {
723         return isHR != null && isHR;
724     }
725 
726 
727     /**
728      * {@inheritDoc}
729      */
730     @Override
731     public boolean isValid( AttributeType attributeType ) throws LdapInvalidAttributeValueException
732     {
733         LdapSyntax syntax = attributeType.getSyntax();
734 
735         if ( syntax == null )
736         {
737             return false;
738         }
739 
740         SyntaxChecker syntaxChecker = syntax.getSyntaxChecker();
741 
742         if ( syntaxChecker == null )
743         {
744             return false;
745         }
746 
747         // Check that we can have no value for this attributeType
748         if ( values.isEmpty() )
749         {
750             return syntaxChecker.isValidSyntax( null );
751         }
752 
753         // Check that we can't have more than one value if the AT is single-value
754         if ( ( attributeType.isSingleValued() ) && ( values.size() > 1 ) )
755         {
756             return false;
757         }
758 
759         // Now check the values
760         for ( Value value : values )
761         {
762             try
763             {
764                 if ( !value.isValid( syntaxChecker ) )
765                 {
766                     return false;
767                 }
768             }
769             catch ( LdapException le )
770             {
771                 return false;
772             }
773         }
774 
775         return true;
776     }
777 
778 
779     /**
780      * {@inheritDoc}
781      */
782     @Override
783     public int add( Value... vals )
784     {
785         int nbAdded = 0;
786         Value nullBinaryValue = null;
787         Value nullStringValue = null;
788         boolean nullValueAdded = false;
789         Value[] valArray = vals;
790 
791         if ( vals == null )
792         {
793             valArray = new Value[0];
794         }
795 
796         if ( attributeType != null )
797         {
798             for ( Value val : valArray )
799             {
800                 if ( attributeType.getSyntax().isHumanReadable() )
801                 {
802                     if ( ( val == null ) || val.isNull() )
803                     {
804                         try
805                         {
806                             Value nullSV = new Value( attributeType, ( String ) null );
807 
808                             if ( values.add( nullSV ) )
809                             {
810                                 nbAdded++;
811                             }
812                         }
813                         catch ( LdapInvalidAttributeValueException iae )
814                         {
815                             continue;
816                         }
817                     }
818                     else if ( val.isHumanReadable() )
819                     {
820                         try
821                         {
822                             if ( val.getAttributeType() == null )
823                             {
824                                 val = new Value( attributeType, val  );
825                             }
826 
827                             if ( values.contains( val ) )
828                             {
829                                 // Replace the value
830                                 values.remove( val );
831                                 values.add( val );
832                             }
833                             else if ( values.add( val ) )
834                             {
835                                 nbAdded++;
836                             }
837                         }
838                         catch ( LdapInvalidAttributeValueException iae )
839                         {
840                             continue;
841                         }
842                     }
843                     else
844                     {
845                         String message = I18n.err( I18n.ERR_13213_VALUE_MUST_BE_A_STRING );
846                         LOG.error( message );
847                     }
848                 }
849                 else
850                 {
851                     if ( val == null )
852                     {
853                         if ( attributeType.getSyntax().getSyntaxChecker().isValidSyntax( val ) )
854                         {
855                             try
856                             {
857                                 Value nullSV = new Value( attributeType, ( byte[] ) null );
858 
859                                 if ( values.add( nullSV ) )
860                                 {
861                                     nbAdded++;
862                                 }
863                             }
864                             catch ( LdapInvalidAttributeValueException iae )
865                             {
866                                 continue;
867                             }
868                         }
869                         else
870                         {
871                             String message = I18n.err( I18n.ERR_13211_BYTE_VALUE_EXPECTED );
872                             LOG.error( message );
873                         }
874                     }
875                     else
876                     {
877                         if ( !val.isHumanReadable() )
878                         {
879                             try
880                             {
881                                 if ( val.getAttributeType() == null )
882                                 {
883                                     val = new Value( attributeType, val.getBytes() );
884                                 }
885 
886                                 if ( values.add( val ) )
887                                 {
888                                     nbAdded++;
889                                 }
890                             }
891                             catch ( LdapInvalidAttributeValueException iae )
892                             {
893                                 continue;
894                             }
895                         }
896                         else
897                         {
898                             String message = I18n.err( I18n.ERR_13211_BYTE_VALUE_EXPECTED );
899                             LOG.error( message );
900                         }
901                     }
902                 }
903             }
904         }
905         else
906         {
907             for ( Value val : valArray )
908             {
909                 if ( val == null )
910                 {
911                     // We have a null value. If the HR flag is not set, we will consider
912                     // that the attribute is not HR. We may change this later
913                     if ( isHR == null )
914                     {
915                         // This is the first value. Add both types, as we
916                         // don't know yet the attribute type's, but we may
917                         // know later if we add some new value.
918                         // We have to do that because we are using a Set,
919                         // and we can't remove the first element of the Set.
920                         nullBinaryValue = new Value( ( byte[] ) null );
921                         nullStringValue = new Value( ( String ) null );
922 
923                         values.add( nullBinaryValue );
924                         values.add( nullStringValue );
925                         nullValueAdded = true;
926                         nbAdded++;
927                     }
928                     else if ( !isHR )
929                     {
930                         // The attribute type is binary.
931                         nullBinaryValue = new Value( ( byte[] ) null );
932 
933                         // Don't add a value if it already exists.
934                         if ( !values.contains( nullBinaryValue ) )
935                         {
936                             values.add( nullBinaryValue );
937                             nbAdded++;
938                         }
939 
940                     }
941                     else
942                     {
943                         // The attribute is HR
944                         nullStringValue = new Value( ( String ) null );
945 
946                         // Don't add a value if it already exists.
947                         if ( !values.contains( nullStringValue ) )
948                         {
949                             values.add( nullStringValue );
950                         }
951                     }
952                 }
953                 else
954                 {
955                     // Let's check the value type.
956                     if ( val.isHumanReadable() )
957                     {
958                         // We have a String value
959                         if ( isHR == null )
960                         {
961                             // The attribute type will be set to HR
962                             isHR = true;
963                             values.add( val );
964                             nbAdded++;
965                         }
966                         else if ( !isHR )
967                         {
968                             // The attributeType is binary, convert the
969                             // value to a Value
970                             Value bv = new Value( val.getBytes() );
971 
972                             if ( !contains( bv ) )
973                             {
974                                 values.add( bv );
975                                 nbAdded++;
976                             }
977                         }
978                         else
979                         {
980                             // The attributeType is HR, simply add the value
981                             if ( !contains( val ) )
982                             {
983                                 values.add( val );
984                                 nbAdded++;
985                             }
986                         }
987                     }
988                     else
989                     {
990                         // We have a Binary value
991                         if ( isHR == null )
992                         {
993                             // The attribute type will be set to binary
994                             isHR = false;
995                             values.add( val );
996                             nbAdded++;
997                         }
998                         else if ( !isHR )
999                         {
1000                             // The attributeType is not HR, simply add the value if it does not already exist
1001                             if ( !contains( val ) )
1002                             {
1003                                 values.add( val );
1004                                 nbAdded++;
1005                             }
1006                         }
1007                         else
1008                         {
1009                             // The attribute Type is HR, convert the
1010                             // value to a Value
1011                             Value sv = new Value( Strings.utf8ToString( val.getBytes() ) );
1012 
1013                             if ( !contains( sv ) )
1014                             {
1015                                 values.add( sv );
1016                                 nbAdded++;
1017                             }
1018                         }
1019                     }
1020                 }
1021             }
1022         }
1023 
1024         // Last, not least, if a nullValue has been added, and if other
1025         // values are all String, we have to keep the correct nullValue,
1026         // and to remove the other
1027         if ( nullValueAdded )
1028         {
1029             if ( isHR )
1030             {
1031                 // Remove the Binary value
1032                 values.remove( nullBinaryValue );
1033             }
1034             else
1035             {
1036                 // Remove the String value
1037                 values.remove( nullStringValue );
1038             }
1039         }
1040 
1041         return nbAdded;
1042     }
1043 
1044 
1045     /**
1046      * {@inheritDoc}
1047      */
1048     @Override
1049     public int add( String... vals ) throws LdapInvalidAttributeValueException
1050     {
1051         int nbAdded = 0;
1052         String[] valArray = vals;
1053 
1054         if ( vals == null )
1055         {
1056             valArray = new String[0];
1057         }
1058 
1059         // First, if the isHR flag is not set, we assume that the
1060         // attribute is HR, because we are asked to add some strings.
1061         if ( isHR == null )
1062         {
1063             isHR = true;
1064         }
1065 
1066         // Check the attribute type.
1067         if ( attributeType == null )
1068         {
1069             if ( isHR )
1070             {
1071                 for ( String val : valArray )
1072                 {
1073                     Value value = createStringValue( attributeType, val );
1074 
1075                     if ( value == null )
1076                     {
1077                         // The value can't be normalized : we don't add it.
1078                         LOG.error( I18n.err( I18n.ERR_13200_VALUE_CANT_BE_NORMALIZED, val ) );
1079                         continue;
1080                     }
1081 
1082                     // Call the add(Value) method, if not already present
1083                     if ( add( value ) == 1 )
1084                     {
1085                         nbAdded++;
1086                     }
1087                     else
1088                     {
1089                         if ( LOG.isWarnEnabled() )
1090                         {
1091                             LOG.warn( I18n.err( I18n.ERR_13207_VALUE_ALREADY_EXISTS, val, upId ) );
1092                         }
1093                     }
1094                 }
1095             }
1096             else
1097             {
1098                 // The attribute is binary. Transform the String to byte[]
1099                 for ( String val : valArray )
1100                 {
1101                     byte[] valBytes = null;
1102 
1103                     if ( val != null )
1104                     {
1105                         valBytes = Strings.getBytesUtf8( val );
1106                     }
1107 
1108                     Value value = createBinaryValue( attributeType, valBytes );
1109 
1110                     // Now call the add(Value) method
1111                     if ( add( value ) == 1 )
1112                     {
1113                         nbAdded++;
1114                     }
1115                 }
1116             }
1117         }
1118         else
1119         {
1120             if ( attributeType.isSingleValued() && ( values.size() + valArray.length > 1 ) )
1121             {
1122                 LOG.error( I18n.err( I18n.ERR_13208_ATTRIBUTE_IS_SINGLE_VALUED, attributeType.getName() ) );
1123                 return 0;
1124             }
1125 
1126             if ( isHR )
1127             {
1128                 for ( String val : valArray )
1129                 {
1130                     Value value = createStringValue( attributeType, val );
1131 
1132                     if ( value == null )
1133                     {
1134                         // The value can't be normalized : we don't add it.
1135                         LOG.error( I18n.err( I18n.ERR_13200_VALUE_CANT_BE_NORMALIZED, val ) );
1136                         continue;
1137                     }
1138 
1139                     // Call the add(Value) method, if not already present
1140                     if ( add( value ) == 1 )
1141                     {
1142                         nbAdded++;
1143                     }
1144                     else
1145                     {
1146                         if ( LOG.isWarnEnabled() )
1147                         {
1148                             LOG.warn( I18n.err( I18n.ERR_13207_VALUE_ALREADY_EXISTS, val, upId ) );
1149                         }
1150                     }
1151                 }
1152             }
1153             else
1154             {
1155                 // The attribute is binary. Transform the String to byte[]
1156                 for ( String val : valArray )
1157                 {
1158                     byte[] valBytes = null;
1159 
1160                     if ( val != null )
1161                     {
1162                         valBytes = Strings.getBytesUtf8( val );
1163                     }
1164 
1165                     Value value = createBinaryValue( attributeType, valBytes );
1166 
1167                     // Now call the add(Value) method
1168                     if ( add( value ) == 1 )
1169                     {
1170                         nbAdded++;
1171                     }
1172                 }
1173             }
1174         }
1175 
1176         return nbAdded;
1177     }
1178 
1179 
1180     /**
1181      * {@inheritDoc}
1182      */
1183     public int add( byte[]... vals ) throws LdapInvalidAttributeValueException
1184     {
1185         int nbAdded = 0;
1186         byte[][] valArray = vals;
1187 
1188         if ( vals == null )
1189         {
1190             valArray = new byte[0][];
1191         }
1192 
1193         // First, if the isHR flag is not set, we assume that the
1194         // attribute is not HR, because we are asked to add some byte[].
1195         if ( isHR == null )
1196         {
1197             isHR = false;
1198         }
1199 
1200         if ( !isHR )
1201         {
1202             for ( byte[] val : valArray )
1203             {
1204                 Value value;
1205 
1206                 if ( attributeType == null )
1207                 {
1208                     value = new Value( val );
1209                 }
1210                 else
1211                 {
1212                     value = createBinaryValue( attributeType, val );
1213                 }
1214 
1215                 if ( add( value ) != 0 )
1216                 {
1217                     nbAdded++;
1218                 }
1219                 else
1220                 {
1221                     if ( LOG.isWarnEnabled() )
1222                     {
1223                         LOG.warn( I18n.err( I18n.ERR_13207_VALUE_ALREADY_EXISTS, Strings.dumpBytes( val ), upId ) );
1224                     }
1225                 }
1226             }
1227         }
1228         else
1229         {
1230             // We can't add Binary values into a String Attribute
1231             if ( LOG.isInfoEnabled() )
1232             {
1233                 LOG.info( I18n.err( I18n.ERR_13213_VALUE_MUST_BE_A_STRING ) );
1234             }
1235             
1236             return 0;
1237         }
1238 
1239         return nbAdded;
1240     }
1241 
1242 
1243     /**
1244      * {@inheritDoc}
1245      */
1246     @Override
1247     public void clear()
1248     {
1249         values.clear();
1250     }
1251 
1252 
1253     /**
1254      * {@inheritDoc}
1255      */
1256     @Override
1257     public boolean contains( Value... vals )
1258     {
1259         if ( isHR == null )
1260         {
1261             // If this flag is null, then there is no values.
1262             return false;
1263         }
1264 
1265         if ( attributeType == null )
1266         {
1267             if ( isHR )
1268             {
1269                 // Iterate through all the values, convert the Binary values
1270                 // to String values, and quit id any of the values is not
1271                 // contained in the object
1272                 for ( Value val : vals )
1273                 {
1274                     if ( val.isHumanReadable() )
1275                     {
1276                         if ( !values.contains( val ) )
1277                         {
1278                             return false;
1279                         }
1280                     }
1281                     else
1282                     {
1283                         byte[] binaryVal = val.getBytes();
1284 
1285                         // We have to convert the binary value to a String
1286                         if ( !values.contains( new Value( Strings.utf8ToString( binaryVal ) ) ) )
1287                         {
1288                             return false;
1289                         }
1290                     }
1291                 }
1292             }
1293             else
1294             {
1295                 // Iterate through all the values, convert the String values
1296                 // to binary values, and quit id any of the values is not
1297                 // contained in the object
1298                 for ( Value val : vals )
1299                 {
1300                     if ( val.isHumanReadable() )
1301                     {
1302                         String stringVal = val.getString();
1303 
1304                         // We have to convert the binary value to a String
1305                         if ( !values.contains( new Value( Strings.getBytesUtf8( stringVal ) ) ) )
1306                         {
1307                             return false;
1308                         }
1309                     }
1310                     else
1311                     {
1312                         if ( !values.contains( val ) )
1313                         {
1314                             return false;
1315                         }
1316                     }
1317                 }
1318             }
1319         }
1320         else
1321         {
1322             // Iterate through all the values, and quit if we
1323             // don't find one in the values. We have to separate the check
1324             // depending on the isHR flag value.
1325             if ( isHR )
1326             {
1327                 for ( Value val : vals )
1328                 {
1329                     if ( val.isHumanReadable() )
1330                     {
1331                         try
1332                         {
1333                             if ( val.getAttributeType() == null )
1334                             {
1335                                 val = new Value( attributeType, val );
1336                             }
1337                         }
1338                         catch ( LdapInvalidAttributeValueException liave )
1339                         {
1340                             return false;
1341                         }
1342 
1343                         if ( !values.contains( val ) )
1344                         {
1345                             return false;
1346                         }
1347                     }
1348                     else
1349                     {
1350                         // Not a String value
1351                         return false;
1352                     }
1353                 }
1354             }
1355             else
1356             {
1357                 for ( Value val : vals )
1358                 {
1359                     if ( !val.isHumanReadable() )
1360                     {
1361                         if ( !values.contains( val ) )
1362                         {
1363                             return false;
1364                         }
1365                     }
1366                     else
1367                     {
1368                         // Not a Binary value
1369                         return false;
1370                     }
1371                 }
1372             }
1373         }
1374 
1375         return true;
1376     }
1377 
1378 
1379     /**
1380      * {@inheritDoc}
1381      */
1382     @Override
1383     public boolean contains( String... vals )
1384     {
1385         if ( isHR == null )
1386         {
1387             // If this flag is null, then there is no values.
1388             return false;
1389         }
1390 
1391         if ( attributeType == null )
1392         {
1393             if ( isHR )
1394             {
1395                 for ( String val : vals )
1396                 {
1397                     try
1398                     {
1399                         if ( !contains( new Value( val ) ) )
1400                         {
1401                             return false;
1402                         }
1403                     }
1404                     catch ( IllegalArgumentException iae )
1405                     {
1406                         return false;
1407                     }
1408                 }
1409             }
1410             else
1411             {
1412                 // As the attribute type is binary, we have to convert
1413                 // the values before checking for them in the values
1414                 // Iterate through all the values, and quit if we
1415                 // don't find one in the values
1416                 for ( String val : vals )
1417                 {
1418                     byte[] binaryVal = Strings.getBytesUtf8( val );
1419 
1420                     if ( !contains( new Value( binaryVal ) ) )
1421                     {
1422                         return false;
1423                     }
1424                 }
1425             }
1426         }
1427         else
1428         {
1429             if ( isHR )
1430             {
1431                 // Iterate through all the values, and quit if we
1432                 // don't find one in the values
1433                 for ( String val : vals )
1434                 {
1435                     try
1436                     {
1437                         Value value = new Value( attributeType, val );
1438 
1439                         if ( !values.contains( value ) )
1440                         {
1441                             return false;
1442                         }
1443                     }
1444                     catch ( LdapInvalidAttributeValueException liave )
1445                     {
1446                         return false;
1447                     }
1448                 }
1449 
1450                 return true;
1451             }
1452             else
1453             {
1454                 return false;
1455             }
1456         }
1457 
1458         return true;
1459     }
1460 
1461 
1462     /**
1463      * {@inheritDoc}
1464      */
1465     public boolean contains( byte[]... vals )
1466     {
1467         if ( isHR == null )
1468         {
1469             // If this flag is null, then there is no values.
1470             return false;
1471         }
1472 
1473         if ( attributeType == null )
1474         {
1475             if ( !isHR )
1476             {
1477                 // Iterate through all the values, and quit if we
1478                 // don't find one in the values
1479                 for ( byte[] val : vals )
1480                 {
1481                     if ( !contains( new Value( val ) ) )
1482                     {
1483                         return false;
1484                     }
1485                 }
1486             }
1487             else
1488             {
1489                 // As the attribute type is String, we have to convert
1490                 // the values before checking for them in the values
1491                 // Iterate through all the values, and quit if we
1492                 // don't find one in the values
1493                 for ( byte[] val : vals )
1494                 {
1495                     String stringVal = Strings.utf8ToString( val );
1496 
1497                     if ( !contains( new Value( stringVal ) ) )
1498                     {
1499                         return false;
1500                     }
1501                 }
1502             }
1503         }
1504         else
1505         {
1506             if ( !isHR )
1507             {
1508                 // Iterate through all the values, and quit if we
1509                 // don't find one in the values
1510                 for ( byte[] val : vals )
1511                 {
1512                     try
1513                     {
1514                         Value value = new Value( attributeType, val );
1515 
1516                         if ( !values.contains( value ) )
1517                         {
1518                             return false;
1519                         }
1520                     }
1521                     catch ( LdapInvalidAttributeValueException liave )
1522                     {
1523                         return false;
1524                     }
1525                 }
1526 
1527                 return true;
1528             }
1529             else
1530             {
1531                 return false;
1532             }
1533         }
1534 
1535         return true;
1536     }
1537 
1538 
1539     /**
1540      * {@inheritDoc}
1541      */
1542     @Override
1543     public Value get()
1544     {
1545         if ( values.isEmpty() )
1546         {
1547             return null;
1548         }
1549 
1550         return values.iterator().next();
1551     }
1552 
1553 
1554     /**
1555      * {@inheritDoc}
1556      */
1557     @Override
1558     public int size()
1559     {
1560         return values.size();
1561     }
1562 
1563 
1564     /**
1565      * {@inheritDoc}
1566      */
1567     @Override
1568     public boolean remove( Value... vals )
1569     {
1570         if ( ( isHR == null ) || ( values.isEmpty() ) )
1571         {
1572             // Trying to remove a value from an empty list will fail
1573             return false;
1574         }
1575 
1576         boolean removed = true;
1577 
1578         if ( attributeType == null )
1579         {
1580             if ( isHR )
1581             {
1582                 for ( Value val : vals )
1583                 {
1584                     if ( val.isHumanReadable() )
1585                     {
1586                         removed &= values.remove( val );
1587                     }
1588                     else
1589                     {
1590                         // Convert the binary value to a string value
1591                         byte[] binaryVal = val.getBytes();
1592                         removed &= values.remove( new Value( Strings.utf8ToString( binaryVal ) ) );
1593                     }
1594                 }
1595             }
1596             else
1597             {
1598                 for ( Value val : vals )
1599                 {
1600                     removed &= values.remove( val );
1601                 }
1602             }
1603         }
1604         else
1605         {
1606             // Loop through all the values to remove. If one of
1607             // them is not present, the method will return false.
1608             // As the attribute may be HR or not, we have two separated treatments
1609             if ( isHR )
1610             {
1611                 for ( Value val : vals )
1612                 {
1613                     if ( val.isHumanReadable() )
1614                     {
1615                         try
1616                         {
1617                             if ( val.getAttributeType() == null )
1618                             {
1619                                 val = new Value( attributeType, val );
1620                             }
1621 
1622                             removed &= values.remove( val );
1623                         }
1624                         catch ( LdapInvalidAttributeValueException liave )
1625                         {
1626                             removed = false;
1627                         }
1628                     }
1629                     else
1630                     {
1631                         removed = false;
1632                     }
1633                 }
1634             }
1635             else
1636             {
1637                 for ( Value val : vals )
1638                 {
1639                     if ( !val.isHumanReadable() )
1640                     {
1641                         try
1642                         {
1643                             if ( val.getAttributeType() == null )
1644                             {
1645                                 val = new Value( attributeType, val );
1646                             }
1647 
1648                             removed &= values.remove( val );
1649                         }
1650                         catch ( LdapInvalidAttributeValueException liave )
1651                         {
1652                             removed = false;
1653                         }
1654                     }
1655                     else
1656                     {
1657                         removed = false;
1658                     }
1659                 }
1660             }
1661         }
1662 
1663         return removed;
1664     }
1665 
1666 
1667     /**
1668      * {@inheritDoc}
1669      */
1670     public boolean remove( byte[]... vals )
1671     {
1672         if ( ( isHR == null ) || ( values.isEmpty() ) )
1673         {
1674             // Trying to remove a value from an empty list will fail
1675             return false;
1676         }
1677 
1678         boolean removed = true;
1679 
1680         if ( attributeType == null )
1681         {
1682             if ( !isHR )
1683             {
1684                 // The attribute type is not HR, we can directly process the values
1685                 for ( byte[] val : vals )
1686                 {
1687                     Value value = new Value( val );
1688                     removed &= values.remove( value );
1689                 }
1690             }
1691             else
1692             {
1693                 // The attribute type is String, we have to convert the values
1694                 // to String before removing them
1695                 for ( byte[] val : vals )
1696                 {
1697                     Value value = new Value( Strings.utf8ToString( val ) );
1698                     removed &= values.remove( value );
1699                 }
1700             }
1701         }
1702         else
1703         {
1704             if ( !isHR )
1705             {
1706                 try
1707                 {
1708                     for ( byte[] val : vals )
1709                     {
1710                         Value value = new Value( attributeType, val );
1711                         removed &= values.remove( value );
1712                     }
1713                 }
1714                 catch ( LdapInvalidAttributeValueException liave )
1715                 {
1716                     removed = false;
1717                 }
1718             }
1719             else
1720             {
1721                 removed = false;
1722             }
1723         }
1724 
1725         return removed;
1726     }
1727 
1728 
1729     /**
1730      * {@inheritDoc}
1731      */
1732     @Override
1733     public boolean remove( String... vals )
1734     {
1735         if ( ( isHR == null ) || ( values.isEmpty() ) )
1736         {
1737             // Trying to remove a value from an empty list will fail
1738             return false;
1739         }
1740 
1741         boolean removed = true;
1742 
1743         if ( attributeType == null )
1744         {
1745             if ( isHR )
1746             {
1747                 // The attribute type is HR, we can directly process the values
1748                 for ( String val : vals )
1749                 {
1750                     Value value = new Value( val );
1751                     removed &= values.remove( value );
1752                 }
1753             }
1754             else
1755             {
1756                 // The attribute type is binary, we have to convert the values
1757                 // to byte[] before removing them
1758                 for ( String val : vals )
1759                 {
1760                     Value value = new Value( Strings.getBytesUtf8( val ) );
1761                     removed &= values.remove( value );
1762                 }
1763             }
1764         }
1765         else
1766         {
1767             if ( isHR )
1768             {
1769                 for ( String val : vals )
1770                 {
1771                     try
1772                     {
1773                         Value value = new Value( attributeType, val );
1774                         removed &= values.remove( value );
1775                     }
1776                     catch ( LdapInvalidAttributeValueException liave )
1777                     {
1778                         removed = false;
1779                     }
1780                 }
1781             }
1782             else
1783             {
1784                 removed = false;
1785             }
1786         }
1787 
1788         return removed;
1789     }
1790 
1791 
1792     /**
1793      * An iterator on top of the stored values.
1794      * 
1795      * @return an iterator over the stored values.
1796      */
1797     @Override
1798     public Iterator<Value> iterator()
1799     {
1800         return values.iterator();
1801     }
1802 
1803 
1804     /**
1805      * {@inheritDoc}
1806      */
1807     @Override
1808     public AttributeType getAttributeType()
1809     {
1810         return attributeType;
1811     }
1812 
1813 
1814     /**
1815      * {@inheritDoc}
1816      */
1817     @Override
1818     public void apply( AttributeType attributeType ) throws LdapInvalidAttributeValueException
1819     {
1820         if ( attributeType == null )
1821         {
1822             throw new IllegalArgumentException( I18n.err( I18n.ERR_13245_AT_PARAMETER_NULL ) );
1823         }
1824 
1825         this.attributeType = attributeType;
1826         this.id = attributeType.getOid();
1827 
1828         if ( Strings.isEmpty( this.upId ) )
1829         {
1830             this.upId = attributeType.getName();
1831         }
1832         else
1833         {
1834             if ( !areCompatible( this.upId, attributeType ) )
1835             {
1836                 this.upId = attributeType.getName();
1837             }
1838         }
1839 
1840         if ( values != null )
1841         {
1842             Set<Value> newValues = new LinkedHashSet<>( values.size() );
1843 
1844             for ( Value value : values )
1845             {
1846                 if ( value.isSchemaAware() )
1847                 {
1848                     newValues.add( value );
1849                 }
1850                 else
1851                 {
1852                     if ( value.isHumanReadable() )
1853                     {
1854                         newValues.add( new Value( attributeType, value.getString() ) );
1855                     }
1856                     else
1857                     {
1858                         newValues.add( new Value( attributeType, value.getBytes() ) );
1859                     }
1860                 }
1861             }
1862 
1863             values = newValues;
1864         }
1865 
1866         isHR = attributeType.getSyntax().isHumanReadable();
1867 
1868         // Compute the hashCode
1869         rehash();
1870     }
1871 
1872 
1873     /**
1874      * {@inheritDoc}
1875      */
1876     @Override
1877     public boolean isInstanceOf( AttributeType attributeType ) throws LdapInvalidAttributeValueException
1878     {
1879         return ( attributeType != null )
1880             && ( this.attributeType.equals( attributeType ) || this.attributeType.isDescendantOf( attributeType ) );
1881     }
1882 
1883 
1884     //-------------------------------------------------------------------------
1885     // Overloaded Object classes
1886     //-------------------------------------------------------------------------
1887     /**
1888      * A helper method to rehash the hashCode
1889      */
1890     private void rehash()
1891     {
1892         int hTmp = 37;
1893 
1894         if ( isHR != null )
1895         {
1896             hTmp = hTmp * 17 + isHR.hashCode();
1897         }
1898 
1899         if ( id != null )
1900         {
1901             hTmp = hTmp * 17 + id.hashCode();
1902         }
1903 
1904         if ( attributeType != null )
1905         {
1906             hTmp = hTmp * 17 + attributeType.hashCode();
1907         }
1908         
1909         h = hTmp;
1910     }
1911 
1912 
1913     /**
1914      * The hashCode is based on the id, the isHR flag and
1915      * on the internal values.
1916      * 
1917      * @see Object#hashCode()
1918      * @return the instance's hashcode
1919      */
1920     @Override
1921     public int hashCode()
1922     {
1923         if ( h == 0 )
1924         {
1925             rehash();
1926         }
1927 
1928         return h;
1929     }
1930 
1931 
1932     /**
1933      * @see Object#equals(Object)
1934      */
1935     @Override
1936     public boolean equals( Object obj )
1937     {
1938         if ( obj == this )
1939         {
1940             return true;
1941         }
1942 
1943         if ( !( obj instanceof Attribute ) )
1944         {
1945             return false;
1946         }
1947 
1948         Attribute other = ( Attribute ) obj;
1949 
1950         if ( id == null )
1951         {
1952             if ( other.getId() != null )
1953             {
1954                 return false;
1955             }
1956         }
1957         else
1958         {
1959             if ( other.getId() == null )
1960             {
1961                 return false;
1962             }
1963             else
1964             {
1965                 if ( attributeType != null )
1966                 {
1967                     if ( !attributeType.equals( other.getAttributeType() ) )
1968                     {
1969                         return false;
1970                     }
1971                 }
1972                 else if ( !id.equals( other.getId() ) )
1973                 {
1974                     return false;
1975                 }
1976             }
1977         }
1978 
1979         if ( isHumanReadable() != other.isHumanReadable() )
1980         {
1981             return false;
1982         }
1983 
1984         if ( values.size() != other.size() )
1985         {
1986             return false;
1987         }
1988 
1989         for ( Value val : values )
1990         {
1991             if ( !other.contains( val ) )
1992             {
1993                 return false;
1994             }
1995         }
1996 
1997         if ( attributeType == null )
1998         {
1999             return other.getAttributeType() == null;
2000         }
2001 
2002         return attributeType.equals( other.getAttributeType() );
2003     }
2004 
2005 
2006     /**
2007      * {@inheritDoc}
2008      */
2009     @Override
2010     public Attribute clone()
2011     {
2012         try
2013         {
2014             DefaultAttribute attribute = ( DefaultAttribute ) super.clone();
2015 
2016             if ( this.attributeType != null )
2017             {
2018                 attribute.id = attributeType.getOid();
2019                 attribute.attributeType = attributeType;
2020             }
2021 
2022             attribute.values = new LinkedHashSet<>( values.size() );
2023 
2024             for ( Value value : values )
2025             {
2026                 // No need to clone the value, it will never be changed
2027                 attribute.values.add( value );
2028             }
2029 
2030             return attribute;
2031         }
2032         catch ( CloneNotSupportedException cnse )
2033         {
2034             return null;
2035         }
2036     }
2037 
2038 
2039     /**
2040      * This is the place where we serialize attributes, and all theirs
2041      * elements.
2042      * 
2043      * {@inheritDoc}
2044      */
2045     @Override
2046     public void writeExternal( ObjectOutput out ) throws IOException
2047     {
2048         // Write the UPId
2049         out.writeUTF( upId );
2050         
2051         // Write the ID
2052         out.writeUTF( id );
2053 
2054         // Write the HR flag, if not null
2055         if ( isHR != null )
2056         {
2057             out.writeBoolean( true );
2058             out.writeBoolean( isHR );
2059         }
2060         else
2061         {
2062             out.writeBoolean( false );
2063         }
2064 
2065         // Write the number of values
2066         out.writeInt( size() );
2067 
2068         if ( size() > 0 )
2069         {
2070             // Write each value
2071             for ( Value value : values )
2072             {
2073                 // Write the value
2074                 value.writeExternal( out );
2075             }
2076         }
2077 
2078         out.flush();
2079     }
2080 
2081 
2082     /**
2083      * {@inheritDoc}
2084      */
2085     @Override
2086     public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException
2087     {
2088         // Read the ID and the UPId
2089         upId = in.readUTF();
2090 
2091         // Read the id
2092         id = in.readUTF();
2093         
2094         // Read the HR flag, if not null
2095         if ( in.readBoolean() )
2096         {
2097             isHR = in.readBoolean();
2098         }
2099 
2100         // Read the number of values
2101         int nbValues = in.readInt();
2102 
2103         if ( nbValues > 0 )
2104         {
2105             for ( int i = 0; i < nbValues; i++ )
2106             {
2107                 Value value = new Value( attributeType );
2108 
2109                 value.readExternal( in );
2110 
2111                 values.add( value );
2112             }
2113         }
2114     }
2115 
2116 
2117     /**
2118      * @see Object#toString()
2119      */
2120     @Override
2121     public String toString()
2122     {
2123         return toString( "" );
2124     }
2125 
2126 
2127     /**
2128      * {@inheritDoc}
2129      */
2130     @Override
2131     public String toString( String tabs )
2132     {
2133         StringBuilder sb = new StringBuilder();
2134 
2135         if ( ( values != null ) && ( !values.isEmpty() ) )
2136         {
2137             boolean isFirst = true;
2138 
2139             for ( Value value : values )
2140             {
2141                 if ( isFirst )
2142                 {
2143                     isFirst = false;
2144                 }
2145                 else
2146                 {
2147                     sb.append( '\n' );
2148                 }
2149 
2150                 sb.append( tabs ).append( upId ).append( ": " );
2151 
2152                 if ( value.isNull() )
2153                 {
2154                     sb.append( "''" );
2155                 }
2156                 else
2157                 {
2158                     sb.append( value );
2159                 }
2160             }
2161         }
2162         else
2163         {
2164             sb.append( tabs ).append( upId ).append( ": (null)" );
2165         }
2166 
2167         return sb.toString();
2168     }
2169 }