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