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