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     */
019    package org.apache.directory.shared.ldap.entry.client;
020    
021    
022    import java.io.IOException;
023    import java.io.ObjectInput;
024    import java.io.ObjectOutput;
025    import java.util.Iterator;
026    import java.util.LinkedHashSet;
027    import java.util.List;
028    import java.util.Set;
029    
030    import javax.naming.NamingException;
031    import javax.naming.directory.InvalidAttributeValueException;
032    
033    import org.apache.directory.shared.i18n.I18n;
034    import org.apache.directory.shared.ldap.entry.EntryAttribute;
035    import org.apache.directory.shared.ldap.entry.Value;
036    import org.apache.directory.shared.ldap.schema.SyntaxChecker;
037    import org.apache.directory.shared.ldap.util.StringTools;
038    import org.slf4j.Logger;
039    import org.slf4j.LoggerFactory;
040    
041    
042    /**
043     * A client side entry attribute. The client is not aware of the schema,
044     * so we can't tell if the stored value will be String or Binary. We will
045     * default to Binary.<p>
046     * To define the kind of data stored, the client must set the isHR flag.
047     *
048     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
049     * @version $Rev$, $Date$
050     */
051    public class DefaultClientAttribute implements ClientAttribute
052    {
053        /** logger for reporting errors that might not be handled properly upstream */
054        private static final Logger LOG = LoggerFactory.getLogger( DefaultClientAttribute.class );
055        
056        
057        /** The set of contained values */
058        protected Set<Value<?>> values = new LinkedHashSet<Value<?>>();
059        
060        /** The User provided ID */
061        protected String upId;
062    
063        /** The normalized ID */
064        protected String id;
065    
066        /** Tells if the attribute is Human Readable or not. When not set, 
067         * this flag is null. */
068        protected Boolean isHR;
069    
070    
071        // maybe have some additional convenience constructors which take
072        // an initial value as a string or a byte[]
073        /**
074         * Create a new instance of a EntryAttribute, without ID nor value.
075         */
076        public DefaultClientAttribute()
077        {
078        }
079    
080    
081        /**
082         * Create a new instance of a EntryAttribute, without value.
083         */
084        public DefaultClientAttribute( String upId )
085        {
086            setUpId( upId );
087        }
088    
089    
090        /**
091         * If the value does not correspond to the same attributeType, then it's
092         * wrapped value is copied into a new ClientValue which uses the specified
093         * attributeType.
094         * 
095         * Otherwise, the value is stored, but as a reference. It's not a copy.
096         *
097         * @param upId
098         * @param attributeType the attribute type according to the schema
099         * @param vals an initial set of values for this attribute
100         */
101        public DefaultClientAttribute( String upId, Value<?>... vals )
102        {
103            // The value can be null, this is a valid value.
104            if ( vals[0] == null )
105            {
106                 add( new ClientStringValue() );
107            }
108            else
109            {
110                for ( Value<?> val:vals )
111                {
112                    if ( ( val instanceof ClientStringValue ) || ( val.isBinary() ) )
113                    {
114                        add( val );
115                    }
116                    else
117                    {
118                        String message = I18n.err( I18n.ERR_04129, val.getClass().getName() );
119                        LOG.error( message );
120                        throw new IllegalStateException( message );
121                    }
122                }
123            }
124            
125            setUpId( upId );
126        }
127    
128    
129        /**
130         * Create a new instance of a EntryAttribute.
131         */
132        public DefaultClientAttribute( String upId, String... vals )
133        {
134            add( vals );
135            setUpId( upId );
136        }
137    
138    
139        /**
140         * Create a new instance of a EntryAttribute, with some byte[] values.
141         */
142        public DefaultClientAttribute( String upId, byte[]... vals )
143        {
144            add( vals );
145            setUpId( upId );
146        }
147    
148    
149        /**
150         * <p>
151         * Get the byte[] value, if and only if the value is known to be Binary,
152         * otherwise a InvalidAttributeValueException will be thrown
153         * </p>
154         * <p>
155         * Note that this method returns the first value only.
156         * </p>
157         *
158         * @return The value as a byte[]
159         * @throws InvalidAttributeValueException If the value is a String
160         */
161        public byte[] getBytes() throws InvalidAttributeValueException
162        {
163            Value<?> value = get();
164            
165            if ( value.isBinary() )
166            {
167                return value.getBytes();
168            }
169            else
170            {
171                String message = I18n.err( I18n.ERR_04130 );
172                LOG.error( message );
173                throw new InvalidAttributeValueException( message );
174            }
175        }
176    
177    
178        /**
179         * <p>
180         * Get the String value, if and only if the value is known to be a String,
181         * otherwise a InvalidAttributeValueException will be thrown
182         * </p>
183         * <p>
184         * Note that this method returns the first value only.
185         * </p>
186         *
187         * @return The value as a String
188         * @throws InvalidAttributeValueException If the value is a byte[]
189         */
190        public String getString() throws InvalidAttributeValueException
191        {
192            Value<?> value = get();
193            
194            if ( value instanceof ClientStringValue )
195            {
196                return value.getString();
197            }
198            else
199            {
200                String message = I18n.err( I18n.ERR_04131 );
201                LOG.error( message );
202                throw new InvalidAttributeValueException( message );
203            }
204        }
205    
206    
207        /**
208         * Get's the attribute identifier. Its value is the same than the
209         * user provided ID.
210         *
211         * @return the attribute's identifier
212         */
213        public String getId()
214        {
215            return id;
216        }
217    
218    
219        /**
220         * <p>
221         * Set the attribute to Human Readable or to Binary. 
222         * </p>
223         * @param isHR <code>true</code> for a Human Readable attribute, 
224         * <code>false</code> for a Binary attribute.
225         */
226        public void setHR( boolean isHR )
227        {
228            this.isHR = isHR;
229            //TODO : deal with the values, we may have to convert them.
230        }
231    
232        
233        /**
234         * Set the normalized ID. The ID will be lowercased, and spaces
235         * will be trimmed. 
236         *
237         * @param id The attribute ID
238         * @throws IllegalArgumentException If the ID is empty or null or
239         * resolve to an empty value after being trimmed
240         */
241        public void setId( String id )
242        {
243            this.id = StringTools.trim( StringTools.lowerCaseAscii( id ) );
244    
245            if ( this.id.length() == 0 )
246            {
247                this.id = null;
248                throw new IllegalArgumentException( I18n.err( I18n.ERR_04132 ) );
249            }
250        }
251    
252        
253        /**
254         * Get's the user provided identifier for this entry.  This is the value
255         * that will be used as the identifier for the attribute within the
256         * entry.  If this is a commonName attribute for example and the user
257         * provides "COMMONname" instead when adding the entry then this is
258         * the format the user will have that entry returned by the directory
259         * server.  To do so we store this value as it was given and track it
260         * in the attribute using this property.
261         *
262         * @return the user provided identifier for this attribute
263         */
264        public String getUpId()
265        {
266            return upId;
267        }
268    
269    
270        /**
271         * Set the user provided ID. It will also set the ID, normalizing
272         * the upId (removing spaces before and after, and lowercasing it)
273         *
274         * @param upId The attribute ID
275         * @throws IllegalArgumentException If the ID is empty or null or
276         * resolve to an empty value after being trimmed
277         */
278        public void setUpId( String upId )
279        {
280            this.upId = StringTools.trim( upId );
281            
282            if ( this.upId.length() == 0 )
283            {
284                this.upId = null;
285                throw new IllegalArgumentException( I18n.err( I18n.ERR_04132 ) );
286            }
287    
288            this.id = StringTools.lowerCaseAscii( this.upId );
289        }
290    
291    
292        /**
293         * <p>
294         * Tells if the attribute is Human Readable. 
295         * </p>
296         * <p>This flag is set by the caller, or implicitly when adding String 
297         * values into an attribute which is not yet declared as Binary.
298         * </p> 
299         * @return
300         */
301        public boolean isHR()
302        {
303            return isHR != null ? isHR : false; 
304        }
305    
306        
307        /**
308         * Checks to see if this attribute is valid along with the values it contains.
309         *
310         * @return true if the attribute and it's values are valid, false otherwise
311         * @throws NamingException if there is a failure to check syntaxes of values
312         */
313        public boolean isValid() throws NamingException
314        {
315            for ( Value<?> value:values )
316            {
317                if ( !value.isValid() )
318                {
319                    return false;
320                }
321            }
322    
323            return true;
324        }
325    
326    
327        /**
328         * Checks to see if this attribute is valid along with the values it contains.
329         *
330         * @return true if the attribute and it's values are valid, false otherwise
331         * @throws NamingException if there is a failure to check syntaxes of values
332         */
333        public boolean isValid( SyntaxChecker checker ) throws NamingException
334        {
335            for ( Value<?> value : values )
336            {
337                if ( !value.isValid( checker ) )
338                {
339                    return false;
340                }
341            }
342    
343            return true;
344        }
345    
346    
347        /**
348         * Adds some values to this attribute. If the new values are already present in
349         * the attribute values, the method has no effect.
350         * <p>
351         * The new values are added at the end of list of values.
352         * </p>
353         * <p>
354         * This method returns the number of values that were added.
355         * </p>
356         * <p>
357         * If the value's type is different from the attribute's type,
358         * a conversion is done. For instance, if we try to set some 
359         * StringValue into a Binary attribute, we just store the UTF-8 
360         * byte array encoding for this StringValue.
361         * </p>
362         * <p>
363         * If we try to store some BinaryValue in a HR attribute, we try to 
364         * convert those BinaryValue assuming they represent an UTF-8 encoded
365         * String. Of course, if it's not the case, the stored value will
366         * be incorrect.
367         * </p>
368         * <p>
369         * It's the responsibility of the caller to check if the stored
370         * values are consistent with the attribute's type.
371         * </p>
372         * <p>
373         * The caller can set the HR flag in order to enforce a type for 
374         * the current attribute, otherwise this type will be set while
375         * adding the first value, using the value's type to set the flag.
376         * </p>
377         * <p>
378         * <b>Note : </b>If the entry contains no value, and the unique added value
379         * is a null length value, then this value will be considered as
380         * a binary value.
381         * </p>
382         * @param val some new values to be added which may be null
383         * @return the number of added values, or 0 if none has been added
384         */
385        public int add( Value<?>... vals )
386        {
387            int nbAdded = 0;
388            ClientBinaryValue nullBinaryValue = null;
389            ClientStringValue nullStringValue = null;
390            boolean nullValueAdded = false;
391            
392            for ( Value<?> val:vals )
393            {
394                if ( val == null )
395                {
396                    // We have a null value. If the HR flag is not set, we will consider 
397                    // that the attribute is not HR. We may change this later
398                    if ( isHR == null )
399                    {
400                        // This is the first value. Add both types, as we 
401                        // don't know yet the attribute type's, but we may
402                        // know later if we add some new value.
403                        // We have to do that because we are using a Set,
404                        // and we can't remove the first element of the Set.
405                        nullBinaryValue = new ClientBinaryValue( null );
406                        nullStringValue = new ClientStringValue( null );
407                        
408                        values.add( nullBinaryValue );
409                        values.add( nullStringValue );
410                        nullValueAdded = true;
411                        nbAdded++;
412                    }
413                    else if ( !isHR )
414                    {
415                        // The attribute type is binary.
416                        nullBinaryValue = new ClientBinaryValue( null );
417                        
418                        // Don't add a value if it already exists. 
419                        if ( !values.contains( nullBinaryValue ) )
420                        {
421                            values.add( nullBinaryValue );
422                            nbAdded++;
423                        }
424                        
425                    }
426                    else
427                    {
428                        // The attribute is HR
429                        nullStringValue = new ClientStringValue( null );
430                        
431                        // Don't add a value if it already exists. 
432                        if ( !values.contains( nullStringValue ) )
433                        {
434                            values.add( nullStringValue );
435                        }
436                    }
437                }
438                else
439                {
440                    // Let's check the value type. 
441                    if ( val instanceof ClientStringValue )
442                    {
443                        // We have a String value
444                        if ( isHR == null )
445                        {
446                            // The attribute type will be set to HR
447                            isHR = true;
448                            values.add( val );
449                            nbAdded++;
450                        }
451                        else if ( !isHR )
452                        {
453                            // The attributeType is binary, convert the
454                            // value to a BinaryValue
455                            ClientBinaryValue cbv = new ClientBinaryValue();
456                            cbv.set( val.getBytes() );
457                            
458                            if ( !contains( cbv ) )
459                            {
460                                values.add( cbv );
461                                nbAdded++;
462                            }
463                        }
464                        else
465                        {
466                            // The attributeType is HR, simply add the value
467                            if ( !contains( val ) )
468                            {
469                                values.add( val );
470                                nbAdded++;
471                            }
472                        }
473                    }
474                    else
475                    {
476                        // We have a Binary value
477                        if ( isHR == null )
478                        {
479                            // The attribute type will be set to binary
480                            isHR = false;
481                            values.add( val );
482                            nbAdded++;
483                        }
484                        else if ( !isHR )
485                        {
486                            // The attributeType is not HR, simply add the value if it does not already exist
487                            if ( !contains( val ) )
488                            {
489                                values.add( val );
490                                nbAdded++;
491                            }
492                        }
493                        else
494                        {
495                            // The attribute Type is HR, convert the
496                            // value to a StringValue
497                            ClientStringValue csv = new ClientStringValue();
498                            csv.set( val.getString() );
499                            
500                            if ( !contains( csv ) )
501                            {
502                                values.add( csv );
503                                nbAdded++;
504                            }
505                        }
506                    }
507                }
508            }
509    
510            // Last, not least, if a nullValue has been added, and if other 
511            // values are all String, we have to keep the correct nullValue,
512            // and to remove the other
513            if ( nullValueAdded )
514            {
515                if ( isHR ) 
516                {
517                    // Remove the Binary value
518                    values.remove( nullBinaryValue );
519                }
520                else
521                {
522                    // Remove the String value
523                    values.remove( nullStringValue );
524                }
525            }
526    
527            return nbAdded;
528        }
529    
530    
531        /**
532         * @see EntryAttribute#add(String...)
533         */
534        public int add( String... vals )
535        {
536            int nbAdded = 0;
537            
538            // First, if the isHR flag is not set, we assume that the
539            // attribute is HR, because we are asked to add some strings.
540            if ( isHR == null )
541            {
542                isHR = true;
543            }
544    
545            // Check the attribute type.
546            if ( isHR )
547            {
548                for ( String val:vals )
549                {
550                    // Call the add(Value) method, if not already present
551                    if ( !contains( val ) )
552                    {
553                        if ( add( new ClientStringValue( val ) ) == 1 )
554                        {
555                            nbAdded++;
556                        }
557                    }
558                }
559            }
560            else
561            {
562                // The attribute is binary. Transform the String to byte[]
563                for ( String val:vals )
564                {
565                    byte[] valBytes = null;
566                    
567                    if ( val != null )
568                    {
569                        valBytes = StringTools.getBytesUtf8( val );
570                    }
571                    
572                    // Now call the add(Value) method
573                    if ( add( new ClientBinaryValue( valBytes ) ) == 1 )
574                    {
575                        nbAdded++;
576                    }
577                }
578            }
579            
580            return nbAdded;
581        }    
582        
583        
584        /**
585         * Adds some values to this attribute. If the new values are already present in
586         * the attribute values, the method has no effect.
587         * <p>
588         * The new values are added at the end of list of values.
589         * </p>
590         * <p>
591         * This method returns the number of values that were added.
592         * </p>
593         * If the value's type is different from the attribute's type,
594         * a conversion is done. For instance, if we try to set some String
595         * into a Binary attribute, we just store the UTF-8 byte array 
596         * encoding for this String.
597         * If we try to store some byte[] in a HR attribute, we try to 
598         * convert those byte[] assuming they represent an UTF-8 encoded
599         * String. Of course, if it's not the case, the stored value will
600         * be incorrect.
601         * <br>
602         * It's the responsibility of the caller to check if the stored
603         * values are consistent with the attribute's type.
604         * <br>
605         * The caller can set the HR flag in order to enforce a type for 
606         * the current attribute, otherwise this type will be set while
607         * adding the first value, using the value's type to set the flag.
608         *
609         * @param val some new values to be added which may be null
610         * @return the number of added values, or 0 if none has been added
611         */
612        public int add( byte[]... vals )
613        {
614            int nbAdded = 0;
615            
616            // First, if the isHR flag is not set, we assume that the
617            // attribute is not HR, because we are asked to add some byte[].
618            if ( isHR == null )
619            {
620                isHR = false;
621            }
622    
623            // Check the attribute type.
624            if ( isHR )
625            {
626                // The attribute is HR. Transform the byte[] to String
627                for ( byte[] val:vals )
628                {
629                    String valString = null;
630                    
631                    if ( val != null )
632                    {
633                        valString = StringTools.utf8ToString( val );
634                    }
635                    
636                    // Now call the add(Value) method, if not already present
637                    if ( !contains( val ) )
638                    {
639                        if ( add( new ClientStringValue( valString ) ) == 1 )
640                        {
641                            nbAdded++;
642                        }
643                    }
644                }
645            }
646            else
647            {
648                for ( byte[] val:vals )
649                {
650                    if ( add( new ClientBinaryValue( val ) ) == 1 )
651                    {
652                        nbAdded++;
653                    }
654                }
655            }
656            
657            return nbAdded;
658        }    
659        
660        
661        /**
662         * Remove all the values from this attribute.
663         */
664        public void clear()
665        {
666            values.clear();
667        }
668    
669    
670        /**
671         * <p>
672         * Indicates whether the specified values are some of the attribute's values.
673         * </p>
674         * <p>
675         * If the Attribute is HR, the binary values will be converted to String before
676         * being checked.
677         * </p>
678         *
679         * @param vals the values
680         * @return true if this attribute contains all the values, otherwise false
681         */
682        public boolean contains( Value<?>... vals )
683        {
684            if ( isHR == null )
685            {
686                // If this flag is null, then there is no values.
687                return false;
688            }
689    
690            if ( isHR )
691            {
692                // Iterate through all the values, convert the Binary values
693                // to String values, and quit id any of the values is not
694                // contained in the object
695                for ( Value<?> val:vals )
696                {
697                    if ( val instanceof ClientStringValue )
698                    {
699                        if ( !values.contains( val ) )
700                        {
701                            return false;
702                        }
703                    }
704                    else
705                    {
706                        byte[] binaryVal = val.getBytes();
707                        
708                        // We have to convert the binary value to a String
709                        if ( ! values.contains( new ClientStringValue( StringTools.utf8ToString( binaryVal ) ) ) )
710                        {
711                            return false;
712                        }
713                    }
714                }
715            }
716            else
717            {
718                // Iterate through all the values, convert the String values
719                // to binary values, and quit id any of the values is not
720                // contained in the object
721                for ( Value<?> val:vals )
722                {
723                    if ( val.isBinary() )
724                    {
725                        if ( !values.contains( val ) )
726                        {
727                            return false;
728                        }
729                    }
730                    else
731                    {
732                        String stringVal = val.getString();
733                        
734                        // We have to convert the binary value to a String
735                        if ( ! values.contains( new ClientBinaryValue( StringTools.getBytesUtf8( stringVal ) ) ) )
736                        {
737                            return false;
738                        }
739                    }
740                }
741            }
742            
743            return true;
744        }
745    
746    
747        /**
748         * <p>
749         * Indicates whether the specified values are some of the attribute's values.
750         * </p>
751         * <p>
752         * If the Attribute is not HR, the values will be converted to byte[]
753         * </p>
754         *
755         * @param vals the values
756         * @return true if this attribute contains all the values, otherwise false
757         */
758        public boolean contains( String... vals )
759        {
760            if ( isHR == null )
761            {
762                // If this flag is null, then there is no values.
763                return false;
764            }
765    
766            if ( isHR )
767            {
768                // Iterate through all the values, and quit if we 
769                // don't find one in the values
770                for ( String val:vals )
771                {
772                    if ( !contains( new ClientStringValue( val ) ) )
773                    {
774                        return false;
775                    }
776                }
777            }
778            else
779            {
780                // As the attribute type is binary, we have to convert 
781                // the values before checking for them in the values
782                // Iterate through all the values, and quit if we 
783                // don't find one in the values
784                for ( String val:vals )
785                {
786                    byte[] binaryVal = StringTools.getBytesUtf8( val );
787    
788                    if ( !contains( new ClientBinaryValue( binaryVal ) ) )
789                    {
790                        return false;
791                    }
792                }
793            }
794            
795            return true;
796        }
797        
798        
799        /**
800         * <p>
801         * Indicates whether the specified values are some of the attribute's values.
802         * </p>
803         * <p>
804         * If the Attribute is HR, the values will be converted to String
805         * </p>
806         *
807         * @param vals the values
808         * @return true if this attribute contains all the values, otherwise false
809         */
810        public boolean contains( byte[]... vals )
811        {
812            if ( isHR == null )
813            {
814                // If this flag is null, then there is no values.
815                return false;
816            }
817    
818            if ( !isHR )
819            {
820                // Iterate through all the values, and quit if we 
821                // don't find one in the values
822                for ( byte[] val:vals )
823                {
824                    if ( !contains( new ClientBinaryValue( val ) ) )
825                    {
826                        return false;
827                    }
828                }
829            }
830            else
831            {
832                // As the attribute type is String, we have to convert 
833                // the values before checking for them in the values
834                // Iterate through all the values, and quit if we 
835                // don't find one in the values
836                for ( byte[] val:vals )
837                {
838                    String stringVal = StringTools.utf8ToString( val );
839    
840                    if ( !contains( new ClientStringValue( stringVal ) ) )
841                    {
842                        return false;
843                    }
844                }
845            }
846            
847            return true;
848        }
849        
850        
851        /**
852         * @see EntryAttribute#contains(Object...)
853         */
854        public boolean contains( Object... vals )
855        {
856            boolean isHR = true;
857            boolean seen = false;
858            
859            // Iterate through all the values, and quit if we 
860            // don't find one in the values
861            for ( Object val:vals )
862            {
863                if ( ( val instanceof String ) ) 
864                {
865                    if ( !seen )
866                    {
867                        isHR = true;
868                        seen = true;
869                    }
870    
871                    if ( isHR )
872                    {
873                        if ( !contains( (String)val ) )
874                        {
875                            return false;
876                        }
877                    }
878                    else
879                    {
880                        return false;
881                    }
882                }
883                else
884                {
885                    if ( !seen )
886                    {
887                        isHR = false;
888                        seen = true;
889                    }
890    
891                    if ( !isHR )
892                    {
893                        if ( !contains( (byte[])val ) )
894                        {
895                            return false;
896                        }
897                    }
898                    else
899                    {
900                        return false;
901                    }
902                }
903            }
904            
905            return true;
906        }
907    
908        
909        /**
910         * <p>
911         * Get the first value of this attribute. If there is none, 
912         * null is returned.
913         * </p>
914         * <p>
915         * Note : even if we are storing values into a Set, one can assume
916         * the values are ordered following the insertion order.
917         * </p>
918         * <p> 
919         * This method is meant to be used if the attribute hold only one value.
920         * </p>
921         * 
922         *  @return The first value for this attribute.
923         */
924        public Value<?> get()
925        {
926            if ( values.isEmpty() )
927            {
928                return null;
929            }
930            
931            return values.iterator().next();
932        }
933    
934    
935        /**
936         * <p>
937         * Get the nth value of this attribute. If there is none, 
938         * null is returned.
939         * </p>
940         * <p>
941         * Note : even if we are storing values into a Set, one can assume
942         * the values are ordered following the insertion order.
943         * </p>
944         * <p> 
945         * 
946         * @param i the index  of the value to get
947         *  @return The nth value for this attribute.
948         */
949        public Value<?> get( int i )
950        {
951            if ( values.size() < i )
952            {
953                return null;
954            }
955            else
956            {
957                int n = 0;
958                
959                for ( Value<?> value:values )
960                {
961                    if ( n == i )
962                    {
963                        return value;
964                    }
965                    
966                    n++;
967                }
968            }
969            
970            // fallback to 
971            return null;
972        }
973        
974        
975        /**
976         * Returns an iterator over all the attribute's values.
977         * <p>
978         * The effect on the returned enumeration of adding or removing values of
979         * the attribute is not specified.
980         * </p>
981         * <p>
982         * This method will throw any <code>NamingException</code> that occurs.
983         * </p>
984         *
985         * @return an enumeration of all values of the attribute
986         */
987        public Iterator<Value<?>> getAll()
988        {
989            return iterator();
990        }
991    
992    
993        /**
994         * Retrieves the number of values in this attribute.
995         *
996         * @return the number of values in this attribute, including any values
997         * wrapping a null value if there is one
998         */
999        public int size()
1000        {
1001            return values.size();
1002        }
1003    
1004    
1005        /**
1006         * <p>
1007         * Removes all the  values that are equal to the given values.
1008         * </p>
1009         * <p>
1010         * Returns true if all the values are removed.
1011         * </p>
1012         * <p>
1013         * If the attribute type is HR and some value which are not String, we
1014         * will convert the values first (same thing for a non-HR attribute).
1015         * </p>
1016         *
1017         * @param vals the values to be removed
1018         * @return true if all the values are removed, otherwise false
1019         */
1020        public boolean remove( Value<?>... vals )
1021        {
1022            if ( ( isHR == null ) || ( values.size() == 0 ) ) 
1023            {
1024                // Trying to remove a value from an empty list will fail
1025                return false;
1026            }
1027            
1028            boolean removed = true;
1029            
1030            if ( isHR )
1031            {
1032                for ( Value<?> val:vals )
1033                {
1034                    if ( val instanceof ClientStringValue )
1035                    {
1036                        removed &= values.remove( val );
1037                    }
1038                    else
1039                    {
1040                        // Convert the binary value to a string value
1041                        byte[] binaryVal = val.getBytes();
1042                        removed &= values.remove( new ClientStringValue( StringTools.utf8ToString( binaryVal ) ) );
1043                    }
1044                }
1045            }
1046            else
1047            {
1048                for ( Value<?> val:vals )
1049                {
1050                    removed &= values.remove( val );
1051                }
1052            }
1053            
1054            return removed;
1055        }
1056    
1057    
1058        /**
1059         * <p>
1060         * Removes all the  values that are equal to the given values.
1061         * </p>
1062         * <p>
1063         * Returns true if all the values are removed.
1064         * </p>
1065         * <p>
1066         * If the attribute type is HR, then the values will be first converted
1067         * to String
1068         * </p>
1069         *
1070         * @param vals the values to be removed
1071         * @return true if all the values are removed, otherwise false
1072         */
1073        public boolean remove( byte[]... vals )
1074        {
1075            if ( ( isHR == null ) || ( values.size() == 0 ) ) 
1076            {
1077                // Trying to remove a value from an empty list will fail
1078                return false;
1079            }
1080            
1081            boolean removed = true;
1082            
1083            if ( !isHR )
1084            {
1085                // The attribute type is not HR, we can directly process the values
1086                for ( byte[] val:vals )
1087                {
1088                    ClientBinaryValue value = new ClientBinaryValue( val );
1089                    removed &= values.remove( value );
1090                }
1091            }
1092            else
1093            {
1094                // The attribute type is String, we have to convert the values
1095                // to String before removing them
1096                for ( byte[] val:vals )
1097                {
1098                    ClientStringValue value = new ClientStringValue( StringTools.utf8ToString( val ) );
1099                    removed &= values.remove( value );
1100                }
1101            }
1102            
1103            return removed;
1104        }
1105    
1106    
1107        /**
1108         * Removes all the  values that are equal to the given values.
1109         * <p>
1110         * Returns true if all the values are removed.
1111         * </p>
1112         * <p>
1113         * If the attribute type is not HR, then the values will be first converted
1114         * to byte[]
1115         * </p>
1116         *
1117         * @param vals the values to be removed
1118         * @return true if all the values are removed, otherwise false
1119         */
1120        public boolean remove( String... vals )
1121        {
1122            if ( ( isHR == null ) || ( values.size() == 0 ) ) 
1123            {
1124                // Trying to remove a value from an empty list will fail
1125                return false;
1126            }
1127            
1128            boolean removed = true;
1129            
1130            if ( isHR )
1131            {
1132                // The attribute type is HR, we can directly process the values
1133                for ( String val:vals )
1134                {
1135                    ClientStringValue value = new ClientStringValue( val );
1136                    removed &= values.remove( value );
1137                }
1138            }
1139            else
1140            {
1141                // The attribute type is binary, we have to convert the values
1142                // to byte[] before removing them
1143                for ( String val:vals )
1144                {
1145                    ClientBinaryValue value = new ClientBinaryValue( StringTools.getBytesUtf8( val ) );
1146                    removed &= values.remove( value );
1147                }
1148            }
1149            
1150            return removed;
1151        }
1152    
1153    
1154        /**
1155         * An iterator on top of the stored values.
1156         * 
1157         * @return an iterator over the stored values.
1158         */
1159        public Iterator<Value<?>> iterator()
1160        {
1161            return values.iterator();
1162        }
1163        
1164        
1165        /**
1166         * Puts some values to this attribute.
1167         * <p>
1168         * The new values will replace the previous values.
1169         * </p>
1170         * <p>
1171         * This method returns the number of values that were put.
1172         * </p>
1173         *
1174         * @param val some values to be put which may be null
1175         * @return the number of added values, or 0 if none has been added
1176         */
1177        public int put( String... vals )
1178        {
1179            values.clear();
1180            return add( vals );
1181        }
1182        
1183        
1184        /**
1185         * Puts some values to this attribute.
1186         * <p>
1187         * The new values will replace the previous values.
1188         * </p>
1189         * <p>
1190         * This method returns the number of values that were put.
1191         * </p>
1192         *
1193         * @param val some values to be put which may be null
1194         * @return the number of added values, or 0 if none has been added
1195         */
1196        public int put( byte[]... vals )
1197        {
1198            values.clear();
1199            return add( vals );
1200        }
1201    
1202        
1203        /**
1204         * Puts some values to this attribute.
1205         * <p>
1206         * The new values are replace the previous values.
1207         * </p>
1208         * <p>
1209         * This method returns the number of values that were put.
1210         * </p>
1211         *
1212         * @param val some values to be put which may be null
1213         * @return the number of added values, or 0 if none has been added
1214         */
1215        public int put( Value<?>... vals )
1216        {
1217            values.clear();
1218            return add( vals );
1219        }
1220        
1221        
1222        /**
1223         * <p>
1224         * Puts a list of values into this attribute.
1225         * </p>
1226         * <p>
1227         * The new values will replace the previous values.
1228         * </p>
1229         * <p>
1230         * This method returns the number of values that were put.
1231         * </p>
1232         *
1233         * @param vals the values to be put
1234         * @return the number of added values, or 0 if none has been added
1235         */
1236        public int put( List<Value<?>> vals )
1237        {
1238            values.clear();
1239            
1240            // Transform the List to an array
1241            Value<?>[] valArray = new Value<?>[vals.size()];
1242            return add( vals.toArray( valArray ) );
1243        }
1244        
1245        //-------------------------------------------------------------------------
1246        // Overloaded Object classes
1247        //-------------------------------------------------------------------------
1248        /**
1249         * The hashCode is based on the id, the isHR flag and 
1250         * on the internal values.
1251         *  
1252         * @see Object#hashCode()
1253         * @return the instance's hashcode 
1254         */
1255        public int hashCode()
1256        {
1257            int h = 37;
1258            
1259            if ( isHR != null )
1260            {
1261                h = h*17 + isHR.hashCode();
1262            }
1263            
1264            if ( id != null )
1265            {
1266                h = h*17 + id.hashCode();
1267            }
1268            
1269            for ( Value<?> value:values )
1270            {
1271                h = h*17 + value.hashCode();
1272            }
1273            
1274            return h;
1275        }
1276        
1277        
1278        /**
1279         * @see Object#equals(Object)
1280         */
1281        public boolean equals( Object obj )
1282        {
1283            if ( obj == this )
1284            {
1285                return true;
1286            }
1287            
1288            if ( ! (obj instanceof EntryAttribute ) )
1289            {
1290                return false;
1291            }
1292            
1293            EntryAttribute other = (EntryAttribute)obj;
1294            
1295            if ( id == null )
1296            {
1297                if ( other.getId() != null )
1298                {
1299                    return false;
1300                }
1301            }
1302            else
1303            {
1304                if ( other.getId() == null )
1305                {
1306                    return false;
1307                }
1308                else
1309                {
1310                    if ( !id.equals( other.getId() ) )
1311                    {
1312                        return false;
1313                    }
1314                }
1315            }
1316            
1317            if ( isHR() !=  other.isHR() )
1318            {
1319                return false;
1320            }
1321            
1322            if ( values.size() != other.size() )
1323            {
1324                return false;
1325            }
1326            
1327            for ( Value<?> val:values )
1328            {
1329                if ( ! other.contains( val ) )
1330                {
1331                    return false;
1332                }
1333            }
1334            
1335            return true;
1336        }
1337        
1338        
1339        /**
1340         * @see Cloneable#clone()
1341         */
1342        public EntryAttribute clone()
1343        {
1344            try
1345            {
1346                DefaultClientAttribute attribute = (DefaultClientAttribute)super.clone();
1347                
1348                attribute.values = new LinkedHashSet<Value<?>>( values.size() );
1349                
1350                for ( Value<?> value:values )
1351                {
1352                    attribute.values.add( value.clone() );
1353                }
1354                
1355                return attribute;
1356            }
1357            catch ( CloneNotSupportedException cnse )
1358            {
1359                return null;
1360            }
1361        }
1362        
1363        
1364        /**
1365         * @see Object#toString() 
1366         */
1367        public String toString()
1368        {
1369            StringBuilder sb = new StringBuilder();
1370            
1371            if ( ( values != null ) && ( values.size() != 0 ) )
1372            {
1373                for ( Value<?> value:values )
1374                {
1375                    sb.append( "    " ).append( upId ).append( ": " );
1376                    
1377                    if ( value.isNull() )
1378                    {
1379                        sb.append( "''" );
1380                    }
1381                    else
1382                    {
1383                        sb.append( value );
1384                    }
1385                    
1386                    sb.append( '\n' );
1387                }
1388            }
1389            else
1390            {
1391                sb.append( "    " ).append( upId ).append( ": (null)\n" );
1392            }
1393            
1394            return sb.toString();
1395        }
1396    
1397    
1398        /**
1399         * @see Externalizable#writeExternal(ObjectOutput)
1400         * <p>
1401         * 
1402         * This is the place where we serialize attributes, and all theirs
1403         * elements. 
1404         * 
1405         * The inner structure is :
1406         * 
1407         */
1408        public void writeExternal( ObjectOutput out ) throws IOException
1409        {
1410            // Write the UPId (the id will be deduced from the upID)
1411            out.writeUTF( upId );
1412            
1413            // Write the HR flag, if not null
1414            if ( isHR != null )
1415            {
1416                out.writeBoolean( true );
1417                out.writeBoolean( isHR );
1418            }
1419            else
1420            {
1421                out.writeBoolean( false );
1422            }
1423            
1424            // Write the number of values
1425            out.writeInt( size() );
1426            
1427            if ( size() > 0 ) 
1428            {
1429                // Write each value
1430                for ( Value<?> value:values )
1431                {
1432                    // Write the value
1433                    out.writeObject( value );
1434                }
1435            }
1436            
1437            out.flush();
1438        }
1439    
1440        
1441        /**
1442         * @see Externalizable#readExternal(ObjectInput)
1443         */
1444        public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException
1445        {
1446            // Read the ID and the UPId
1447            upId = in.readUTF();
1448            
1449            // Compute the id
1450            setUpId( upId );
1451            
1452            // Read the HR flag, if not null
1453            if ( in.readBoolean() )
1454            {
1455                isHR = in.readBoolean();
1456            }
1457    
1458            // Read the number of values
1459            int nbValues = in.readInt();
1460    
1461            if ( nbValues > 0 )
1462            {
1463                for ( int i = 0; i < nbValues; i++ )
1464                {
1465                    values.add( (Value<?>)in.readObject() );
1466                }
1467            }
1468        }
1469    }