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