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;
020    
021    
022    import java.io.Externalizable;
023    import java.io.IOException;
024    import java.io.ObjectInput;
025    import java.io.ObjectOutput;
026    
027    import javax.naming.NamingException;
028    
029    import org.apache.directory.shared.asn1.primitives.OID;
030    import org.apache.directory.shared.i18n.I18n;
031    import org.apache.directory.shared.ldap.entry.client.DefaultClientAttribute;
032    import org.apache.directory.shared.ldap.exception.LdapException;
033    import org.apache.directory.shared.ldap.schema.AttributeType;
034    import org.apache.directory.shared.ldap.util.StringTools;
035    import org.slf4j.Logger;
036    import org.slf4j.LoggerFactory;
037    
038    
039    /**
040     * A server side entry attribute aware of schema.
041     *
042     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
043     * @version $Rev$, $Date$
044     */
045    public final class DefaultServerAttribute extends DefaultClientAttribute implements EntryAttribute
046    {
047        public static final long serialVersionUID = 1L;
048        
049        /** logger for reporting errors that might not be handled properly upstream */
050        private static final Logger LOG = LoggerFactory.getLogger( DefaultServerAttribute.class );
051        
052        //-------------------------------------------------------------------------
053        // Constructors
054        //-------------------------------------------------------------------------
055        /**
056         * 
057         * Creates a new instance of DefaultServerAttribute, by copying
058         * another attribute, which can be a ClientAttribute. If the other
059         * attribute is a ServerAttribute, it will be copied.
060         *
061         * @param attributeType The attribute's type 
062         * @param attribute The attribute to be copied
063         */
064        public DefaultServerAttribute( AttributeType attributeType, EntryAttribute attribute )
065        {
066            // Copy the common values. isHR is only available on a ServerAttribute 
067            this.attributeType = attributeType;
068            this.id = attribute.getId();
069            this.upId = attribute.getUpId();
070    
071            if ( attributeType == null )
072            {
073                isHR = attribute.isHR();
074    
075                // Copy all the values
076                for ( Value<?> value:attribute )
077                {
078                    add( value.clone() );
079                }
080            }
081            else
082            {
083                
084                isHR = attributeType.getSyntax().isHumanReadable();
085    
086                // Copy all the values
087                for ( Value<?> clientValue:attribute )
088                {
089                    Value<?> serverValue = null; 
090    
091                    // We have to convert the value first
092                    if ( clientValue instanceof StringValue )
093                    {
094                        if ( isHR )
095                        {
096                            serverValue = new StringValue( attributeType, clientValue.getString() );
097                        }
098                        else
099                        {
100                            // We have to convert the value to a binary value first
101                            serverValue = new BinaryValue( attributeType, 
102                                clientValue.getBytes() );
103                        }
104                    }
105                    else if ( clientValue instanceof BinaryValue )
106                    {
107                        if ( isHR )
108                        {
109                            // We have to convert the value to a String value first
110                            serverValue = new StringValue( attributeType, 
111                                clientValue.getString() );
112                        }
113                        else
114                        {
115                            serverValue = new BinaryValue( attributeType, clientValue.getBytes() );
116                        }
117                    }
118    
119                    add( serverValue );
120                }
121            }
122        }
123        
124        
125        // maybe have some additional convenience constructors which take
126        // an initial value as a string or a byte[]
127        /**
128         * Create a new instance of a EntryAttribute, without ID nor value.
129         * 
130         * @param attributeType the attributeType for the empty attribute added into the entry
131         */
132        public DefaultServerAttribute( AttributeType attributeType )
133        {
134            if ( attributeType == null )
135            {
136                throw new IllegalArgumentException( I18n.err( I18n.ERR_04442 ) );
137            }
138            
139            setAttributeType( attributeType );
140        }
141    
142    
143        /**
144         * Create a new instance of a EntryAttribute, without value.
145         * 
146         * @param upId the ID for the added attributeType
147         * @param attributeType the added AttributeType
148         */
149        public DefaultServerAttribute( String upId, AttributeType attributeType )
150        {
151            if ( attributeType == null ) 
152            {
153                String message = I18n.err( I18n.ERR_04442 );
154                LOG.error( message );
155                throw new IllegalArgumentException( message );
156            }
157    
158            setAttributeType( attributeType );
159            setUpId( upId, attributeType );
160        }
161    
162    
163        /**
164         * Doc me more!
165         *
166         * If the value does not correspond to the same attributeType, then it's
167         * wrapped value is copied into a new Value which uses the specified
168         * attributeType.
169         *
170         * @param attributeType the attribute type according to the schema
171         * @param vals an initial set of values for this attribute
172         */
173        public DefaultServerAttribute( AttributeType attributeType, Value<?>... vals )
174        {
175            this( null, attributeType, vals );
176        }
177    
178    
179        /**
180         * Doc me more!
181         *
182         * If the value does not correspond to the same attributeType, then it's
183         * wrapped value is copied into a new Value which uses the specified
184         * attributeType.
185         * 
186         * Otherwise, the value is stored, but as a reference. It's not a copy.
187         *
188         * @param upId the ID of the added attribute
189         * @param attributeType the attribute type according to the schema
190         * @param vals an initial set of values for this attribute
191         */
192        public DefaultServerAttribute( String upId, AttributeType attributeType, Value<?>... vals )
193        {
194            if ( attributeType == null )
195            {
196                throw new IllegalArgumentException( I18n.err( I18n.ERR_04442 ) );
197            }
198            
199            setAttributeType( attributeType );
200            setUpId( upId, attributeType );
201            add( vals );
202        }
203    
204    
205        /**
206         * Create a new instance of a EntryAttribute, without ID but with some values.
207         * 
208         * @param attributeType The attributeType added on creation
209         * @param vals The added value for this attribute
210         */
211        public DefaultServerAttribute( AttributeType attributeType, String... vals )
212        {
213            this( null, attributeType, vals );
214        }
215    
216    
217        /**
218         * Create a new instance of a EntryAttribute.
219         * 
220         * @param upId the ID for the added attribute
221         * @param attributeType The attributeType added on creation
222         * @param vals the added values for this attribute
223         */
224        public DefaultServerAttribute( String upId, AttributeType attributeType, String... vals )
225        {
226            if ( attributeType == null )
227            {
228                throw new IllegalArgumentException( I18n.err( I18n.ERR_04442 ) );
229            }
230    
231            setAttributeType( attributeType );
232            add( vals );
233            setUpId( upId, attributeType );
234        }
235    
236    
237        /**
238         * Create a new instance of a EntryAttribute, with some byte[] values.
239         * 
240         * @param attributeType The attributeType added on creation
241         * @param vals The value for the added attribute
242         */
243        public DefaultServerAttribute( AttributeType attributeType, byte[]... vals )
244        {
245            this( null, attributeType, vals );
246        }
247    
248    
249        /**
250         * Create a new instance of a EntryAttribute, with some byte[] values.
251         * 
252         * @param upId the ID for the added attribute
253         * @param attributeType the AttributeType to be added
254         * @param vals the values for the added attribute
255         */
256        public DefaultServerAttribute( String upId, AttributeType attributeType, byte[]... vals )
257        {
258            if ( attributeType == null )
259            {
260                throw new IllegalArgumentException( I18n.err( I18n.ERR_04442 ) );
261            }
262    
263            setAttributeType( attributeType );
264            add( vals );
265            setUpId( upId, attributeType );
266        }
267        
268        
269        //-------------------------------------------------------------------------
270        // API
271        //-------------------------------------------------------------------------
272        /**
273         * <p>
274         * Adds some values to this attribute. If the new values are already present in
275         * the attribute values, the method has no effect.
276         * </p>
277         * <p>
278         * The new values are added at the end of list of values.
279         * </p>
280         * <p>
281         * This method returns the number of values that were added.
282         * </p>
283         * <p>
284         * If the value's type is different from the attribute's type,
285         * the value is not added.
286         * </p>
287         * It's the responsibility of the caller to check if the stored
288         * values are consistent with the attribute's type.
289         * <p>
290         *
291         * @param vals some new values to be added which may be null
292         * @return the number of added values, or 0 if none has been added
293         */
294        public int add( byte[]... vals )
295        {
296            if ( !isHR )
297            {
298                int nbAdded = 0;
299                
300                for ( byte[] val:vals )
301                {
302                    Value<?> value = new BinaryValue( attributeType, val );
303                    
304                    try
305                    {
306                        value.normalize();
307                    }
308                    catch( LdapException ne )
309                    {
310                        // The value can't be normalized : we don't add it.
311                        LOG.error( I18n.err( I18n.ERR_04449, StringTools.dumpBytes( val ) ) );
312                        return 0;
313                    }
314                    
315                    if ( add( value ) != 0 )
316                    {
317                        nbAdded++;
318                    }
319                    else
320                    {
321                        LOG.error( I18n.err( I18n.ERR_04450, StringTools.dumpBytes( val ) ) );
322                    }
323                }
324                
325                return nbAdded;
326            }
327            else
328            {
329                // We can't add Binary values into a String serverAttribute
330                return 0;
331            }
332        }    
333    
334    
335        /**
336         * <p>
337         * Adds some values to this attribute. If the new values are already present in
338         * the attribute values, the method has no effect.
339         * </p>
340         * <p>
341         * The new values are added at the end of list of values.
342         * </p>
343         * <p>
344         * This method returns the number of values that were added.
345         * </p>
346         * If the value's type is different from the attribute's type,
347         * the value is not added.
348         *
349         * @param vals some new values to be added which may be null
350         * @return the number of added values, or 0 if none has been added
351         */
352        public int add( String... vals )
353        {
354            if ( isHR )
355            {
356                int nbAdded = 0;
357                
358                for ( String val:vals )
359                {
360                    Value<String> newValue = new StringValue( attributeType, val );
361                    
362                    if ( add( newValue ) != 0 )
363                    {
364                        nbAdded++;
365                    }
366                    else
367                    {
368                        LOG.error( I18n.err( I18n.ERR_04450, val ) );
369                    }
370                }
371                
372                return nbAdded;
373            }
374            else
375            {
376                // We can't add String values into a Binary serverAttribute
377                return 0;
378            }
379        }    
380    
381    
382        /**
383         * @see EntryAttribute#add(org.apache.directory.shared.ldap.entry.Value...)
384         * 
385         * @return the number of added values into this attribute
386         */
387        public int add( Value<?>... vals )
388        {
389            int nbAdded = 0;
390            
391            for ( Value<?> val:vals )
392            {
393                if ( attributeType.getSyntax().isHumanReadable() )
394                {
395                    if ( ( val == null ) || val.isNull() )
396                    {
397                        Value<String> nullSV = new StringValue( attributeType, (String)null );
398                        
399                        if ( values.add( nullSV ) )
400                        {
401                            nbAdded++;
402                        }
403                    }
404                    else if ( val instanceof StringValue )
405                    {
406                        StringValue stringValue = (StringValue)val;
407                        
408                        if ( stringValue.getAttributeType() == null )
409                        {
410                            stringValue.apply( attributeType );
411                        }
412                        
413                        if ( values.add( val ) )
414                        {
415                            nbAdded++;
416                        }
417                    }
418                    else
419                    {
420                        String message = I18n.err( I18n.ERR_04451 );
421                        LOG.error( message );
422                    }
423                }
424                else
425                {
426                    if ( val == null )
427                    {
428                        Value<byte[]> nullSV = new BinaryValue( attributeType, (byte[])null );
429                        
430                        if ( values.add( nullSV ) )
431                        {
432                            nbAdded++;
433                        }
434                    }
435                    else
436                    {
437                        if ( val instanceof BinaryValue )
438                        {
439                            BinaryValue binaryValue = (BinaryValue)val;
440                            
441                            if ( binaryValue.getAttributeType() == null )
442                            {
443                                binaryValue = new BinaryValue( attributeType, val.getBytes() ); 
444                            }
445        
446                            if ( values.add( binaryValue ) )
447                            {
448                                nbAdded++;
449                            }
450                        }
451                        else
452                        {
453                            String message = I18n.err( I18n.ERR_04452 );
454                            LOG.error( message );
455                        }
456                    }
457                }
458            }
459            
460            return nbAdded;
461        }
462    
463    
464        /**
465         * Remove all the values from this attribute type, including a 
466         * null value. 
467         */
468        public void clear()
469        {
470            values.clear();
471        }
472    
473    
474        /**
475         * <p>
476         * Indicates whether all the specified values are attribute's values. If
477         * at least one value is not an attribute's value, this method will return 
478         * <code>false</code>
479         * </p>
480         * <p>
481         * If the Attribute is HR, this method will returns <code>false</code>
482         * </p>
483         *
484         * @param vals the values
485         * @return true if this attribute contains all the values, otherwise false
486         */
487        public boolean contains( byte[]... vals )
488        {
489            if ( !isHR )
490            {
491                // Iterate through all the values, and quit if we 
492                // don't find one in the values
493                for ( byte[] val:vals )
494                {
495                    BinaryValue value = new BinaryValue( attributeType, val );
496                    
497                    try
498                    {
499                        value.normalize();
500                    }
501                    catch ( LdapException ne )
502                    {
503                        return false;
504                    }
505                    
506                    if ( !values.contains( value ) )
507                    {
508                        return false;
509                    }
510                }
511                
512                return true;
513            }
514            else
515            {
516                return false;
517            }
518        }
519        
520        
521        /**
522         * <p>
523         * Indicates whether all the specified values are attribute's values. If
524         * at least one value is not an attribute's value, this method will return 
525         * <code>false</code>
526         * </p>
527         * <p>
528         * If the Attribute is not HR, this method will returns <code>false</code>
529         * </p>
530         *
531         * @param vals the values
532         * @return true if this attribute contains all the values, otherwise false
533         */
534        public boolean contains( String... vals )
535        {
536            if ( isHR )
537            {
538                // Iterate through all the values, and quit if we 
539                // don't find one in the values
540                for ( String val:vals )
541                {
542                    StringValue value = new StringValue( attributeType, val );
543                    
544                    if ( !values.contains( value ) )
545                    {
546                        return false;
547                    }
548                }
549                
550                return true;
551            }
552            else
553            {
554                return false;
555            }
556        }
557        
558        
559        /**
560         * <p>
561         * Indicates whether the specified values are some of the attribute's values.
562         * </p>
563         * <p>
564         * If the Attribute is HR, te metho will only accept String Values. Otherwise, 
565         * it will only accept Binary values.
566         * </p>
567         *
568         * @param vals the values
569         * @return true if this attribute contains all the values, otherwise false
570         */
571        public boolean contains( Value<?>... vals )
572        {
573            // Iterate through all the values, and quit if we 
574            // don't find one in the values. We have to separate the check
575            // depending on the isHR flag value.
576            if ( isHR )
577            {
578                for ( Value<?> val:vals )
579                {
580                    if ( val instanceof StringValue )
581                    {
582                        StringValue stringValue = (StringValue)val;
583                        
584                        if ( stringValue.getAttributeType() == null )
585                        {
586                            stringValue.apply( attributeType );
587                        }
588                        
589                        if ( !values.contains( val ) )
590                        {
591                            return false;
592                        }
593                    }
594                    else
595                    {
596                        // Not a String value
597                        return false;
598                    }
599                }
600            }
601            else
602            {
603                for ( Value<?> val:vals )
604                {
605                    if ( val instanceof BinaryValue )
606                    {
607                        if ( !values.contains( val ) )
608                        {
609                            return false;
610                        }
611                    }
612                    else
613                    {
614                        // Not a Binary value
615                        return false;
616                    }
617                }
618            }
619            
620            return true;
621        }
622        
623        
624        /**
625         * <p>
626         * Checks to see if this attribute is valid along with the values it contains.
627         * </p>
628         * <p>
629         * An attribute is valid if :
630         * <li>All of its values are valid with respect to the attributeType's syntax checker</li>
631         * <li>If the attributeType is SINGLE-VALUE, then no more than a value should be present</li>
632         *</p>
633         * @return true if the attribute and it's values are valid, false otherwise
634         * @throws NamingException if there is a failure to check syntaxes of values
635         */
636        public boolean isValid() throws LdapException
637        {
638            // First check if the attribute has more than one value
639            // if the attribute is supposed to be SINGLE_VALUE
640            if ( attributeType.isSingleValued() && ( values.size() > 1 ) )
641            {
642                return false;
643            }
644    
645            // Check that we can have no value for this attributeType
646            if ( values.size() == 0 )
647            {
648                return attributeType.getSyntax().getSyntaxChecker().isValidSyntax( null );
649            }
650    
651            for ( Value<?> value : values )
652            {
653                if ( ! value.isValid() )
654                {
655                    return false;
656                }
657            }
658            
659            return true;
660        }
661    
662    
663        /**
664         * @see EntryAttribute#remove(byte[]...)
665         * 
666         * @return <code>true</code> if all the values shave been removed from this attribute
667         */
668        public boolean remove( byte[]... vals )
669        {
670            if ( isHR ) 
671            {
672                return false;
673            }
674            
675            boolean removed = true;
676            
677            for ( byte[] val:vals )
678            {
679                BinaryValue value = new BinaryValue( attributeType, val );
680                removed &= values.remove( value );
681            }
682            
683            return removed;
684        }
685    
686    
687        /**
688         * @see EntryAttribute#remove(String...)
689         * 
690         * @return <code>true</code> if all the values shave been removed from this attribute
691         */
692        public boolean remove( String... vals )
693        {
694            if ( !isHR )
695            {
696                return false;
697            }
698            
699            boolean removed = true;
700            
701            for ( String val:vals )
702            {
703                StringValue value = new StringValue( attributeType, val );
704                removed &= values.remove( value );
705            }
706            
707            return removed;
708        }
709    
710    
711        /**
712         * @see EntryAttribute#remove(org.apache.directory.shared.ldap.entry.Value...)
713         * 
714         * @return <code>true</code> if all the values shave been removed from this attribute
715         */
716        public boolean remove( Value<?>... vals )
717        {
718            boolean removed = true;
719            
720            // Loop through all the values to remove. If one of
721            // them is not present, the method will return false.
722            // As the attribute may be HR or not, we have two separated treatments
723            if ( isHR )
724            {
725                for ( Value<?> val:vals )
726                {
727                    if ( val instanceof StringValue )
728                    {
729                        StringValue stringValue = (StringValue)val;
730                        
731                        if ( stringValue.getAttributeType() == null )
732                        {
733                            stringValue.apply( attributeType );
734                        }
735                        
736                        removed &= values.remove( stringValue );
737                    }
738                    else
739                    {
740                        removed = false;
741                    }
742                }
743            }
744            else
745            {
746                for ( Value<?> val:vals )
747                {
748                    if ( val instanceof BinaryValue )
749                    {
750                        BinaryValue binaryValue = (BinaryValue)val;
751                        
752                        if ( binaryValue.getAttributeType() == null )
753                        {
754                            binaryValue = new BinaryValue( attributeType, (byte[])val.get() );
755                        }
756                        
757                        removed &= values.remove( binaryValue );
758                    }
759                    else
760                    {
761                        removed = false;
762                    }
763                }
764            }
765            
766            return removed;
767        }
768    
769    
770        
771        /**
772         * <p>
773         * Overload the ClientAttribte isHR method : we can't change this flag
774         * for a ServerAttribute, as the HR is already set using the AttributeType.
775         * Set the attribute to Human Readable or to Binary. 
776         * </p>
777         * 
778         * @param isHR <code>true</code> for a Human Readable attribute, 
779         * <code>false</code> for a Binary attribute.
780         */
781        public void setHR( boolean isHR )
782        {
783            // Do nothing...
784        }
785    
786        
787        /**
788         * <p>
789         * Overload the {@link DefaultClientAttribute#setId(String)} method.
790         * </p>
791         * <p>
792         * As the attributeType has already been set, we have to be sure that the 
793         * argument is compatible with the attributeType's name. 
794         * </p>
795         * <p>
796         * If the given ID is not compatible with the attributeType's possible
797         * names, the previously loaded ID will be kept.
798         * </p>
799         *
800         * @param id The attribute ID
801         */
802        public void setId( String id )
803        {
804            if ( !StringTools.isEmpty( StringTools.trim( id  ) ) )
805            {
806                if ( attributeType.getName() == null )
807                {
808                    // If the name is null, then we may have to store an OID
809                    if ( OID.isOID( id )  && attributeType.getOid().equals( id ) )
810                    {
811                        // Everything is fine, store the upId.
812                        // This should not happen...
813                        super.setId( id );
814                    }
815                }
816                else
817                {
818                    // We have at least one name. Check that the normalized upId
819                    // is one of those names. Otherwise, the upId may be an OID too.
820                    // In this case, it must be equals to the attributeType OID.
821                    String normId = StringTools.lowerCaseAscii( StringTools.trim( id ) );
822                    
823                    for ( String atName:attributeType.getNames() )
824                    {
825                        if ( atName.equalsIgnoreCase( normId ) )
826                        {
827                            // Found ! We can store the upId and get out
828                            super.setId( normId );
829                            return;
830                        }
831                    }
832                    
833                    // Last case, the UpId is an OID
834                    if ( OID.isOID( normId ) && attributeType.getOid().equals( normId ) )
835                    {
836                        // We have an OID : stores it
837                        super.setUpId( normId );
838                    }
839                    else
840                    {
841                        // The id is incorrect : this is not allowed 
842                        throw new IllegalArgumentException( I18n.err( I18n.ERR_04455, id, attributeType.getName() ) );
843                    }
844                }
845            }
846            else
847            {
848                throw new IllegalArgumentException( I18n.err( I18n.ERR_04456 ) );
849            }
850        }
851        
852        
853        /**
854         * <p>
855         * Overload the {@link DefaultClientAttribute#setUpId(String)} method.
856         * </p>
857         * <p>
858         * As the attributeType has already been set, we have to be sure that the 
859         * argument is compatible with the attributeType's name. 
860         * </p>
861         * <p>
862         * If the given ID is not compatible with the attributeType's possible
863         * names, the previously loaded ID will be kept.
864         * </p>
865         *
866         * @param upId The attribute ID
867         *
868        public void setUpId( String upId )
869        {
870            if ( !StringTools.isEmpty( StringTools.trim( upId  ) ) )
871            {
872                if ( attributeType.getName() == null )
873                {
874                    // If the name is null, then we may have to store an OID
875                    if ( OID.isOID( upId )  && attributeType.getOid().equals( upId ) )
876                    {
877                        // Everything is fine, store the upId.
878                        // This should not happen...
879                        super.setUpId( upId );
880                        return;
881                    }
882                }
883                else
884                {
885                    // We have at least one name. Check that the normalized upId
886                    // is one of those names. Otherwise, the upId may be an OID too.
887                    // In this case, it must be equals to the attributeType OID.
888                    String normUpId = StringTools.lowerCaseAscii( StringTools.trim( upId ) );
889                    
890                    for ( String atId:attributeType.getNames() )
891                    {
892                        if ( atId.equalsIgnoreCase( normUpId ) )
893                        {
894                            // Found ! We can store the upId and get out
895                            super.setUpId( upId );
896                            return;
897                        }
898                    }
899                    
900                    // Last case, the UpId is an OID
901                    if ( OID.isOID( normUpId ) && attributeType.getOid().equals( normUpId ) )
902                    {
903                        // We have an OID : stores it
904                        super.setUpId( upId );
905                        return;
906                    }
907                    
908                    return;
909                }
910            }
911            
912            return;
913        }
914        
915        
916        //-------------------------------------------------------------------------
917        // Serialization methods
918        //-------------------------------------------------------------------------
919        
920        /**
921         * @see java.io.Externalizable#writeExternal(ObjectOutput)
922         * 
923         * We can't use this method for a ServerAttribute, as we have to feed the value
924         * with an AttributeType object
925         */
926        public void writeExternal( ObjectOutput out ) throws IOException
927        {
928            throw new IllegalStateException( I18n.err( I18n.ERR_04454 ) );
929        }
930        
931        
932        /**
933         * @see Externalizable#writeExternal(ObjectOutput)
934         * <p>
935         * 
936         * This is the place where we serialize attributes, and all theirs
937         * elements. 
938         * 
939         * The inner structure is the same as the client attribute, but we can't call
940         * it as we won't be able to serialize the serverValues
941         * 
942         */
943        public void serialize( ObjectOutput out ) throws IOException
944        {
945            // Write the UPId (the id will be deduced from the upID)
946            out.writeUTF( upId );
947            
948            // Write the HR flag, if not null
949            if ( isHR != null )
950            {
951                out.writeBoolean( true );
952                out.writeBoolean( isHR );
953            }
954            else
955            {
956                out.writeBoolean( false );
957            }
958            
959            // Write the number of values
960            out.writeInt( size() );
961            
962            if ( size() > 0 ) 
963            {
964                // Write each value
965                for ( Value<?> value:values )
966                {
967                    // Write the value, using the correct method
968                    if ( value instanceof StringValue )
969                    {
970                        ((StringValue)value).serialize( out );
971                    }
972                    else
973                    {
974                        ((BinaryValue)value).serialize( out );
975                    }
976                }
977            }
978        }
979    
980        
981        /**
982         * @see java.io.Externalizable#readExternal(ObjectInput)
983         * 
984         * We can't use this method for a ServerAttribute, as we have to feed the value
985         * with an AttributeType object
986         */
987        public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException
988        {
989            throw new IllegalStateException( I18n.err( I18n.ERR_04454 ) );
990        }
991        
992        
993        /**
994         * @see Externalizable#readExternal(ObjectInput)
995         */
996        public void deserialize( ObjectInput in ) throws IOException, ClassNotFoundException
997        {
998            // Read the ID and the UPId
999            upId = in.readUTF();
1000            
1001            // Compute the id
1002            setUpId( upId );
1003            
1004            // Read the HR flag, if not null
1005            if ( in.readBoolean() )
1006            {
1007                isHR = in.readBoolean();
1008            }
1009    
1010            // Read the number of values
1011            int nbValues = in.readInt();
1012    
1013            if ( nbValues > 0 )
1014            {
1015                for ( int i = 0; i < nbValues; i++ )
1016                {
1017                    Value<?> value = null;
1018                    
1019                    if ( isHR )
1020                    {
1021                        value  = new StringValue( attributeType );
1022                        ((StringValue)value).deserialize( in );
1023                    }
1024                    else
1025                    {
1026                        value  = new BinaryValue( attributeType );
1027                        ((BinaryValue)value).deserialize( in );
1028                    }
1029                    
1030                    try
1031                    {
1032                        value.normalize();
1033                    }
1034                    catch ( LdapException ne )
1035                    {
1036                        // Do nothing...
1037                    }
1038                        
1039                    values.add( value );
1040                }
1041            }
1042        }
1043        
1044        
1045        //-------------------------------------------------------------------------
1046        // Overloaded Object class methods
1047        //-------------------------------------------------------------------------
1048        /**
1049         * Clone an attribute. All the element are duplicated, so a modification on
1050         * the original object won't affect the cloned object, as a modification
1051         * on the cloned object has no impact on the original object
1052         * 
1053         * @return a clone of the current attribute
1054         */
1055        public EntryAttribute clone()
1056        {
1057            // clone the structure by cloner the inherited class
1058            EntryAttribute clone = (EntryAttribute)super.clone();
1059            
1060            // We are done !
1061            return clone;
1062        }
1063    
1064    
1065        /**
1066         * @see Object#equals(Object)
1067         * 
1068         * @return <code>true</code> if the two objects are equal
1069         */
1070        public boolean equals( Object obj )
1071        {
1072            if ( obj == this )
1073            {
1074                return true;
1075            }
1076            
1077            if ( ! (obj instanceof EntryAttribute ) )
1078            {
1079                return false;
1080            }
1081            
1082            EntryAttribute other = (EntryAttribute)obj;
1083            
1084            if ( !attributeType.equals( other.getAttributeType() ) )
1085            {
1086                return false;
1087            }
1088            
1089            if ( values.size() != other.size() )
1090            {
1091                return false;
1092            }
1093            
1094            for ( Value<?> val:values )
1095            {
1096                if ( ! other.contains( val ) )
1097                {
1098                    return false;
1099                }
1100            }
1101            
1102            return true;
1103        }
1104        
1105        
1106        /**
1107         * The hashCode is based on the id, the isHR flag and 
1108         * on the internal values.
1109         *  
1110         * @see Object#hashCode()
1111         * 
1112         * @return the instance's hash code 
1113         */
1114        public int hashCode()
1115        {
1116            int h = super.hashCode();
1117            
1118            if ( attributeType != null )
1119            {
1120                h = h*17 + attributeType.hashCode();
1121            }
1122            
1123            return h;
1124        }
1125        
1126        
1127        /**
1128         * @see Object#toString()
1129         * 
1130         * @return A String representation of this instance
1131         */
1132        public String toString()
1133        {
1134            StringBuilder sb = new StringBuilder();
1135            
1136            if ( ( values != null ) && ( values.size() != 0 ) )
1137            {
1138                for ( Value<?> value:values )
1139                {
1140                    sb.append( "    " ).append( upId ).append( ": " );
1141                    
1142                    if ( value.isNull() )
1143                    {
1144                        sb.append( "''" );
1145                    }
1146                    else
1147                    {
1148                        sb.append( value );
1149                    }
1150                    
1151                    sb.append( '\n' );
1152                }
1153            }
1154            else
1155            {
1156                sb.append( "    " ).append( upId ).append( ": (null)\n" );
1157            }
1158            
1159            return sb.toString();
1160        }
1161    }