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