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