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