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