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 * 
019 */
020package org.apache.directory.api.ldap.model.name;
021
022
023import java.io.Externalizable;
024import java.io.IOException;
025import java.io.ObjectInput;
026import java.io.ObjectOutput;
027import java.util.Arrays;
028
029import org.apache.directory.api.i18n.I18n;
030import org.apache.directory.api.ldap.model.entry.BinaryValue;
031import org.apache.directory.api.ldap.model.entry.StringValue;
032import org.apache.directory.api.ldap.model.entry.Value;
033import org.apache.directory.api.ldap.model.exception.LdapException;
034import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException;
035import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException;
036import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
037import org.apache.directory.api.ldap.model.schema.AttributeType;
038import org.apache.directory.api.ldap.model.schema.LdapComparator;
039import org.apache.directory.api.ldap.model.schema.MatchingRule;
040import org.apache.directory.api.ldap.model.schema.SchemaManager;
041import org.apache.directory.api.util.Serialize;
042import org.apache.directory.api.util.Strings;
043import org.slf4j.Logger;
044import org.slf4j.LoggerFactory;
045
046
047/**
048 * A Attribute Type And Value, which is the basis of all Rdn. It contains a
049 * type, and a value. The type must not be case sensitive. Superfluous leading
050 * and trailing spaces MUST have been trimmed before. The value MUST be in UTF8
051 * format, according to RFC 2253. If the type is in OID form, then the value
052 * must be a hexadecimal string prefixed by a '#' character. Otherwise, the
053 * string must respect the RC 2253 grammar.
054 *
055 * We will also keep a User Provided form of the AVA (Attribute Type And Value),
056 * called upName.
057 *
058 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
059 */
060public class Ava implements Externalizable, Cloneable, Comparable<Ava>
061{
062    /**
063     * Declares the Serial Version Uid.
064     *
065     * @see <a
066     *      href="http://c2.com/cgi/wiki?AlwaysDeclareSerialVersionUid">Always
067     *      Declare Serial Version Uid</a>
068     */
069    private static final long serialVersionUID = 1L;
070
071    /** The LoggerFactory used by this class */
072    private static final Logger LOG = LoggerFactory.getLogger( Ava.class );
073
074    /** The normalized Name type */
075    private String normType;
076
077    /** The user provided Name type */
078    private String upType;
079
080    /** The name value. It can be a String or a byte array */
081    private Value<?> normValue;
082
083    /** The name user provided value. It can be a String or a byte array */
084    private Value<?> upValue;
085
086    /** The user provided Ava */
087    private String upName;
088
089    /** The attributeType if the Ava is schemaAware */
090    private AttributeType attributeType;
091
092    /** the schema manager */
093    private SchemaManager schemaManager;
094
095    /** The computed hashcode */
096    private volatile int h;
097
098
099    /**
100     * Constructs an empty Ava
101     */
102    public Ava()
103    {
104        this( null );
105    }
106
107
108    /**
109     * Constructs an empty schema aware Ava.
110     * 
111     * @param schemaManager The SchemaManager instance
112     */
113    public Ava( SchemaManager schemaManager )
114    {
115        normType = null;
116        upType = null;
117        normValue = null;
118        upValue = null;
119        upName = "";
120        this.schemaManager = schemaManager;
121        this.attributeType = null;
122    }
123
124
125    /**
126     * Construct an Ava containing a binary value.
127     * <p>
128     * Note that the upValue should <b>not</b> be null or empty, or resolve
129     * to an empty string after having trimmed it.
130     *
131     * @param upType The User Provided type
132     * @param upValue The User Provided binary value
133     * 
134     * @throws LdapInvalidDnException If the given type or value are invalid
135     */
136    public Ava( String upType, byte[] upValue ) throws LdapInvalidDnException
137    {
138        this( null, upType, upValue );
139    }
140
141
142    /**
143     * Construct a schema aware Ava containing a binary value. The AttributeType
144     * and value will be normalized accordingly to the given SchemaManager.
145     * <p>
146     * Note that the upValue should <b>not</b> be null or empty, or resolve
147     * to an empty string after having trimmed it.
148     *
149     * @param schemaManager The SchemaManager instance
150     * @param upType The User Provided type
151     * @param upValue The User Provided binary value
152     * 
153     * @throws LdapInvalidDnException If the given type or value are invalid
154     */
155    public Ava( SchemaManager schemaManager, String upType, byte[] upValue ) throws LdapInvalidDnException
156    {
157        if ( schemaManager != null )
158        {
159            this.schemaManager = schemaManager;
160
161            try
162            {
163                attributeType = schemaManager.lookupAttributeTypeRegistry( upType );
164            }
165            catch ( LdapException le )
166            {
167                String message = I18n.err( I18n.ERR_04188 );
168                LOG.error( message );
169                throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, message, le );
170            }
171
172            try
173            {
174                createAva( schemaManager, upType, new BinaryValue( attributeType, upValue ) );
175            }
176            catch ( LdapInvalidAttributeValueException liave )
177            {
178                String message = I18n.err( I18n.ERR_04188 );
179                LOG.error( message );
180                throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, message, liave );
181            }
182        }
183        else
184        {
185            createAva( upType, new BinaryValue( upValue ) );
186        }
187    }
188
189
190    /**
191     * Construct an Ava with a String value.
192     * <p>
193     * Note that the upValue should <b>not</b> be null or empty, or resolve
194     * to an empty string after having trimmed it.
195     *
196     * @param upType The User Provided type
197     * @param upValue The User Provided String value
198     * 
199     * @throws LdapInvalidDnException If the given type or value are invalid
200     */
201    public Ava( String upType, String upValue ) throws LdapInvalidDnException
202    {
203        this( null, upType, upValue );
204    }
205
206
207    /**
208     * Construct a schema aware Ava with a String value.
209     * <p>
210     * Note that the upValue should <b>not</b> be null or empty, or resolve
211     * to an empty string after having trimmed it.
212     *
213     * @param schemaManager The SchemaManager instance
214     * @param upType The User Provided type
215     * @param upValue The User Provided String value
216     * 
217     * @throws LdapInvalidDnException If the given type or value are invalid
218     */
219    public Ava( SchemaManager schemaManager, String upType, String upValue ) throws LdapInvalidDnException
220    {
221        if ( schemaManager != null )
222        {
223            this.schemaManager = schemaManager;
224
225            try
226            {
227                attributeType = schemaManager.lookupAttributeTypeRegistry( upType );
228            }
229            catch ( LdapException le )
230            {
231                String message = I18n.err( I18n.ERR_04188 );
232                LOG.error( message );
233                throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, message, le );
234            }
235
236            try
237            {
238                createAva( schemaManager, upType, new StringValue( attributeType, upValue ) );
239            }
240            catch ( LdapInvalidAttributeValueException liave )
241            {
242                String message = I18n.err( I18n.ERR_04188 );
243                LOG.error( message );
244                throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, message, liave );
245            }
246        }
247        else
248        {
249            createAva( upType, new StringValue( upValue ) );
250        }
251    }
252
253
254    /**
255     * Construct a schema aware Ava. The AttributeType and value will be checked accordingly
256     * to the SchemaManager.
257     * <p>
258     * Note that the upValue should <b>not</b> be null or empty, or resolve
259     * to an empty string after having trimmed it.
260     *
261     * @param schemaManager The SchemaManager instance
262     * @param upType The User Provided type
263     * @param upValue The User Provided value
264     * 
265     * @throws LdapInvalidDnException If the given type or value are invalid
266     */
267    private void createAva( SchemaManager schemaManager, String upType, Value<?> upValue )
268        throws LdapInvalidDnException
269    {
270        normType = attributeType.getOid();
271        this.upType = upType;
272
273        try
274        {
275            MatchingRule equalityMatchingRule = attributeType.getEquality();
276
277            if ( equalityMatchingRule != null )
278            {
279                this.normValue = equalityMatchingRule.getNormalizer().normalize( upValue );
280            }
281            else
282            {
283                this.normValue = upValue;
284            }
285        }
286        catch ( LdapException le )
287        {
288            String message = I18n.err( I18n.ERR_04188 );
289            LOG.error( message );
290            throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, message, le );
291        }
292
293        this.upValue = upValue;
294
295        upName = this.upType + '=' + ( this.upValue == null ? "" : Rdn.escapeValue( this.upValue.getString() ) );
296        hashCode();
297    }
298
299
300    /**
301     * Construct an Ava. The type and value are normalized :
302     * <li> the type is trimmed and lowercased </li>
303     * <li> the value is trimmed </li>
304     * <p>
305     * Note that the upValue should <b>not</b> be null or empty, or resolved
306     * to an empty string after having trimmed it.
307     *
308     * @param upType The User Provided type
309     * @param upValue The User Provided value
310     * 
311     * @throws LdapInvalidDnException If the given type or value are invalid
312     */
313    private void createAva( String upType, Value<?> upValue ) throws LdapInvalidDnException
314    {
315        String upTypeTrimmed = Strings.trim( upType );
316        String normTypeTrimmed = Strings.trim( normType );
317
318        if ( Strings.isEmpty( upTypeTrimmed ) )
319        {
320            if ( Strings.isEmpty( normTypeTrimmed ) )
321            {
322                String message = I18n.err( I18n.ERR_04188 );
323                LOG.error( message );
324                throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, message );
325            }
326            else
327            {
328                // In this case, we will use the normType instead
329                this.normType = Strings.lowerCaseAscii( normTypeTrimmed );
330                this.upType = normType;
331            }
332        }
333        else if ( Strings.isEmpty( normTypeTrimmed ) )
334        {
335            // In this case, we will use the upType instead
336            this.normType = Strings.lowerCaseAscii( upTypeTrimmed );
337            this.upType = upType;
338        }
339        else
340        {
341            this.normType = Strings.lowerCaseAscii( normTypeTrimmed );
342            this.upType = upType;
343
344        }
345
346        this.normValue = upValue;
347        this.upValue = upValue;
348
349        upName = this.upType + '=' + ( this.upValue == null ? "" : Rdn.escapeValue( this.upValue.getString() ) );
350        hashCode();
351    }
352
353
354    /**
355     * Construct an Ava. The type and value are normalized :
356     * <li> the type is trimmed and lowercased </li>
357     * <li> the value is trimmed </li>
358     * <p>
359     * Note that the upValue should <b>not</b> be null or empty, or resolved
360     * to an empty string after having trimmed it.
361     *
362     * @param schemaManager The SchemaManager
363     * @param upType The User Provided type
364     * @param normType The normalized type
365     * @param upValue The User Provided value
366     * @param normValue The normalized value
367     * 
368     * @throws LdapInvalidDnException If the given type or value are invalid
369     */
370    // WARNING : The protection level is left unspecified intentionally.
371    // We need this method to be visible from the DnParser class, but not
372    // from outside this package.
373    /* Unspecified protection */Ava( SchemaManager schemaManager, String upType, String normType, Value<?> upValue,
374        Value<?> normValue )
375        throws LdapInvalidDnException
376    {
377        this.upType = upType;
378        this.normType = normType;
379        this.upValue = upValue;
380        this.normValue = normValue;
381        upName = this.upType + '=' + ( this.upValue == null ? "" : this.upValue.getString() );
382
383        if ( schemaManager != null )
384        {
385            apply( schemaManager );
386        }
387
388        hashCode();
389    }
390
391
392    /**
393     * Construct an Ava. The type and value are normalized :
394     * <li> the type is trimmed and lowercased </li>
395     * <li> the value is trimmed </li>
396     * <p>
397     * Note that the upValue should <b>not</b> be null or empty, or resolved
398     * to an empty string after having trimmed it.
399     *
400     * @param upType The User Provided type
401     * @param normType The normalized type
402     * @param upValue The User Provided value
403     * @param normValue The normalized value
404     * @param upName The User Provided name (may be escaped)
405     * 
406     * @throws LdapInvalidDnException If the given type or value are invalid
407     */
408    // WARNING : The protection level is left unspecified intentionally.
409    // We need this method to be visible from the DnParser class, but not
410    // from outside this package.
411    /* Unspecified protection */Ava( String upType, String normType, Value<?> upValue, Value<?> normValue,
412        String upName )
413        throws LdapInvalidDnException
414    {
415        String upTypeTrimmed = Strings.trim( upType );
416        String normTypeTrimmed = Strings.trim( normType );
417
418        if ( Strings.isEmpty( upTypeTrimmed ) )
419        {
420            if ( Strings.isEmpty( normTypeTrimmed ) )
421            {
422                String message = I18n.err( I18n.ERR_04188 );
423                LOG.error( message );
424                throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, message );
425            }
426            else
427            {
428                // In this case, we will use the normType instead
429                this.normType = Strings.lowerCaseAscii( normTypeTrimmed );
430                this.upType = normType;
431            }
432        }
433        else if ( Strings.isEmpty( normTypeTrimmed ) )
434        {
435            // In this case, we will use the upType instead
436            this.normType = Strings.lowerCaseAscii( upTypeTrimmed );
437            this.upType = upType;
438        }
439        else
440        {
441            this.normType = Strings.lowerCaseAscii( normTypeTrimmed );
442            this.upType = upType;
443        }
444
445        this.normValue = normValue;
446        this.upValue = upValue;
447        this.upName = upName;
448        hashCode();
449    }
450
451
452    /**
453     * Apply a SchemaManager to the Ava. It will normalize the Ava.<br/>
454     * If the Ava already had a SchemaManager, then the new SchemaManager will be
455     * used instead.
456     * 
457     * @param schemaManager The SchemaManager instance to use
458     * @throws LdapInvalidDnException If the Ava can't be normalized accordingly
459     * to the given SchemaManager
460     */
461    public void apply( SchemaManager schemaManager ) throws LdapInvalidDnException
462    {
463        if ( schemaManager != null )
464        {
465            this.schemaManager = schemaManager;
466
467            AttributeType attributeType = null;
468
469            try
470            {
471                attributeType = schemaManager.lookupAttributeTypeRegistry( normType );
472            }
473            catch ( LdapException le )
474            {
475                String message = I18n.err( I18n.ERR_04188 );
476                LOG.error( message );
477                throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, message, le );
478            }
479
480            if ( this.attributeType == attributeType )
481            {
482                // No need to normalize again
483                return;
484            }
485            else
486            {
487                this.attributeType = attributeType;
488            }
489
490            normType = attributeType.getOid();
491
492            if ( normValue != null )
493            {
494                return;
495            }
496
497            try
498            {
499                // We use the Equality matching rule to normalize the value
500                MatchingRule equalityMatchingRule = attributeType.getEquality();
501
502                if ( equalityMatchingRule != null )
503                {
504                    this.normValue = equalityMatchingRule.getNormalizer().normalize( upValue );
505                }
506                else
507                {
508                    this.normValue = upValue;
509                }
510            }
511            catch ( LdapException le )
512            {
513                String message = I18n.err( I18n.ERR_04188 );
514                LOG.error( message );
515                throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, message, le );
516            }
517
518            hashCode();
519        }
520    }
521
522
523    /**
524     * Get the normalized type of a Ava
525     *
526     * @return The normalized type
527     */
528    public String getNormType()
529    {
530        return normType;
531    }
532
533
534    /**
535     * Get the user provided type of a Ava
536     *
537     * @return The user provided type
538     */
539    public String getType()
540    {
541        return upType;
542    }
543
544
545    /**
546     * Get the Value of a Ava
547     *
548     * @return The value
549     */
550    public Value<?> getNormValue()
551    {
552        return normValue.clone();
553    }
554
555
556    /**
557     * Get the User Provided Value of a Ava
558     *
559     * @return The value
560     */
561    public Value<?> getValue()
562    {
563        return upValue.clone();
564    }
565
566
567    /**
568     * Get the normalized Name of a Ava
569     *
570     * @return The name
571     */
572    public String getNormName()
573    {
574        return normalize();
575    }
576
577
578    /**
579     * Get the user provided form of this attribute type and value
580     *
581     * @return The user provided form of this ava
582     */
583    public String getName()
584    {
585        return upName;
586    }
587
588
589    /**
590     * Implements the cloning.
591     *
592     * @return a clone of this object
593     */
594    public Ava clone()
595    {
596        try
597        {
598            Ava clone = ( Ava ) super.clone();
599            clone.upValue = upValue.clone();
600            clone.normValue = normValue.clone();
601
602            return clone;
603        }
604        catch ( CloneNotSupportedException cnse )
605        {
606            throw new Error( "Assertion failure" );
607        }
608    }
609
610
611    /**
612     * A Normalized String representation of a Ava :
613     * <ul>
614     * <li>type is trimed and lowercased</li>
615     * <li>value is trimed and lowercased, and special characters</li>
616     * </ul>
617     * are escaped if needed.
618     *
619     * @return A normalized string representing an Ava
620     */
621    public String normalize()
622    {
623        if ( normValue.isHumanReadable() )
624        {
625            // The result will be gathered in a stringBuilder
626            StringBuilder sb = new StringBuilder();
627
628            // First, store the type and the '=' char
629            sb.append( normType ).append( '=' );
630
631            String normalizedValue = normValue.getString();
632
633            if ( normalizedValue.length() > 0 )
634            {
635                sb.append( Rdn.escapeValue( normalizedValue ) );
636            }
637
638            return sb.toString();
639        }
640        else
641        {
642            return normType + "=#"
643                + Strings.dumpHexPairs( normValue.getBytes() );
644        }
645    }
646
647
648    /**
649     * Gets the hashcode of this object.
650     *
651     * @see java.lang.Object#hashCode()
652     * @return The instance hash code
653     */
654    public int hashCode()
655    {
656        if ( h == 0 )
657        {
658            h = 37;
659
660            h = h * 17 + ( normType != null ? normType.hashCode() : 0 );
661            h = h * 17 + ( normValue != null ? normValue.hashCode() : 0 );
662        }
663
664        return h;
665    }
666
667
668    /**
669     * @see Object#equals(Object)
670     */
671    public boolean equals( Object obj )
672    {
673        if ( this == obj )
674        {
675            return true;
676        }
677
678        if ( !( obj instanceof Ava ) )
679        {
680            return false;
681        }
682
683        Ava instance = ( Ava ) obj;
684
685        // Compare the type
686        if ( normType == null )
687        {
688            if ( instance.normType != null )
689            {
690                return false;
691            }
692        }
693        else
694        {
695            if ( !normType.equals( instance.normType ) )
696            {
697                return false;
698            }
699        }
700
701        // Compare the values
702        if ( normValue.isNull() )
703        {
704            return instance.normValue.isNull();
705        }
706        else
707        {
708            if ( schemaManager != null )
709            {
710                MatchingRule equalityMatchingRule = attributeType.getEquality();
711
712                if ( equalityMatchingRule != null )
713                {
714                    return equalityMatchingRule.getLdapComparator().compare( normValue.getValue(),
715                        instance.normValue.getValue() ) == 0;
716                }
717                else
718                {
719                    // No Equality MR, use a direct comparison
720                    if ( normValue instanceof BinaryValue )
721                    {
722                        return Arrays.equals( normValue.getBytes(), instance.normValue.getBytes() );
723                    }
724                    else
725                    {
726                        return normValue.getString().equals( instance.normValue.getString() );
727                    }
728                }
729            }
730            else
731            {
732                return normValue.equals( instance.normValue );
733            }
734        }
735    }
736
737
738    /**
739     * Serialize the AVA into a buffer at the given position.
740     * 
741     * @param buffer The buffer which will contain the serialized Ava
742     * @param pos The position in the buffer for the serialized value
743     * @return The new position in the buffer
744     */
745    public int serialize( byte[] buffer, int pos ) throws IOException
746    {
747        if ( Strings.isEmpty( upName )
748            || Strings.isEmpty( upType )
749            || Strings.isEmpty( normType )
750            || ( upValue.isNull() )
751            || ( normValue.isNull() ) )
752        {
753            String message = "Cannot serialize an wrong ATAV, ";
754
755            if ( Strings.isEmpty( upName ) )
756            {
757                message += "the upName should not be null or empty";
758            }
759            else if ( Strings.isEmpty( upType ) )
760            {
761                message += "the upType should not be null or empty";
762            }
763            else if ( Strings.isEmpty( normType ) )
764            {
765                message += "the normType should not be null or empty";
766            }
767            else if ( upValue.isNull() )
768            {
769                message += "the upValue should not be null";
770            }
771            else if ( normValue.isNull() )
772            {
773                message += "the value should not be null";
774            }
775
776            LOG.error( message );
777            throw new IOException( message );
778        }
779
780        int length = 0;
781
782        // The upName
783        byte[] upNameBytes = null;
784
785        if ( upName != null )
786        {
787            upNameBytes = Strings.getBytesUtf8( upName );
788            length += 1 + 4 + upNameBytes.length;
789        }
790
791        // The upType
792        byte[] upTypeBytes = null;
793
794        if ( upType != null )
795        {
796            upTypeBytes = Strings.getBytesUtf8( upType );
797            length += 1 + 4 + upTypeBytes.length;
798        }
799
800        // The normType
801        byte[] normTypeBytes = null;
802
803        if ( normType != null )
804        {
805            normTypeBytes = Strings.getBytesUtf8( normType );
806            length += 1 + 4 + normTypeBytes.length;
807        }
808
809        // Is HR
810        length++;
811
812        // The hash code
813        length += 4;
814
815        // Check that we will be able to store the data in the buffer
816        if ( buffer.length - pos < length )
817        {
818            throw new ArrayIndexOutOfBoundsException();
819        }
820
821        // Write the upName
822        if ( upName != null )
823        {
824            buffer[pos++] = Serialize.TRUE;
825            pos = Serialize.serialize( upNameBytes, buffer, pos );
826        }
827        else
828        {
829            buffer[pos++] = Serialize.FALSE;
830        }
831
832        // Write the upType
833        if ( upType != null )
834        {
835            buffer[pos++] = Serialize.TRUE;
836            pos = Serialize.serialize( upTypeBytes, buffer, pos );
837        }
838        else
839        {
840            buffer[pos++] = Serialize.FALSE;
841        }
842
843        // Write the normType
844        if ( normType != null )
845        {
846            buffer[pos++] = Serialize.TRUE;
847            pos = Serialize.serialize( normTypeBytes, buffer, pos );
848        }
849        else
850        {
851            buffer[pos++] = Serialize.FALSE;
852        }
853
854        // Write the isHR flag
855        if ( normValue.isHumanReadable() )
856        {
857            buffer[pos++] = Serialize.TRUE;
858        }
859        else
860        {
861            buffer[pos++] = Serialize.FALSE;
862        }
863
864        // Write the upValue
865        if ( upValue.isHumanReadable() )
866        {
867            pos = ( ( StringValue ) upValue ).serialize( buffer, pos );
868        }
869        else
870        {
871            //pos = ( ( BinaryValue ) upValue ).serialize( buffer, pos );
872
873        }
874
875        // Write the normValue
876        if ( normValue.isHumanReadable() )
877        {
878            pos = ( ( StringValue ) normValue ).serialize( buffer, pos );
879        }
880        else
881        {
882            //pos = ( ( BinaryValue ) normValue ).serialize( buffer, pos );
883        }
884
885        // Write the hash code
886        pos = Serialize.serialize( h, buffer, pos );
887
888        return pos;
889    }
890
891
892    /**
893     * Deserialize an AVA from a byte[], starting at a given position
894     * 
895     * @param buffer The buffer containing the AVA
896     * @param pos The position in the buffer
897     * @return The new position
898     * @throws IOException If the serialized value is not an AVA
899     * @throws LdapInvalidAttributeValueException If the serialized AVA is invalid
900     */
901    public int deserialize( byte[] buffer, int pos ) throws IOException, LdapInvalidAttributeValueException
902    {
903        if ( ( pos < 0 ) || ( pos >= buffer.length ) )
904        {
905            throw new ArrayIndexOutOfBoundsException();
906        }
907
908        // Read the upName value, if it's not null
909        boolean hasUpName = Serialize.deserializeBoolean( buffer, pos );
910        pos++;
911
912        if ( hasUpName )
913        {
914            byte[] wrappedValueBytes = Serialize.deserializeBytes( buffer, pos );
915            pos += 4 + wrappedValueBytes.length;
916            upName = Strings.utf8ToString( wrappedValueBytes );
917        }
918
919        // Read the upType value, if it's not null
920        boolean hasUpType = Serialize.deserializeBoolean( buffer, pos );
921        pos++;
922
923        if ( hasUpType )
924        {
925            byte[] upTypeBytes = Serialize.deserializeBytes( buffer, pos );
926            pos += 4 + upTypeBytes.length;
927            upType = Strings.utf8ToString( upTypeBytes );
928        }
929
930        // Read the normType value, if it's not null
931        boolean hasNormType = Serialize.deserializeBoolean( buffer, pos );
932        pos++;
933
934        if ( hasNormType )
935        {
936            byte[] normTypeBytes = Serialize.deserializeBytes( buffer, pos );
937            pos += 4 + normTypeBytes.length;
938            normType = Strings.utf8ToString( normTypeBytes );
939        }
940
941        // Update the AtributeType
942        if ( schemaManager != null )
943        {
944            if ( !Strings.isEmpty( upType ) )
945            {
946                attributeType = schemaManager.getAttributeType( upType );
947            }
948            else
949            {
950                attributeType = schemaManager.getAttributeType( normType );
951            }
952        }
953
954        // Read the isHR flag
955        boolean isHR = Serialize.deserializeBoolean( buffer, pos );
956        pos++;
957
958        if ( isHR )
959        {
960            // Read the upValue
961            upValue = new StringValue( attributeType );
962            pos = ( ( StringValue ) upValue ).deserialize( buffer, pos );
963
964            // Read the normValue
965            normValue = new StringValue( attributeType );
966            pos = ( ( StringValue ) normValue ).deserialize( buffer, pos );
967        }
968        else
969        {
970            // TODO
971        }
972
973        // Read the hashCode
974        h = Serialize.deserializeInt( buffer, pos );
975        pos += 4;
976
977        return pos;
978    }
979
980
981    /**
982     * 
983     * An Ava is composed of  a type and a value.
984     * The data are stored following the structure :
985     * <ul>
986     *   <li>
987     *     <b>upName</b> The User provided ATAV
988     *   </li>
989     *   <li>
990     *     <b>start</b> The position of this ATAV in the Dn
991     *   </li>
992     *   <li>
993     *     <b>length</b> The ATAV length
994     *   </li>
995     *   <li>
996     *     <b>upType</b> The user Provided Type
997     *   </li>
998     *   <li>
999     *     <b>normType</b> The normalized AttributeType
1000     *   </li>
1001     *   <li>
1002     *     <b>isHR</b> Tells if the value is a String or not
1003     *   </li>
1004     * </ul>
1005     * <br/>
1006     * if the value is a String :
1007     * <ul>
1008     *   <li>
1009     *     <b>upValue</b> The User Provided value
1010     *   </li>
1011     *   <li>
1012     *     <b>value</b> The normalized value
1013     *   </li>
1014     * </ul>
1015     * <br/>
1016     * if the value is binary :
1017     * <ul>
1018     *   <li>
1019     *     <b>upValueLength</b>
1020     *   </li>
1021     *   <li>
1022     *     <b>upValue</b> The User Provided value
1023     *   </li>
1024     *   <li>
1025     *     <b>valueLength</b>
1026     *   </li>
1027     *   <li>
1028     *     <b>value</b> The normalized value
1029     *   </li>
1030     * </ul>
1031     * 
1032     * @see Externalizable#readExternal(ObjectInput)
1033     * 
1034     * @throws IoException If the Ava can't be written in the stream
1035     */
1036    public void writeExternal( ObjectOutput out ) throws IOException
1037    {
1038        if ( Strings.isEmpty( upName )
1039            || Strings.isEmpty( upType )
1040            || Strings.isEmpty( normType )
1041            || ( upValue.isNull() )
1042            || ( normValue.isNull() ) )
1043        {
1044            String message = "Cannot serialize an wrong ATAV, ";
1045
1046            if ( Strings.isEmpty( upName ) )
1047            {
1048                message += "the upName should not be null or empty";
1049            }
1050            else if ( Strings.isEmpty( upType ) )
1051            {
1052                message += "the upType should not be null or empty";
1053            }
1054            else if ( Strings.isEmpty( normType ) )
1055            {
1056                message += "the normType should not be null or empty";
1057            }
1058            else if ( upValue.isNull() )
1059            {
1060                message += "the upValue should not be null";
1061            }
1062            else if ( normValue.isNull() )
1063            {
1064                message += "the value should not be null";
1065            }
1066
1067            LOG.error( message );
1068            throw new IOException( message );
1069        }
1070
1071        if ( upName != null )
1072        {
1073            out.writeBoolean( true );
1074            out.writeUTF( upName );
1075        }
1076        else
1077        {
1078            out.writeBoolean( false );
1079        }
1080
1081        if ( upType != null )
1082        {
1083            out.writeBoolean( true );
1084            out.writeUTF( upType );
1085        }
1086        else
1087        {
1088            out.writeBoolean( false );
1089        }
1090
1091        if ( normType != null )
1092        {
1093            out.writeBoolean( true );
1094            out.writeUTF( normType );
1095        }
1096        else
1097        {
1098            out.writeBoolean( false );
1099        }
1100
1101        boolean isHR = normValue.isHumanReadable();
1102
1103        out.writeBoolean( isHR );
1104
1105        upValue.writeExternal( out );
1106        normValue.writeExternal( out );
1107
1108        // Write the hashCode
1109        out.writeInt( h );
1110
1111        out.flush();
1112    }
1113
1114
1115    /**
1116     * We read back the data to create a new ATAV. The structure
1117     * read is exposed in the {@link Ava#writeExternal(ObjectOutput)}
1118     * method
1119     * 
1120     * @see Externalizable#readExternal(ObjectInput)
1121     * 
1122     * @throws IOException If the Ava can't b written to the stream
1123     * @throws ClassNotFoundException If we can't deserialize an Ava from the stream
1124     */
1125    public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException
1126    {
1127        boolean hasUpName = in.readBoolean();
1128
1129        if ( hasUpName )
1130        {
1131            upName = in.readUTF();
1132        }
1133
1134        boolean hasUpType = in.readBoolean();
1135
1136        if ( hasUpType )
1137        {
1138            upType = in.readUTF();
1139        }
1140
1141        boolean hasNormType = in.readBoolean();
1142
1143        if ( hasNormType )
1144        {
1145            normType = in.readUTF();
1146        }
1147
1148        if ( schemaManager != null )
1149        {
1150            if ( !Strings.isEmpty( upType ) )
1151            {
1152                attributeType = schemaManager.getAttributeType( upType );
1153            }
1154            else
1155            {
1156                attributeType = schemaManager.getAttributeType( normType );
1157            }
1158        }
1159
1160        boolean isHR = in.readBoolean();
1161
1162        if ( isHR )
1163        {
1164            upValue = StringValue.deserialize( attributeType, in );
1165            normValue = StringValue.deserialize( attributeType, in );
1166        }
1167        else
1168        {
1169            upValue = BinaryValue.deserialize( attributeType, in );
1170            normValue = BinaryValue.deserialize( attributeType, in );
1171        }
1172
1173        h = in.readInt();
1174
1175        if ( schemaManager != null )
1176        {
1177            attributeType = schemaManager.getAttributeType( upType );
1178        }
1179    }
1180
1181
1182    /**
1183     * Tells if the Ava is schema aware or not.
1184     * 
1185     * @return true if the Ava is schema aware
1186     */
1187    public boolean isSchemaAware()
1188    {
1189        return attributeType != null;
1190    }
1191
1192
1193    /**
1194     * @return the attributeType
1195     */
1196    public AttributeType getAttributeType()
1197    {
1198        return attributeType;
1199    }
1200
1201
1202    private int compareValues( Ava that )
1203    {
1204        int comp = 0;
1205
1206        if ( normValue.getNormValue() instanceof String )
1207        {
1208            comp = ( ( String ) normValue.getNormValue() ).compareTo( ( ( String ) that.normValue.getNormValue() ) );
1209
1210            return comp;
1211        }
1212        else
1213        {
1214            byte[] bytes1 = ( byte[] ) normValue.getNormValue();
1215            byte[] bytes2 = ( byte[] ) that.normValue.getNormValue();
1216
1217            for ( int pos = 0; pos < bytes1.length; pos++ )
1218            {
1219                int v1 = ( bytes1[pos] & 0x00FF );
1220                int v2 = ( bytes2[pos] & 0x00FF );
1221
1222                if ( v1 > v2 )
1223                {
1224                    return 1;
1225                }
1226                else if ( v2 > v1 )
1227                {
1228                    return -1;
1229                }
1230            }
1231
1232            return 0;
1233        }
1234
1235    }
1236
1237
1238    /**
1239     * @see Comparable#compareTo(Object)
1240     */
1241    @SuppressWarnings("unchecked")
1242    public int compareTo( Ava that )
1243    {
1244        if ( that == null )
1245        {
1246            return 1;
1247        }
1248
1249        int comp = 0;
1250
1251        if ( schemaManager == null )
1252        {
1253            // Compare the ATs
1254            comp = normType.compareTo( that.normType );
1255
1256            if ( comp != 0 )
1257            {
1258                return comp;
1259            }
1260
1261            // and compare the values
1262            if ( normValue == null )
1263            {
1264                if ( that.normValue == null )
1265                {
1266                    return 0;
1267                }
1268                else
1269                {
1270                    return -1;
1271                }
1272            }
1273            else
1274            {
1275                if ( that.normValue == null )
1276                {
1277                    return 1;
1278                }
1279                else
1280                {
1281                    if ( normValue instanceof StringValue )
1282                    {
1283                        comp = ( ( StringValue ) normValue ).compareTo( ( StringValue ) that.normValue );
1284
1285                        return comp;
1286                    }
1287                    else
1288                    {
1289                        comp = ( ( BinaryValue ) normValue ).compareTo( ( BinaryValue ) that.normValue );
1290
1291                        return comp;
1292                    }
1293                }
1294            }
1295        }
1296        else
1297        {
1298            if ( that.schemaManager == null )
1299            {
1300                // Problem : we will apply the current Ava SchemaManager to the given Ava
1301                try
1302                {
1303                    that.apply( schemaManager );
1304                }
1305                catch ( LdapInvalidDnException lide )
1306                {
1307                    return 1;
1308                }
1309            }
1310
1311            // First compare the AT OID
1312            comp = attributeType.getOid().compareTo( that.attributeType.getOid() );
1313
1314            if ( comp != 0 )
1315            {
1316                return comp;
1317            }
1318
1319            // Now, compare the two values using the ordering matchingRule comparator, if any
1320            MatchingRule orderingMR = attributeType.getOrdering();
1321
1322            if ( orderingMR != null )
1323            {
1324                LdapComparator<Object> comparator = ( LdapComparator<Object> ) orderingMR.getLdapComparator();
1325
1326                if ( comparator != null )
1327                {
1328                    comp = comparator.compare( normValue.getNormValue(), that.normValue.getNormValue() );
1329
1330                    return comp;
1331                }
1332                else
1333                {
1334                    comp = compareValues( that );
1335
1336                    return comp;
1337                }
1338            }
1339            else
1340            {
1341                comp = compareValues( that );
1342
1343                return comp;
1344            }
1345        }
1346    }
1347
1348
1349    /**
1350     * A String representation of an Ava, as provided by the user.
1351     *
1352     * @return A string representing an Ava
1353     */
1354    public String toString()
1355    {
1356        return upName;
1357    }
1358}