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