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