001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *  
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *  
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License. 
018 *  
019 */
020package org.apache.directory.shared.ldap.model.name;
021
022
023import java.io.Externalizable;
024import java.io.IOException;
025import java.io.ObjectInput;
026import java.io.ObjectOutput;
027
028import org.apache.directory.shared.i18n.I18n;
029import org.apache.directory.shared.ldap.model.entry.BinaryValue;
030import org.apache.directory.shared.ldap.model.entry.StringValue;
031import org.apache.directory.shared.ldap.model.entry.Value;
032import org.apache.directory.shared.ldap.model.exception.LdapException;
033import org.apache.directory.shared.ldap.model.exception.LdapInvalidAttributeValueException;
034import org.apache.directory.shared.ldap.model.exception.LdapInvalidDnException;
035import org.apache.directory.shared.ldap.model.message.ResultCodeEnum;
036import org.apache.directory.shared.ldap.model.schema.AttributeType;
037import org.apache.directory.shared.ldap.model.schema.MatchingRule;
038import org.apache.directory.shared.ldap.model.schema.SchemaManager;
039import org.apache.directory.shared.util.Strings;
040import org.slf4j.Logger;
041import org.slf4j.LoggerFactory;
042
043
044/**
045 * A Attribute Type And Value, which is the basis of all Rdn. It contains a
046 * type, and a value. The type must not be case sensitive. Superfluous leading
047 * and trailing spaces MUST have been trimmed before. The value MUST be in UTF8
048 * format, according to RFC 2253. If the type is in OID form, then the value
049 * must be a hexadecimal string prefixed by a '#' character. Otherwise, the
050 * string must respect the RC 2253 grammar. 
051 *
052 * We will also keep a User Provided form of the AVA (Attribute Type And Value),
053 * called upName.
054 *
055 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
056 */
057public class Ava implements Externalizable, Cloneable
058{
059    /**
060     * Declares the Serial Version Uid.
061     *
062     * @see <a
063     *      href="http://c2.com/cgi/wiki?AlwaysDeclareSerialVersionUid">Always
064     *      Declare Serial Version Uid</a>
065     */
066    private static final long serialVersionUID = 1L;
067
068    /** The LoggerFactory used by this class */
069    private static final Logger LOG = LoggerFactory.getLogger( Ava.class );
070
071    /** The normalized Name type */
072    private String normType;
073    
074    /** The user provided Name type */
075    private String upType;
076
077    /** The name value. It can be a String or a byte array */
078    private Value<?> normValue;
079
080    /** The name user provided value. It can be a String or a byte array */
081    private Value<?> upValue;
082
083    /** The user provided Ava */
084    private String upName;
085
086    /** The attributeType if the Ava is schemaAware */
087    private AttributeType attributeType;
088
089    /** the schema manager */
090    private SchemaManager schemaManager;
091    
092    /** The computed hashcode */
093    private volatile int h;
094
095    /**
096     * Constructs an empty Ava
097     */
098    public Ava()
099    {
100        this( null );
101    }
102
103    
104    /**
105     * Constructs an empty schema aware Ava.
106     * 
107     * @param schemaManager The SchemaManager instance
108     */
109    public Ava( SchemaManager schemaManager )
110    {
111        normType = null;
112        upType = null;
113        normValue = null;
114        upValue = null;
115        upName = "";
116        this.schemaManager = schemaManager;
117        this.attributeType = null;
118    }
119    
120    
121    /**
122     * Construct an Ava containing a binary value. 
123     * <p>
124     * Note that the upValue should <b>not</b> be null or empty, or resolve
125     * to an empty string after having trimmed it. 
126     *
127     * @param upType The User Provided type
128     * @param upValue The User Provided binary value
129     * 
130     * @throws LdapInvalidDnException If the given type or value are invalid
131     */
132    public Ava( String upType, byte[] upValue ) throws LdapInvalidDnException
133    {
134        this( null, upType, upValue );
135    }
136
137    
138    /**
139     * Construct a schema aware Ava containing a binary value. The AttributeType
140     * and value will be normalized accordingly to the given SchemaManager.
141     * <p>
142     * Note that the upValue should <b>not</b> be null or empty, or resolve
143     * to an empty string after having trimmed it. 
144     *
145     * @param schemaManager The SchemaManager instance
146     * @param upType The User Provided type
147     * @param upValue The User Provided binary value
148     * 
149     * @throws LdapInvalidDnException If the given type or value are invalid
150     */
151    public Ava( SchemaManager schemaManager, String upType, byte[] upValue ) throws LdapInvalidDnException
152    {
153        if ( schemaManager != null )
154        { 
155            try
156            {
157                attributeType = schemaManager.lookupAttributeTypeRegistry( upType );
158            }
159            catch ( LdapException le )
160            {
161                String message =  I18n.err( I18n.ERR_04188 );
162                LOG.error( message );
163                throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, message, le );
164            }
165            
166            try
167            { 
168                createAva( schemaManager, upType, new BinaryValue( attributeType, upValue ) );
169            }
170            catch ( LdapInvalidAttributeValueException liave )
171            {
172                String message =  I18n.err( I18n.ERR_04188 );
173                LOG.error( message );
174                throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, message, liave );
175            }
176        }
177        else
178        {
179            createAva( upType, new BinaryValue( upValue ) );
180        }
181    }
182
183    
184    /**
185     * Construct an Ava with a String value. 
186     * <p>
187     * Note that the upValue should <b>not</b> be null or empty, or resolve
188     * to an empty string after having trimmed it. 
189     *
190     * @param upType The User Provided type
191     * @param upValue The User Provided String value
192     * 
193     * @throws LdapInvalidDnException If the given type or value are invalid
194     */
195    public Ava( String upType, String upValue ) throws LdapInvalidDnException
196    {
197        this( null, upType, upValue );
198    }
199    
200    
201    /**
202     * Construct a schema aware Ava with a String value.
203     * <p>
204     * Note that the upValue should <b>not</b> be null or empty, or resolve
205     * to an empty string after having trimmed it. 
206     *
207     * @param schemaManager The SchemaManager instance
208     * @param upType The User Provided type
209     * @param upValue The User Provided String value
210     * 
211     * @throws LdapInvalidDnException If the given type or value are invalid
212     */
213    public Ava( SchemaManager schemaManager, String upType, String upValue ) throws LdapInvalidDnException
214    {
215        if ( schemaManager != null )
216        { 
217            try
218            {
219                attributeType = schemaManager.lookupAttributeTypeRegistry( upType );
220            }
221            catch ( LdapException le )
222            {
223                String message =  I18n.err( I18n.ERR_04188 );
224                LOG.error( message );
225                throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, message, le );
226            }
227            
228            try
229            { 
230                createAva( schemaManager, upType, new StringValue( attributeType, upValue ) );
231            }
232            catch ( LdapInvalidAttributeValueException liave )
233            {
234                String message =  I18n.err( I18n.ERR_04188 );
235                LOG.error( message );
236                throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, message, liave );
237            }
238        }
239        else
240        {
241            createAva( upType, new StringValue( upValue ) );
242        }
243    }
244
245    
246    /**
247     * Construct a schema aware Ava. The AttributeType and value will be checked accordingly
248     * to the SchemaManager.
249     * <p>
250     * Note that the upValue should <b>not</b> be null or empty, or resolve
251     * to an empty string after having trimmed it. 
252     *
253     * @param schemaManager The SchemaManager instance
254     * @param upType The User Provided type
255     * @param upValue The User Provided value
256     * 
257     * @throws LdapInvalidDnException If the given type or value are invalid
258     */
259    private void createAva( SchemaManager schemaManager, String upType, Value<?> upValue ) throws LdapInvalidDnException
260    {
261        normType = attributeType.getOid();
262        this.upType = upType;
263            
264        try
265        {
266            MatchingRule equalityMatchingRule = attributeType.getEquality();
267            
268            if ( equalityMatchingRule != null )
269            {
270                this.normValue = equalityMatchingRule.getNormalizer().normalize( upValue );
271            }
272            else
273            {
274                this.normValue = upValue;
275            }
276        }
277        catch ( LdapException le )
278        {
279            String message =  I18n.err( I18n.ERR_04188 );
280            LOG.error( message );
281            throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, message, le );
282        }
283
284        this.upValue = upValue;
285        
286        upName = this.upType + '=' + ( this.upValue == null ? "" : this.upValue.getString() );
287        hashCode();
288    }
289
290    
291    /**
292     * Construct an Ava. The type and value are normalized :
293     * <li> the type is trimmed and lowercased </li>
294     * <li> the value is trimmed </li>
295     * <p>
296     * Note that the upValue should <b>not</b> be null or empty, or resolved
297     * to an empty string after having trimmed it. 
298     *
299     * @param upType The User Provided type
300     * @param upValue The User Provided value
301     * 
302     * @throws LdapInvalidDnException If the given type or value are invalid
303     */
304    private void createAva( String upType, Value<?> upValue ) throws LdapInvalidDnException
305    {
306        String upTypeTrimmed = Strings.trim(upType);
307        String normTypeTrimmed = Strings.trim(normType);
308        
309        if ( Strings.isEmpty(upTypeTrimmed) )
310        {
311            if ( Strings.isEmpty(normTypeTrimmed) )
312            {
313                String message =  I18n.err( I18n.ERR_04188 );
314                LOG.error( message );
315                throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, message );
316            }
317            else
318            {
319                // In this case, we will use the normType instead
320                this.normType = Strings.lowerCaseAscii( normTypeTrimmed );
321                this.upType = normType;
322            }
323        }
324        else if ( Strings.isEmpty(normTypeTrimmed) )
325        {
326            // In this case, we will use the upType instead
327            this.normType = Strings.lowerCaseAscii( upTypeTrimmed );
328            this.upType = upType;
329        }
330        else
331        {
332            this.normType = Strings.lowerCaseAscii( normTypeTrimmed );
333            this.upType = upType;
334            
335        }
336            
337        this.normValue = upValue;
338        this.upValue = upValue;
339        
340        upName = this.upType + '=' + ( this.upValue == null ? "" : this.upValue.getString() );
341        hashCode();
342    }
343
344
345    /**
346     * Construct an Ava. The type and value are normalized :
347     * <li> the type is trimmed and lowercased </li>
348     * <li> the value is trimmed </li>
349     * <p>
350     * Note that the upValue should <b>not</b> be null or empty, or resolved
351     * to an empty string after having trimmed it. 
352     *
353     * @param schemaManager The SchemaManager
354     * @param upType The User Provided type
355     * @param normType The normalized type
356     * @param upValue The User Provided value
357     * @param normValue The normalized value
358     * 
359     * @throws LdapInvalidDnException If the given type or value are invalid
360     */
361    // WARNING : The protection level is left unspecified intentionally.
362    // We need this method to be visible from the DnParser class, but not
363    // from outside this package.
364    /* Unspecified protection */ Ava( SchemaManager schemaManager, String upType, String normType, Value<?> upValue, Value<?> normValue ) 
365        throws LdapInvalidDnException
366    {
367        this.upType = upType;
368        this.normType = normType;
369        this.upValue = upValue;
370        this.normValue = normValue;
371        upName = this.upType + '=' + ( this.upValue == null ? "" : this.upValue.getString() );
372        
373        if ( schemaManager != null )
374        {
375            apply( schemaManager );
376        }
377
378        hashCode();
379    }
380
381
382    /**
383     * Construct an Ava. The type and value are normalized :
384     * <li> the type is trimmed and lowercased </li>
385     * <li> the value is trimmed </li>
386     * <p>
387     * Note that the upValue should <b>not</b> be null or empty, or resolved
388     * to an empty string after having trimmed it. 
389     *
390     * @param upType The User Provided type
391     * @param normType The normalized type
392     * @param upValue The User Provided value
393     * @param normValue The normalized value
394     * @param upName The User Provided name (may be escaped)
395     * 
396     * @throws LdapInvalidDnException If the given type or value are invalid
397     */
398    // WARNING : The protection level is left unspecified intentionally.
399    // We need this method to be visible from the DnParser class, but not
400    // from outside this package.
401    /* Unspecified protection */ Ava( String upType, String normType, Value<?> upValue, Value<?> normValue, String upName )
402        throws LdapInvalidDnException
403    {
404        String upTypeTrimmed = Strings.trim( upType );
405        String normTypeTrimmed = Strings.trim( normType );
406
407        if ( Strings.isEmpty( upTypeTrimmed ) )
408        {
409            if ( Strings.isEmpty( normTypeTrimmed ) )
410            {
411                String message = I18n.err( I18n.ERR_04188 );
412                LOG.error( message );
413                throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, message );
414            }
415            else
416            {
417                // In this case, we will use the normType instead
418                this.normType = Strings.lowerCaseAscii( normTypeTrimmed );
419                this.upType = normType;
420            }
421        }
422        else if ( Strings.isEmpty( normTypeTrimmed ) )
423        {
424            // In this case, we will use the upType instead
425            this.normType = Strings.lowerCaseAscii( upTypeTrimmed );
426            this.upType = upType;
427        }
428        else
429        {
430            this.normType = Strings.lowerCaseAscii( normTypeTrimmed );
431            this.upType = upType;
432        }
433
434        this.normValue = normValue;
435        this.upValue = upValue;
436        this.upName = upName;
437        hashCode();
438    }
439
440    
441    /**
442     * Apply a SchemaManager to the Ava. It will normalize the Ava.<br/>
443     * If the Ava already had a SchemaManager, then the new SchemaManager will be
444     * used instead.
445     * 
446     * @param schemaManager The SchemaManager instance to use
447     * @throws LdapInvalidDnException If the Ava can't be normalized accordingly
448     * to the given SchemaManager
449     */
450    public void apply( SchemaManager schemaManager ) throws LdapInvalidDnException
451    {
452        if ( schemaManager != null )
453        { 
454            this.schemaManager = schemaManager;
455            
456            AttributeType attributeType = null;
457            
458            try
459            {
460                attributeType = schemaManager.lookupAttributeTypeRegistry( normType );
461            }
462            catch ( LdapException le )
463            {
464                String message =  I18n.err( I18n.ERR_04188 );
465                LOG.error( message );
466                throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, message, le );
467            }
468            
469            if ( this.attributeType == attributeType ) 
470            {
471                // No need to normalize again
472                return;
473            }
474            else
475            {
476                this.attributeType = attributeType;
477            }
478            
479            normType = attributeType.getOid();
480            
481            if ( normValue != null )
482            {
483                return;
484            }
485
486            try
487            {
488                // We use the Equality matching rule to normalize the value
489                MatchingRule equalityMatchingRule = attributeType.getEquality();
490                
491                if ( equalityMatchingRule != null )
492                {
493                    this.normValue = equalityMatchingRule.getNormalizer().normalize( upValue );
494                }
495                else
496                {
497                    this.normValue = upValue;
498                }
499            }
500            catch ( LdapException le )
501            {
502                String message =  I18n.err( I18n.ERR_04188 );
503                LOG.error( message );
504                throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, message, le );
505            }
506            
507            hashCode();
508        }
509    }
510    
511
512    /**
513     * Get the normalized type of a Ava
514     *
515     * @return The normalized type
516     */
517    public String getNormType()
518    {
519        return normType;
520    }
521
522    /**
523     * Get the user provided type of a Ava
524     *
525     * @return The user provided type
526     */
527    public String getUpType()
528    {
529        return upType;
530    }
531
532
533    /**
534     * Get the Value of a Ava
535     *
536     * @return The value
537     */
538    public Value<?> getNormValue()
539    {
540        return normValue.clone();
541    }
542
543    /**
544     * Get the User Provided Value of a Ava
545     *
546     * @return The value
547     */
548    public Value<?> getUpValue()
549    {
550        return upValue.clone();
551    }
552
553    /**
554     * Get the normalized Name of a Ava
555     *
556     * @return The name
557     */
558    public String getNormName()
559    {
560        return normalize();
561    }
562
563
564    /**
565     * Get the user provided form of this attribute type and value
566     *
567     * @return The user provided form of this ava
568     */
569    public String getUpName()
570    {
571        return upName;
572    }
573
574
575    /**
576     * Implements the cloning.
577     *
578     * @return a clone of this object
579     */
580    public Ava clone()
581    {
582        try
583        {
584            Ava clone = (Ava) super.clone();
585            clone.upValue = upValue.clone();
586            clone.normValue = normValue.clone();
587            
588            return clone;
589        }
590        catch ( CloneNotSupportedException cnse )
591        {
592            throw new Error( "Assertion failure" );
593        }
594    }
595
596
597    private static final boolean[] DN_ESCAPED_CHARS = new boolean[]
598        {
599        true,  true,  true,  true,  true,  true,  true,  true,  // 0x00 -> 0x07
600        true,  true,  true,  true,  true,  true,  true,  true,  // 0x08 -> 0x0F
601        true,  true,  true,  true,  true,  true,  true,  true,  // 0x10 -> 0x17
602        true,  true,  true,  true,  true,  true,  true,  true,  // 0x18 -> 0x1F
603        true,  false, true,  true,  false, false, false, false, // 0x20 -> 0x27 ' ', '"', '#'
604        false, false, false, true,  true,  false, false, false, // 0x28 -> 0x2F '+', ','
605        false, false, false, false, false, false, false, false, // 0x30 -> 0x37 
606        false, false, false, true,  true,  false, true,  false, // 0x38 -> 0x3F ';', '<', '>'
607        false, false, false, false, false, false, false, false, // 0x40 -> 0x47
608        false, false, false, false, false, false, false, false, // 0x48 -> 0x4F
609        false, false, false, false, false, false, false, false, // 0x50 -> 0x57
610        false, false, false, false, true,  false, false, false, // 0x58 -> 0x5F
611        false, false, false, false, false, false, false, false, // 0x60 -> 0x67
612        false, false, false, false, false, false, false, false, // 0x68 -> 0x6F
613        false, false, false, false, false, false, false, false, // 0x70 -> 0x77
614        false, false, false, false, false, false, false, false, // 0x78 -> 0x7F
615        };
616    
617    
618    /**
619     * Normalize the value in order to be able to use it in a DN as a String. Some
620     * characters will be escaped (prefixed with '\'), 
621     * 
622     * @return The normalized Ava
623     */
624    private String normalizeValue()
625    {
626        // The result will be gathered in a stringBuilder
627        StringBuilder sb = new StringBuilder();
628        
629        String normalizedValue =  normValue.getString();
630        int valueLength = normalizedValue.length();
631
632        if ( normalizedValue.length() > 0 )
633        {
634            char[] chars = normalizedValue.toCharArray();
635
636            // Here, we have a char to escape. Start again the loop...
637            for ( int i = 0; i < valueLength; i++ )
638            {
639                char c = chars[i];
640
641                if ( ( c >= 0 ) && ( c < DN_ESCAPED_CHARS.length ) && DN_ESCAPED_CHARS[ c ] ) 
642                {
643                    // Some chars need to be escaped even if they are US ASCII
644                    // Just prefix them with a '\'
645                    // Special cases are ' ' (space), '#') which need a special
646                    // treatment.
647                    switch ( c )
648                    {
649                        case ' ' :
650                            if ( ( i == 0 ) || ( i == valueLength - 1 ) )
651                            {
652                                sb.append( "\\ " );
653                            }
654                            else
655                            {
656                                sb.append( ' ' );
657                            }
658    
659                            break;
660                            
661                        case '#' :
662                            if ( i == 0 )
663                            {
664                                sb.append( "\\#" );
665                                continue;
666                            }
667                            else
668                            {
669                                sb.append( '#' );
670                            }
671                        
672                            break;
673
674                        default :
675                            sb.append( '\\' ).append( c );
676                    }
677                }
678                else
679                {
680                    // Standard ASCII chars are just appended
681                    sb.append( c );
682                }
683            }
684        }
685        
686        return sb.toString();
687    }
688    
689
690    /**
691     * A Normalized String representation of a Ava :
692     * <ul>
693     * <li>type is trimed and lowercased</li> 
694     * <li>value is trimed and lowercased, and special characters</li>
695     * </ul>
696     * are escaped if needed.
697     *
698     * @return A normalized string representing an Ava
699     */
700    public String normalize()
701    {
702        if ( normValue.isHumanReadable() )
703        {
704            // The result will be gathered in a stringBuilder
705            StringBuilder sb = new StringBuilder();
706            
707            // First, store the type and the '=' char
708            sb.append( normType ).append( '=' );
709            
710            String normalizedValue = normValue.getString();
711            
712            if ( normalizedValue.length() > 0 )
713            {
714                sb.append( normalizeValue() );
715            }
716            
717            return sb.toString();
718        }
719        else
720        {
721            return normType + "=#"
722                + Strings.dumpHexPairs( normValue .getBytes() );
723        }
724    }
725
726
727    /**
728     * Gets the hashcode of this object.
729     *
730     * @see java.lang.Object#hashCode()
731     * @return The instance hash code
732     */
733    public int hashCode()
734    {
735        if ( h == 0 )
736        {
737            h = 37;
738    
739            h = h*17 + ( normType != null ? normType.hashCode() : 0 );
740            h = h*17 + ( normValue != null ? normValue.hashCode() : 0 );
741        }
742
743        return h;
744    }
745    
746
747    /**
748     * @see Object#equals(Object)
749     */
750    public boolean equals( Object obj )
751    {
752        if ( this == obj )
753        {
754            return true;
755        }
756        
757        if ( !( obj instanceof Ava) )
758        {
759            return false;
760        }
761        
762        Ava instance = (Ava)obj;
763     
764        // Compare the type
765        if ( normType == null )
766        {
767            if ( instance.normType != null )
768            {
769                return false;
770            }
771        }
772        else 
773        {
774            if ( !normType.equals( instance.normType ) )
775            {
776                return false;
777            }
778        }
779            
780        // Compare the values
781        if ( normValue.isNull() )
782        {
783            return instance.normValue.isNull();
784        }
785        else
786        {
787            if ( schemaManager != null )
788            {
789                MatchingRule equalityMatchingRule = attributeType.getEquality();
790                
791                if ( equalityMatchingRule != null )
792                {
793                    return equalityMatchingRule.getLdapComparator().compare( normValue.getValue(), instance.normValue.getValue() ) == 0;
794                }
795                
796                return false;
797            }
798            else
799            {
800                return normValue.equals( instance.normValue );
801            }
802        }
803    }
804
805    
806    /**
807     * 
808     * An Ava is composed of  a type and a value.
809     * The data are stored following the structure :
810     * <ul>
811     *   <li>
812     *     <b>upName</b> The User provided ATAV
813     *   </li>
814     *   <li>
815     *     <b>start</b> The position of this ATAV in the Dn
816     *   </li>
817     *   <li>
818     *     <b>length</b> The ATAV length
819     *   </li>
820     *   <li>
821     *     <b>upType</b> The user Provided Type
822     *   </li>
823     *   <li>
824     *     <b>normType</b> The normalized AttributeType
825     *   </li>
826     *   <li>
827     *     <b>isHR</b> Tells if the value is a String or not
828     *   </li>
829     * </ul>
830     * <br/>
831     * if the value is a String :
832     * <ul>
833     *   <li>
834     *     <b>upValue</b> The User Provided value
835     *   </li>
836     *   <li>
837     *     <b>value</b> The normalized value
838     *   </li>
839     * </ul>
840     * <br/>
841     * if the value is binary :
842     * <ul>
843     *   <li>
844     *     <b>upValueLength</b>
845     *   </li>
846     *   <li>
847     *     <b>upValue</b> The User Provided value
848     *   </li>
849     *   <li>
850     *     <b>valueLength</b>
851     *   </li>
852     *   <li>
853     *     <b>value</b> The normalized value
854     *   </li>
855     * </ul>
856     * 
857     * @see Externalizable#readExternal(ObjectInput)
858     * 
859     * @throws IoException If the Ava can't be written in the stream
860     */
861    public void writeExternal( ObjectOutput out ) throws IOException
862    {
863        if ( Strings.isEmpty( upName )
864            || Strings.isEmpty( upType )
865            || Strings.isEmpty( normType )
866            || ( upValue.isNull() )
867            || ( normValue.isNull() ) )
868        {
869            String message = "Cannot serialize an wrong ATAV, ";
870            
871            if ( Strings.isEmpty(upName) )
872            {
873                message += "the upName should not be null or empty";
874            }
875            else if ( Strings.isEmpty(upType) )
876            {
877                message += "the upType should not be null or empty";
878            }
879            else if ( Strings.isEmpty(normType) )
880            {
881                message += "the normType should not be null or empty";
882            }
883            else if ( upValue.isNull() )
884            {
885                message += "the upValue should not be null";
886            }
887            else if ( normValue.isNull() )
888            {
889                message += "the value should not be null";
890            }
891                
892            LOG.error( message );
893            throw new IOException( message );
894        }
895        
896        if ( upName != null )
897        {
898            out.writeBoolean( true );
899            out.writeUTF( upName );
900        }
901        else
902        {
903            out.writeBoolean( false);
904        }
905        
906        if ( upType != null )
907        {
908            out.writeBoolean( true );
909            out.writeUTF( upType );
910        }
911        else
912        {
913            out.writeBoolean( false);
914        }
915        
916        if ( normType != null )
917        {
918            out.writeBoolean( true );
919            out.writeUTF( normType );
920        }
921        else
922        {
923            out.writeBoolean( false);
924        }
925        
926        boolean isHR = normValue.isHumanReadable();
927        
928        out.writeBoolean( isHR );
929        
930        upValue.writeExternal( out );
931        normValue.writeExternal( out );
932        
933        // Write the hashCode
934        out.writeInt( h );
935        
936        out.flush();
937    }
938    
939    
940    /**
941     * We read back the data to create a new ATAV. The structure 
942     * read is exposed in the {@link Ava#writeExternal(ObjectOutput)}
943     * method
944     * 
945     * @see Externalizable#readExternal(ObjectInput)
946     * 
947     * @throws IOException If the Ava can't b written to the stream
948     * @throws ClassNotFoundException If we can't deserialize an Ava from the stream
949     */
950    public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException
951    {
952        boolean hasUpName = in.readBoolean();
953        
954        if ( hasUpName )
955        {
956            upName = in.readUTF();
957        }
958        
959        boolean hasUpType = in.readBoolean();
960
961        if ( hasUpType )
962        {
963            upType = in.readUTF();
964        }
965        
966        boolean hasNormType = in.readBoolean();
967
968        if ( hasNormType )
969        {
970            normType = in.readUTF();
971        }
972        
973        if ( schemaManager != null )
974        {
975            if ( !Strings.isEmpty( upType ) )
976            {
977                attributeType = schemaManager.getAttributeType( upType );
978            }
979            else
980            {
981                attributeType = schemaManager.getAttributeType( normType );
982            }
983        }
984        
985        boolean isHR = in.readBoolean();
986
987        if ( isHR )
988        {
989            upValue = StringValue.deserialize( attributeType, in );
990            normValue = StringValue.deserialize( attributeType, in );
991        }
992        else
993        {
994            upValue = BinaryValue.deserialize( attributeType, in );
995            normValue = BinaryValue.deserialize( attributeType, in );
996        }
997
998        h = in.readInt();
999
1000        if ( schemaManager != null )
1001        {
1002            attributeType = schemaManager.getAttributeType( upType );
1003        }
1004    }
1005    
1006    
1007    /**
1008     * Tells if the Ava is schema aware or not.
1009     * 
1010     * @return true if the Ava is schema aware
1011     */
1012    public boolean isSchemaAware()
1013    {
1014        return attributeType != null;
1015    }
1016
1017
1018    /**
1019     * @return the attributeType
1020     */
1021    public AttributeType getAttributeType()
1022    {
1023        return attributeType;
1024    }
1025    
1026    
1027    /**
1028     * A String representation of an Ava, as provided by the user.
1029     *
1030     * @return A string representing an Ava
1031     */
1032    public String toString()
1033    {
1034        return upName;
1035    }
1036}