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