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   *    https://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.Value;
31  import org.apache.directory.api.ldap.model.exception.LdapException;
32  import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException;
33  import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException;
34  import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
35  import org.apache.directory.api.ldap.model.schema.AttributeType;
36  import org.apache.directory.api.ldap.model.schema.LdapComparator;
37  import org.apache.directory.api.ldap.model.schema.MatchingRule;
38  import org.apache.directory.api.ldap.model.schema.Normalizer;
39  import org.apache.directory.api.ldap.model.schema.SchemaManager;
40  import org.apache.directory.api.util.Serialize;
41  import org.apache.directory.api.util.Strings;
42  import org.slf4j.Logger;
43  import org.slf4j.LoggerFactory;
44  
45  
46  /**
47   * <p>
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   * </p>
55   * <p>
56   * We will also keep a User Provided form of the AVA (Attribute Type And Value),
57   * called upName.
58   * </p>
59   * <p>
60   * This class is immutable
61   * </p>
62   *
63   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
64   */
65  public class Ava implements Externalizable, Cloneable, Comparable<Ava>
66  {
67      /**
68       * Declares the Serial Version Uid.
69       *
70       * @see <a
71       *      href="http://c2.com/cgi/wiki?AlwaysDeclareSerialVersionUid">Always
72       *      Declare Serial Version Uid</a>
73       */
74      private static final long serialVersionUID = 1L;
75  
76      /** The LoggerFactory used by this class */
77      private static final Logger LOG = LoggerFactory.getLogger( Ava.class );
78  
79      /** The normalized Name type */
80      /* package protected */ String normType;
81  
82      /** The user provided Name type */
83      /* package protected */ String upType;
84  
85      /** The value. It can be a String or a byte array */
86      /* package protected */ Value value;
87  
88      /** The user provided Ava */
89      /* package protected */ String upName;
90  
91      /** The attributeType if the Ava is schemaAware */
92      /* package protected */ AttributeType attributeType;
93  
94      /** the schema manager */
95      private transient SchemaManager schemaManager;
96  
97      /** The computed hashcode */
98      private volatile int h;
99  
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     /* Package protected */ void setAttributeType( SchemaManager schemaManager, String upType )
1301         throws LdapInvalidDnException
1302     {
1303         if ( schemaManager != null )
1304         {
1305             this.schemaManager = schemaManager;
1306 
1307             try
1308             {
1309                 attributeType = schemaManager.lookupAttributeTypeRegistry( upType );
1310             }
1311             catch ( LdapException le )
1312             {
1313                 String message = I18n.err( I18n.ERR_13600_TYPE_IS_NULL_OR_EMPTY );
1314                 throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, message, le );
1315             }
1316 
1317             normType = attributeType.getOid();
1318         }
1319         else
1320         {
1321             String upTypeTrimmed = Strings.trim( upType );
1322 
1323             if ( Strings.isEmpty( upTypeTrimmed ) )
1324             {
1325                 String message = I18n.err( I18n.ERR_13600_TYPE_IS_NULL_OR_EMPTY );
1326                 // Do NOT log the message here. The error may be handled and therefore the log message may polute the log files.
1327                 // Let the caller log the exception if needed.
1328                 throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, message );
1329             }
1330             else
1331             {
1332                 // In this case, we will use the upType instead
1333                 this.normType = Strings.lowerCaseAscii( upTypeTrimmed );
1334                 this.upType = upType;
1335             }
1336         }
1337     }
1338 
1339 
1340     private int compareValues( Ava that )
1341     {
1342         int comp;
1343 
1344         if ( value.isHumanReadable() )
1345         {
1346             comp = value.compareTo( that.value );
1347 
1348             return comp;
1349         }
1350         else
1351         {
1352             byte[] bytes1 = value.getBytes();
1353             byte[] bytes2 = that.value.getBytes();
1354 
1355             for ( int pos = 0; pos < bytes1.length; pos++ )
1356             {
1357                 int v1 = bytes1[pos] & 0x00FF;
1358                 int v2 = bytes2[pos] & 0x00FF;
1359 
1360                 if ( v1 > v2 )
1361                 {
1362                     return 1;
1363                 }
1364                 else if ( v2 > v1 )
1365                 {
1366                     return -1;
1367                 }
1368             }
1369 
1370             return 0;
1371         }
1372 
1373     }
1374 
1375 
1376     /**
1377      * @see Comparable#compareTo(Object)
1378      */
1379     @Override
1380     public int compareTo( Ava that )
1381     {
1382         if ( that == null )
1383         {
1384             return 1;
1385         }
1386 
1387         int comp;
1388 
1389         if ( schemaManager == null )
1390         {
1391             // Compare the ATs
1392             comp = normType.compareTo( that.normType );
1393 
1394             if ( comp != 0 )
1395             {
1396                 return comp;
1397             }
1398 
1399             // and compare the values
1400             if ( value == null )
1401             {
1402                 if ( that.value == null )
1403                 {
1404                     return 0;
1405                 }
1406                 else
1407                 {
1408                     return -1;
1409                 }
1410             }
1411             else
1412             {
1413                 if ( that.value == null )
1414                 {
1415                     return 1;
1416                 }
1417                 else
1418                 {
1419                     comp = value.compareTo( ( Value ) that.value );
1420 
1421                     return comp;
1422                 }
1423             }
1424         }
1425         else
1426         {
1427             if ( that.schemaManager == null )
1428             {
1429                 // Problem : we will apply the current Ava SchemaManager to the given Ava
1430                 try
1431                 {
1432                     that.apply( schemaManager );
1433                 }
1434                 catch ( LdapInvalidDnException lide )
1435                 {
1436                     return 1;
1437                 }
1438             }
1439 
1440             // First compare the AT OID
1441             comp = attributeType.getOid().compareTo( that.attributeType.getOid() );
1442 
1443             if ( comp != 0 )
1444             {
1445                 return comp;
1446             }
1447 
1448             // Now, compare the two values using the ordering matchingRule comparator, if any
1449             MatchingRule orderingMR = attributeType.getOrdering();
1450 
1451             if ( orderingMR != null )
1452             {
1453                 LdapComparator<Object> comparator = ( LdapComparator<Object> ) orderingMR.getLdapComparator();
1454 
1455                 if ( comparator != null )
1456                 {
1457                     comp = value.compareTo( that.value );
1458 
1459                     return comp;
1460                 }
1461                 else
1462                 {
1463                     comp = compareValues( that );
1464 
1465                     return comp;
1466                 }
1467             }
1468             else
1469             {
1470                 comp = compareValues( that );
1471 
1472                 return comp;
1473             }
1474         }
1475     }
1476     
1477     
1478     /**
1479      * A String representation of an Ava, as provided by the user.
1480      *
1481      * @return A string representing an Ava
1482      */
1483     @Override
1484     public String toString()
1485     {
1486         return upName;
1487     }
1488 }