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