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