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