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    @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "NP_LOAD_OF_KNOWN_NULL_VALUE",
772        justification = "Validity of null depends on the checker")
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.error( 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.error( 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                    if ( value == null )
1155                    {
1156                        // The value can't be normalized or is invalid : we don't add it.
1157                        LOG.error( I18n.err( I18n.ERR_04449, val ) );
1158                        continue;
1159                    }
1160
1161                    // Now call the add(Value) method
1162                    if ( add( value ) == 1 )
1163                    {
1164                        nbAdded++;
1165                    }
1166                }
1167            }
1168        }
1169
1170        return nbAdded;
1171    }
1172
1173
1174    /**
1175     * {@inheritDoc}
1176     */
1177    public int add( byte[]... vals ) throws LdapInvalidAttributeValueException
1178    {
1179        int nbAdded = 0;
1180        byte[][] valArray = vals;
1181
1182        if ( vals == null )
1183        {
1184            valArray = new byte[0][];
1185        }
1186
1187        // First, if the isHR flag is not set, we assume that the
1188        // attribute is not HR, because we are asked to add some byte[].
1189        if ( isHR == null )
1190        {
1191            isHR = false;
1192        }
1193
1194        if ( !isHR )
1195        {
1196            for ( byte[] val : valArray )
1197            {
1198                Value<byte[]> value = null;
1199
1200                if ( attributeType == null )
1201                {
1202                    value = new BinaryValue( val );
1203                }
1204                else
1205                {
1206                    value = createBinaryValue( attributeType, val );
1207                }
1208
1209                if ( add( value ) != 0 )
1210                {
1211                    nbAdded++;
1212                }
1213                else
1214                {
1215                    LOG.error( I18n.err( I18n.ERR_04486_VALUE_ALREADY_EXISTS, Strings.dumpBytes( val ), upId ) );
1216                }
1217            }
1218        }
1219        else
1220        {
1221            // We can't add Binary values into a String Attribute
1222            LOG.info( I18n.err( I18n.ERR_04451 ) );
1223            return 0;
1224        }
1225
1226        return nbAdded;
1227    }
1228
1229
1230    /**
1231     * {@inheritDoc}
1232     */
1233    public void clear()
1234    {
1235        values.clear();
1236    }
1237
1238
1239    /**
1240     * {@inheritDoc}
1241     */
1242    public boolean contains( Value<?>... vals )
1243    {
1244        if ( isHR == null )
1245        {
1246            // If this flag is null, then there is no values.
1247            return false;
1248        }
1249
1250        if ( attributeType == null )
1251        {
1252            if ( isHR )
1253            {
1254                // Iterate through all the values, convert the Binary values
1255                // to String values, and quit id any of the values is not
1256                // contained in the object
1257                for ( Value<?> val : vals )
1258                {
1259                    if ( val instanceof StringValue )
1260                    {
1261                        if ( !values.contains( val ) )
1262                        {
1263                            return false;
1264                        }
1265                    }
1266                    else
1267                    {
1268                        byte[] binaryVal = val.getBytes();
1269
1270                        // We have to convert the binary value to a String
1271                        if ( !values.contains( new StringValue( Strings.utf8ToString( binaryVal ) ) ) )
1272                        {
1273                            return false;
1274                        }
1275                    }
1276                }
1277            }
1278            else
1279            {
1280                // Iterate through all the values, convert the String values
1281                // to binary values, and quit id any of the values is not
1282                // contained in the object
1283                for ( Value<?> val : vals )
1284                {
1285                    if ( val.isHumanReadable() )
1286                    {
1287                        String stringVal = val.getString();
1288
1289                        // We have to convert the binary value to a String
1290                        if ( !values.contains( new BinaryValue( Strings.getBytesUtf8( stringVal ) ) ) )
1291                        {
1292                            return false;
1293                        }
1294                    }
1295                    else
1296                    {
1297                        if ( !values.contains( val ) )
1298                        {
1299                            return false;
1300                        }
1301                    }
1302                }
1303            }
1304        }
1305        else
1306        {
1307            // Iterate through all the values, and quit if we
1308            // don't find one in the values. We have to separate the check
1309            // depending on the isHR flag value.
1310            if ( isHR )
1311            {
1312                for ( Value<?> val : vals )
1313                {
1314                    if ( val instanceof StringValue )
1315                    {
1316                        StringValue stringValue = ( StringValue ) val;
1317
1318                        try
1319                        {
1320                            if ( stringValue.getAttributeType() == null )
1321                            {
1322                                stringValue.apply( attributeType );
1323                            }
1324                        }
1325                        catch ( LdapInvalidAttributeValueException liave )
1326                        {
1327                            return false;
1328                        }
1329
1330                        if ( !values.contains( val ) )
1331                        {
1332                            return false;
1333                        }
1334                    }
1335                    else
1336                    {
1337                        // Not a String value
1338                        return false;
1339                    }
1340                }
1341            }
1342            else
1343            {
1344                for ( Value<?> val : vals )
1345                {
1346                    if ( val instanceof BinaryValue )
1347                    {
1348                        if ( !values.contains( val ) )
1349                        {
1350                            return false;
1351                        }
1352                    }
1353                    else
1354                    {
1355                        // Not a Binary value
1356                        return false;
1357                    }
1358                }
1359            }
1360        }
1361
1362        return true;
1363    }
1364
1365
1366    /**
1367     * {@inheritDoc}
1368     */
1369    public boolean contains( String... vals )
1370    {
1371        if ( isHR == null )
1372        {
1373            // If this flag is null, then there is no values.
1374            return false;
1375        }
1376
1377        if ( attributeType == null )
1378        {
1379            if ( isHR )
1380            {
1381                for ( String val : vals )
1382                {
1383                    try
1384                    {
1385                        if ( !contains( new StringValue( val ) ) )
1386                        {
1387                            return false;
1388                        }
1389                    }
1390                    catch ( IllegalArgumentException iae )
1391                    {
1392                        return false;
1393                    }
1394                }
1395            }
1396            else
1397            {
1398                // As the attribute type is binary, we have to convert
1399                // the values before checking for them in the values
1400                // Iterate through all the values, and quit if we
1401                // don't find one in the values
1402                for ( String val : vals )
1403                {
1404                    byte[] binaryVal = Strings.getBytesUtf8( val );
1405
1406                    if ( !contains( new BinaryValue( binaryVal ) ) )
1407                    {
1408                        return false;
1409                    }
1410                }
1411            }
1412        }
1413        else
1414        {
1415            if ( isHR )
1416            {
1417                // Iterate through all the values, and quit if we
1418                // don't find one in the values
1419                for ( String val : vals )
1420                {
1421                    try
1422                    {
1423                        StringValue value = new StringValue( attributeType, val );
1424
1425                        if ( !values.contains( value ) )
1426                        {
1427                            return false;
1428                        }
1429                    }
1430                    catch ( LdapInvalidAttributeValueException liave )
1431                    {
1432                        return false;
1433                    }
1434                }
1435
1436                return true;
1437            }
1438            else
1439            {
1440                return false;
1441            }
1442        }
1443
1444        return true;
1445    }
1446
1447
1448    /**
1449     * {@inheritDoc}
1450     */
1451    public boolean contains( byte[]... vals )
1452    {
1453        if ( isHR == null )
1454        {
1455            // If this flag is null, then there is no values.
1456            return false;
1457        }
1458
1459        if ( attributeType == null )
1460        {
1461            if ( !isHR )
1462            {
1463                // Iterate through all the values, and quit if we
1464                // don't find one in the values
1465                for ( byte[] val : vals )
1466                {
1467                    if ( !contains( new BinaryValue( val ) ) )
1468                    {
1469                        return false;
1470                    }
1471                }
1472            }
1473            else
1474            {
1475                // As the attribute type is String, we have to convert
1476                // the values before checking for them in the values
1477                // Iterate through all the values, and quit if we
1478                // don't find one in the values
1479                for ( byte[] val : vals )
1480                {
1481                    String stringVal = Strings.utf8ToString( val );
1482
1483                    if ( !contains( new StringValue( stringVal ) ) )
1484                    {
1485                        return false;
1486                    }
1487                }
1488            }
1489        }
1490        else
1491        {
1492            if ( !isHR )
1493            {
1494                // Iterate through all the values, and quit if we
1495                // don't find one in the values
1496                for ( byte[] val : vals )
1497                {
1498                    try
1499                    {
1500                        BinaryValue value = new BinaryValue( attributeType, val );
1501
1502                        if ( !values.contains( value ) )
1503                        {
1504                            return false;
1505                        }
1506                    }
1507                    catch ( LdapInvalidAttributeValueException liave )
1508                    {
1509                        return false;
1510                    }
1511                }
1512
1513                return true;
1514            }
1515            else
1516            {
1517                return false;
1518            }
1519        }
1520
1521        return true;
1522    }
1523
1524
1525    /**
1526     * {@inheritDoc}
1527     */
1528    public Value<?> get()
1529    {
1530        if ( values.isEmpty() )
1531        {
1532            return null;
1533        }
1534
1535        return values.iterator().next();
1536    }
1537
1538
1539    /**
1540     * {@inheritDoc}
1541     */
1542    public int size()
1543    {
1544        return values.size();
1545    }
1546
1547
1548    /**
1549     * {@inheritDoc}
1550     */
1551    public boolean remove( Value<?>... vals )
1552    {
1553        if ( ( isHR == null ) || ( values.size() == 0 ) )
1554        {
1555            // Trying to remove a value from an empty list will fail
1556            return false;
1557        }
1558
1559        boolean removed = true;
1560
1561        if ( attributeType == null )
1562        {
1563            if ( isHR )
1564            {
1565                for ( Value<?> val : vals )
1566                {
1567                    if ( val instanceof StringValue )
1568                    {
1569                        removed &= values.remove( val );
1570                    }
1571                    else
1572                    {
1573                        // Convert the binary value to a string value
1574                        byte[] binaryVal = val.getBytes();
1575                        removed &= values.remove( new StringValue( Strings.utf8ToString( binaryVal ) ) );
1576                    }
1577                }
1578            }
1579            else
1580            {
1581                for ( Value<?> val : vals )
1582                {
1583                    removed &= values.remove( val );
1584                }
1585            }
1586        }
1587        else
1588        {
1589            // Loop through all the values to remove. If one of
1590            // them is not present, the method will return false.
1591            // As the attribute may be HR or not, we have two separated treatments
1592            if ( isHR )
1593            {
1594                for ( Value<?> val : vals )
1595                {
1596                    if ( val instanceof StringValue )
1597                    {
1598                        StringValue stringValue = ( StringValue ) val;
1599
1600                        try
1601                        {
1602                            if ( stringValue.getAttributeType() == null )
1603                            {
1604                                stringValue.apply( attributeType );
1605                            }
1606
1607                            removed &= values.remove( stringValue );
1608                        }
1609                        catch ( LdapInvalidAttributeValueException liave )
1610                        {
1611                            removed = false;
1612                        }
1613                    }
1614                    else
1615                    {
1616                        removed = false;
1617                    }
1618                }
1619            }
1620            else
1621            {
1622                for ( Value<?> val : vals )
1623                {
1624                    if ( val instanceof BinaryValue )
1625                    {
1626                        try
1627                        {
1628                            BinaryValue binaryValue = ( BinaryValue ) val;
1629
1630                            if ( binaryValue.getAttributeType() == null )
1631                            {
1632                                binaryValue.apply( attributeType );
1633                            }
1634
1635                            removed &= values.remove( binaryValue );
1636                        }
1637                        catch ( LdapInvalidAttributeValueException liave )
1638                        {
1639                            removed = false;
1640                        }
1641                    }
1642                    else
1643                    {
1644                        removed = false;
1645                    }
1646                }
1647            }
1648        }
1649
1650        return removed;
1651    }
1652
1653
1654    /**
1655     * {@inheritDoc}
1656     */
1657    public boolean remove( byte[]... vals )
1658    {
1659        if ( ( isHR == null ) || ( values.size() == 0 ) )
1660        {
1661            // Trying to remove a value from an empty list will fail
1662            return false;
1663        }
1664
1665        boolean removed = true;
1666
1667        if ( attributeType == null )
1668        {
1669            if ( !isHR )
1670            {
1671                // The attribute type is not HR, we can directly process the values
1672                for ( byte[] val : vals )
1673                {
1674                    BinaryValue value = new BinaryValue( val );
1675                    removed &= values.remove( value );
1676                }
1677            }
1678            else
1679            {
1680                // The attribute type is String, we have to convert the values
1681                // to String before removing them
1682                for ( byte[] val : vals )
1683                {
1684                    StringValue value = new StringValue( Strings.utf8ToString( val ) );
1685                    removed &= values.remove( value );
1686                }
1687            }
1688        }
1689        else
1690        {
1691            if ( !isHR )
1692            {
1693                try
1694                {
1695                    for ( byte[] val : vals )
1696                    {
1697                        BinaryValue value = new BinaryValue( attributeType, val );
1698                        removed &= values.remove( value );
1699                    }
1700                }
1701                catch ( LdapInvalidAttributeValueException liave )
1702                {
1703                    removed = false;
1704                }
1705            }
1706            else
1707            {
1708                removed = false;
1709            }
1710        }
1711
1712        return removed;
1713    }
1714
1715
1716    /**
1717     * {@inheritDoc}
1718     */
1719    public boolean remove( String... vals )
1720    {
1721        if ( ( isHR == null ) || ( values.size() == 0 ) )
1722        {
1723            // Trying to remove a value from an empty list will fail
1724            return false;
1725        }
1726
1727        boolean removed = true;
1728
1729        if ( attributeType == null )
1730        {
1731            if ( isHR )
1732            {
1733                // The attribute type is HR, we can directly process the values
1734                for ( String val : vals )
1735                {
1736                    StringValue value = new StringValue( val );
1737                    removed &= values.remove( value );
1738                }
1739            }
1740            else
1741            {
1742                // The attribute type is binary, we have to convert the values
1743                // to byte[] before removing them
1744                for ( String val : vals )
1745                {
1746                    BinaryValue value = new BinaryValue( Strings.getBytesUtf8( val ) );
1747                    removed &= values.remove( value );
1748                }
1749            }
1750        }
1751        else
1752        {
1753            if ( isHR )
1754            {
1755                for ( String val : vals )
1756                {
1757                    try
1758                    {
1759                        StringValue value = new StringValue( attributeType, val );
1760                        removed &= values.remove( value );
1761                    }
1762                    catch ( LdapInvalidAttributeValueException liave )
1763                    {
1764                        removed = false;
1765                    }
1766                }
1767            }
1768            else
1769            {
1770                removed = false;
1771            }
1772        }
1773
1774        return removed;
1775    }
1776
1777
1778    /**
1779     * An iterator on top of the stored values.
1780     * 
1781     * @return an iterator over the stored values.
1782     */
1783    public Iterator<Value<?>> iterator()
1784    {
1785        return values.iterator();
1786    }
1787
1788
1789    /**
1790     * {@inheritDoc}
1791     */
1792    public AttributeType getAttributeType()
1793    {
1794        return attributeType;
1795    }
1796
1797
1798    /**
1799     * {@inheritDoc}
1800     */
1801    public void apply( AttributeType attributeType ) throws LdapInvalidAttributeValueException
1802    {
1803        if ( attributeType == null )
1804        {
1805            throw new IllegalArgumentException( "The AttributeType parameter should not be null" );
1806        }
1807
1808        this.attributeType = attributeType;
1809        this.id = attributeType.getOid();
1810
1811        if ( Strings.isEmpty( this.upId ) )
1812        {
1813            this.upId = attributeType.getName();
1814        }
1815        else
1816        {
1817            if ( !areCompatible( this.upId, attributeType ) )
1818            {
1819                this.upId = attributeType.getName();
1820            }
1821        }
1822
1823        if ( values != null )
1824        {
1825            Set<Value<?>> newValues = new LinkedHashSet<Value<?>>( values.size() );
1826
1827            for ( Value<?> value : values )
1828            {
1829                if ( value instanceof StringValue )
1830                {
1831                    newValues.add( new StringValue( attributeType, value.getString() ) );
1832                }
1833                else
1834                {
1835                    newValues.add( new BinaryValue( attributeType, value.getBytes() ) );
1836                }
1837            }
1838
1839            values = newValues;
1840        }
1841
1842        isHR = attributeType.getSyntax().isHumanReadable();
1843
1844        // Compute the hashCode
1845        rehash();
1846    }
1847
1848
1849    /**
1850     * {@inheritDoc}
1851     */
1852    public boolean isInstanceOf( AttributeType attributeType ) throws LdapInvalidAttributeValueException
1853    {
1854        return ( attributeType != null ) &&
1855            ( this.attributeType.equals( attributeType ) ||
1856            this.attributeType.isDescendantOf( attributeType ) );
1857    }
1858
1859
1860    //-------------------------------------------------------------------------
1861    // Overloaded Object classes
1862    //-------------------------------------------------------------------------
1863    /**
1864     * A helper method to rehash the hashCode
1865     */
1866    private void rehash()
1867    {
1868        h = 37;
1869
1870        if ( isHR != null )
1871        {
1872            h = h * 17 + isHR.hashCode();
1873        }
1874
1875        if ( id != null )
1876        {
1877            h = h * 17 + id.hashCode();
1878        }
1879
1880        if ( attributeType != null )
1881        {
1882            h = h * 17 + attributeType.hashCode();
1883        }
1884    }
1885
1886
1887    /**
1888     * The hashCode is based on the id, the isHR flag and
1889     * on the internal values.
1890     * 
1891     * @see Object#hashCode()
1892     * @return the instance's hashcode
1893     */
1894    public int hashCode()
1895    {
1896        if ( h == 0 )
1897        {
1898            rehash();
1899        }
1900
1901        return h;
1902    }
1903
1904
1905    /**
1906     * @see Object#equals(Object)
1907     */
1908    public boolean equals( Object obj )
1909    {
1910        if ( obj == this )
1911        {
1912            return true;
1913        }
1914
1915        if ( !( obj instanceof Attribute ) )
1916        {
1917            return false;
1918        }
1919
1920        Attribute other = ( Attribute ) obj;
1921
1922        if ( id == null )
1923        {
1924            if ( other.getId() != null )
1925            {
1926                return false;
1927            }
1928        }
1929        else
1930        {
1931            if ( other.getId() == null )
1932            {
1933                return false;
1934            }
1935            else
1936            {
1937                if ( attributeType != null )
1938                {
1939                    if ( !attributeType.equals( other.getAttributeType() ) )
1940                    {
1941                        return false;
1942                    }
1943                }
1944                else if ( !id.equals( other.getId() ) )
1945                {
1946                    return false;
1947                }
1948            }
1949        }
1950
1951        if ( isHumanReadable() != other.isHumanReadable() )
1952        {
1953            return false;
1954        }
1955
1956        if ( values.size() != other.size() )
1957        {
1958            return false;
1959        }
1960
1961        for ( Value<?> val : values )
1962        {
1963            if ( !other.contains( val ) )
1964            {
1965                return false;
1966            }
1967        }
1968
1969        if ( attributeType == null )
1970        {
1971            return other.getAttributeType() == null;
1972        }
1973
1974        return attributeType.equals( other.getAttributeType() );
1975    }
1976
1977
1978    /**
1979     * {@inheritDoc}
1980     */
1981    public Attribute clone()
1982    {
1983        try
1984        {
1985            DefaultAttribute attribute = ( DefaultAttribute ) super.clone();
1986
1987            if ( this.attributeType != null )
1988            {
1989                attribute.id = attributeType.getOid();
1990                attribute.attributeType = attributeType;
1991            }
1992
1993            attribute.values = new LinkedHashSet<Value<?>>( values.size() );
1994
1995            for ( Value<?> value : values )
1996            {
1997                // No need to clone the value, it will never be changed
1998                attribute.values.add( value );
1999            }
2000
2001            return attribute;
2002        }
2003        catch ( CloneNotSupportedException cnse )
2004        {
2005            return null;
2006        }
2007    }
2008
2009
2010    /**
2011     * This is the place where we serialize attributes, and all theirs
2012     * elements.
2013     * 
2014     * {@inheritDoc}
2015     */
2016    public void writeExternal( ObjectOutput out ) throws IOException
2017    {
2018        // Write the UPId (the id will be deduced from the upID)
2019        out.writeUTF( upId );
2020
2021        // Write the HR flag, if not null
2022        if ( isHR != null )
2023        {
2024            out.writeBoolean( true );
2025            out.writeBoolean( isHR );
2026        }
2027        else
2028        {
2029            out.writeBoolean( false );
2030        }
2031
2032        // Write the number of values
2033        out.writeInt( size() );
2034
2035        if ( size() > 0 )
2036        {
2037            // Write each value
2038            for ( Value<?> value : values )
2039            {
2040                // Write the value
2041                value.writeExternal( out );
2042            }
2043        }
2044
2045        out.flush();
2046    }
2047
2048
2049    /**
2050     * {@inheritDoc}
2051     */
2052    public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException
2053    {
2054        // Read the ID and the UPId
2055        upId = in.readUTF();
2056
2057        // Compute the id
2058        setUpId( upId );
2059
2060        // Read the HR flag, if not null
2061        if ( in.readBoolean() )
2062        {
2063            isHR = in.readBoolean();
2064        }
2065
2066        // Read the number of values
2067        int nbValues = in.readInt();
2068
2069        if ( nbValues > 0 )
2070        {
2071            for ( int i = 0; i < nbValues; i++ )
2072            {
2073                Value<?> value = null;
2074
2075                if ( isHR )
2076                {
2077                    value = new StringValue( attributeType );
2078                }
2079                else
2080                {
2081                    value = new BinaryValue( attributeType );
2082                }
2083
2084                value.readExternal( in );
2085
2086                values.add( value );
2087            }
2088        }
2089    }
2090
2091
2092    /**
2093     * @see Object#toString()
2094     */
2095    public String toString()
2096    {
2097        return toString( "" );
2098    }
2099
2100
2101    /**
2102     * {@inheritDoc}
2103     */
2104    public String toString( String tabs )
2105    {
2106        StringBuilder sb = new StringBuilder();
2107
2108        if ( ( values != null ) && ( values.size() != 0 ) )
2109        {
2110            boolean isFirst = true;
2111
2112            for ( Value<?> value : values )
2113            {
2114                if ( isFirst )
2115                {
2116                    isFirst = false;
2117                }
2118                else
2119                {
2120                    sb.append( '\n' );
2121                }
2122
2123                sb.append( tabs ).append( upId ).append( ": " );
2124
2125                if ( value.isNull() )
2126                {
2127                    sb.append( "''" );
2128                }
2129                else
2130                {
2131                    sb.append( value );
2132                }
2133            }
2134        }
2135        else
2136        {
2137            sb.append( tabs ).append( upId ).append( ": (null)" );
2138        }
2139
2140        return sb.toString();
2141    }
2142}