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     */
020    package org.apache.directory.shared.ldap.name;
021    
022    
023    import java.io.Externalizable;
024    import java.io.IOException;
025    import java.io.ObjectInput;
026    import java.io.ObjectOutput;
027    import java.util.Arrays;
028    
029    import org.apache.directory.shared.i18n.I18n;
030    import org.apache.directory.shared.ldap.entry.BinaryValue;
031    import org.apache.directory.shared.ldap.entry.StringValue;
032    import org.apache.directory.shared.ldap.entry.Value;
033    import org.apache.directory.shared.ldap.exception.LdapInvalidDnException;
034    import org.apache.directory.shared.ldap.message.ResultCodeEnum;
035    import org.apache.directory.shared.ldap.util.StringTools;
036    import org.slf4j.Logger;
037    import org.slf4j.LoggerFactory;
038    
039    
040    /**
041     * A Attribute Type And Value, which is the basis of all RDN. It contains a
042     * type, and a value. The type must not be case sensitive. Superfluous leading
043     * and trailing spaces MUST have been trimmed before. The value MUST be in UTF8
044     * format, according to RFC 2253. If the type is in OID form, then the value
045     * must be a hexadecimal string prefixed by a '#' character. Otherwise, the
046     * string must respect the RC 2253 grammar. No further normalization will be
047     * done, because we don't have any knowledge of the Schema definition in the
048     * parser.
049     *
050     * We will also keep a User Provided form of the atav (Attribute Type And Value),
051     * called upName.
052     *
053     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
054     * @version $Rev: 928945 $, $Date: 2010-03-30 01:59:49 +0200 (Tue, 30 Mar 2010) $
055     */
056    public class AVA implements Cloneable, Comparable, Externalizable
057    {
058        /**
059         * Declares the Serial Version Uid.
060         *
061         * @see <a
062         *      href="http://c2.com/cgi/wiki?AlwaysDeclareSerialVersionUid">Always
063         *      Declare Serial Version Uid</a>
064         */
065        private static final long serialVersionUID = 1L;
066    
067        /** The LoggerFactory used by this class */
068        private static Logger LOG = LoggerFactory.getLogger( AVA.class );
069    
070        /** The normalized Name type */
071        private String normType;
072    
073        /** The user provided Name type */
074        private String upType;
075    
076        /** The name value. It can be a String or a byte array */
077        private Value<?> normValue;
078    
079        /** The name user provided value. It can be a String or a byte array */
080        private Value<?> upValue;
081    
082        /** The user provided AVA */
083        private String upName;
084    
085        /** The starting position of this atav in the given string from which
086         * we have extracted the upName */
087        private int start;
088    
089        /** The length of this atav upName */
090        private int length;
091    
092        /** Two values used for comparizon */
093        private static final boolean CASE_SENSITIVE = true;
094    
095        private static final boolean CASE_INSENSITIVE = false;
096    
097    
098        /**
099         * Construct an empty AVA
100         */
101        public AVA()
102        {
103            normType = null;
104            upType = null;
105            normValue = null;
106            upValue = null;
107            upName = "";
108            start = -1;
109            length = 0;
110        }
111    
112        
113        /**
114         * Construct an AVA. The type and value are normalized :
115         * <li> the type is trimmed and lowercased </li>
116         * <li> the value is trimmed </li>
117         * <p>
118         * Note that the upValue should <b>not</b> be null or empty, or resolved
119         * to an empty string after having trimmed it. 
120         *
121         * @param upType The Usrr Provided type
122         * @param normType The normalized type
123         * @param upValue The User Provided value
124         * @param normValue The normalized value
125         */
126        public AVA( String upType, String normType, String upValue, String normValue ) throws LdapInvalidDnException
127        {
128            this( upType, normType, new StringValue( upValue ), new StringValue( normValue ) );
129        }
130    
131    
132    
133        
134        /**
135         * Construct an AVA. The type and value are normalized :
136         * <li> the type is trimmed and lowercased </li>
137         * <li> the value is trimmed </li>
138         * <p>
139         * Note that the upValue should <b>not</b> be null or empty, or resolved
140         * to an empty string after having trimmed it. 
141         *
142         * @param upType The Usrr Provided type
143         * @param normType The normalized type
144         * @param upValue The User Provided value
145         * @param normValue The normalized value
146         */
147        public AVA( String upType, String normType, byte[] upValue, byte[] normValue ) throws LdapInvalidDnException
148        {
149            this( upType, normType, new BinaryValue( upValue ), new BinaryValue( normValue ) );
150        }
151    
152    
153        /**
154         * Construct an AVA. The type and value are normalized :
155         * <li> the type is trimmed and lowercased </li>
156         * <li> the value is trimmed </li>
157         * <p>
158         * Note that the upValue should <b>not</b> be null or empty, or resolved
159         * to an empty string after having trimmed it. 
160         *
161         * @param upType The Usrr Provided type
162         * @param normType The normalized type
163         * @param upValue The User Provided value
164         * @param normValue The normalized value
165         */
166        public AVA( String upType, String normType, Value<?> upValue, Value<?> normValue ) throws LdapInvalidDnException
167        {
168            String upTypeTrimmed = StringTools.trim( upType );
169            String normTypeTrimmed = StringTools.trim( normType );
170            
171            if ( StringTools.isEmpty( upTypeTrimmed ) )
172            {
173                if ( StringTools.isEmpty( normTypeTrimmed ) )
174                {
175                    String message =  I18n.err( I18n.ERR_04188 );
176                    LOG.error( message );
177                    throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, message );
178                }
179                else
180                {
181                    // In this case, we will use the normType instead
182                    this.normType = StringTools.lowerCaseAscii( normTypeTrimmed );
183                    this.upType = normType;
184                }
185            }
186            else if ( StringTools.isEmpty( normTypeTrimmed ) )
187            {
188                // In this case, we will use the upType instead
189                this.normType = StringTools.lowerCaseAscii( upTypeTrimmed );
190                this.upType = upType;
191            }
192            else
193            {
194                this.normType = StringTools.lowerCaseAscii( normTypeTrimmed );
195                this.upType = upType;
196                
197            }
198                
199            this.normValue = normValue;
200            this.upValue = upValue;
201            
202            upName = this.upType + '=' + ( this.upValue == null ? "" : this.upValue.getString() );
203            start = 0;
204            length = upName.length();
205        }
206    
207    
208        /**
209         * Construct an AVA. The type and value are normalized :
210         * <li> the type is trimmed and lowercased </li>
211         * <li> the value is trimmed </li>
212         * <p>
213         * Note that the upValue should <b>not</b> be null or empty, or resolved
214         * to an empty string after having trimmed it. 
215         *
216         * @param upType The User Provided type
217         * @param normType The normalized type
218         * @param upValue The User Provided value
219         * @param normValue The normalized value
220         * @param upName The User Provided name (may be escaped)
221         */
222        public AVA( String upType, String normType, Value<?> upValue, Value<?> normValue, String upName )
223            throws LdapInvalidDnException
224        {
225            String upTypeTrimmed = StringTools.trim( upType );
226            String normTypeTrimmed = StringTools.trim( normType );
227    
228            if ( StringTools.isEmpty( upTypeTrimmed ) )
229            {
230                if ( StringTools.isEmpty( normTypeTrimmed ) )
231                {
232                    String message = I18n.err( I18n.ERR_04188 );
233                    LOG.error( message );
234                    throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, message );
235                }
236                else
237                {
238                    // In this case, we will use the normType instead
239                    this.normType = StringTools.lowerCaseAscii( normTypeTrimmed );
240                    this.upType = normType;
241                }
242            }
243            else if ( StringTools.isEmpty( normTypeTrimmed ) )
244            {
245                // In this case, we will use the upType instead
246                this.normType = StringTools.lowerCaseAscii( upTypeTrimmed );
247                this.upType = upType;
248            }
249            else
250            {
251                this.normType = StringTools.lowerCaseAscii( normTypeTrimmed );
252                this.upType = upType;
253    
254            }
255    
256            this.normValue = normValue;
257            this.upValue = upValue;
258    
259            this.upName = upName;
260            start = 0;
261            length = upName.length();
262        }
263    
264    
265        /**
266         * Get the normalized type of a AVA
267         *
268         * @return The normalized type
269         */
270        public String getNormType()
271        {
272            return normType;
273        }
274    
275        /**
276         * Get the user provided type of a AVA
277         *
278         * @return The user provided type
279         */
280        public String getUpType()
281        {
282            return upType;
283        }
284    
285    
286        /**
287         * Store a new type
288         *
289         * @param upType The AVA User Provided type
290         * @param type The AVA type
291         * 
292         * @throws LdapInvalidDnException if the type or upType are empty or null.
293         * If the upName is invalid.
294         */
295        public void setType( String upType, String type ) throws LdapInvalidDnException
296        {
297            if ( StringTools.isEmpty( type ) || StringTools.isEmpty( type.trim() ) )
298            {
299                String message = I18n.err( I18n.ERR_04188 );
300                LOG.error( message );
301                throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, message );
302            }
303            
304            if ( StringTools.isEmpty( upType ) || StringTools.isEmpty( upType.trim() ) )
305            {
306                String message = I18n.err( I18n.ERR_04189 );
307                LOG.error( message );
308                throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, message );
309            }
310            
311            int equalPosition = upName.indexOf( '=' );
312            
313            if ( equalPosition <= 1 )
314            {
315                String message = I18n.err( I18n.ERR_04190 ); 
316                LOG.error( message );
317                throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, message );
318            }
319    
320            normType = type.trim().toLowerCase();
321            this.upType = upType;
322            upName = upType + upName.substring( equalPosition );
323            start = -1;
324            length = upName.length();
325        }
326    
327    
328        /**
329         * Store the type, after having trimmed and lowercased it.
330         *
331         * @param type The AVA type
332         */
333        public void setTypeNormalized( String type ) throws LdapInvalidDnException
334        {
335            if ( StringTools.isEmpty( type ) || StringTools.isEmpty( type.trim() ) )
336            {
337                LOG.error( I18n.err( I18n.ERR_04191 ) );
338                throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, I18n.err( I18n.ERR_04191 ) );
339            }
340    
341            normType = type.trim().toLowerCase();
342            upType = type;
343            upName = type + upName.substring( upName.indexOf( '=' ) );
344            start = -1;
345            length = upName.length();
346        }
347    
348    
349        /**
350         * Get the Value of a AVA
351         *
352         * @return The value
353         */
354        public Value<?> getNormValue()
355        {
356            return normValue;
357        }
358    
359        /**
360         * Get the User Provided Value of a AVA
361         *
362         * @return The value
363         */
364        public Value<?> getUpValue()
365        {
366            return upValue;
367        }
368    
369        /**
370         * Get the normalized Name of a AVA
371         *
372         * @return The name
373         */
374        public String getNormName()
375        {
376            return normalize();
377        }
378    
379    
380        /**
381         * Store the value of a AVA.
382         *
383         * @param value The user provided value of the AVA
384         * @param normValue The normalized value
385         */
386        public void setValue( Value<?> upValue, Value<?> normValue )
387        {
388            this.normValue = normValue;
389            this.upValue = upValue;
390            upName = upName.substring( 0, upName.indexOf( '=' ) + 1 ) + upValue;
391            start = -1;
392            length = upName.length();
393        }
394    
395    
396        /**
397         * Get the upName length
398         *
399         * @return the upName length
400         */
401        public int getLength()
402        {
403            return length;
404        }
405    
406    
407        /**
408         * get the position in the original upName where this atav starts.
409         *
410         * @return The starting position of this atav
411         */
412        public int getStart()
413        {
414            return start;
415        }
416    
417    
418        /**
419         * Get the user provided form of this attribute type and value
420         *
421         * @return The user provided form of this atav
422         */
423        public String getUpName()
424        {
425            return upName;
426        }
427    
428    
429        /**
430         * Store the value of a AVA, after having trimmed it.
431         *
432         * @param value The value of the AVA
433         */
434        public void setValueNormalized( String value )
435        {
436            String newValue = StringTools.trim( value );
437    
438            if ( StringTools.isEmpty( newValue ) )
439            {
440                this.normValue = new StringValue( "" );
441            }
442            else
443            {
444                this.normValue = new StringValue( newValue );
445            }
446    
447            upName = upName.substring( 0, upName.indexOf( '=' ) + 1 ) + value;
448            start = -1;
449            length = upName.length();
450        }
451    
452    
453        /**
454         * Implements the cloning.
455         *
456         * @return a clone of this object
457         */
458        public Object clone()
459        {
460            try
461            {
462                return super.clone();
463            }
464            catch ( CloneNotSupportedException cnse )
465            {
466                throw new Error( "Assertion failure" );
467            }
468        }
469    
470    
471        /**
472         * Compares two NameComponents. They are equals if : 
473         * - types are equals, case insensitive, 
474         * - values are equals, case sensitive
475         *
476         * @param object
477         * @return 0 if both NC are equals, otherwise a positive value if the
478         *         original NC is superior to the second one, a negative value if
479         *         the second NC is superior.
480         */
481        public int compareTo( Object object )
482        {
483            if ( object instanceof AVA )
484            {
485                AVA nc = ( AVA ) object;
486    
487                int res = compareType( normType, nc.normType );
488    
489                if ( res != 0 )
490                {
491                    return res;
492                }
493                else
494                {
495                    return compareValue( normValue, nc.normValue, CASE_SENSITIVE );
496                }
497            }
498            else
499            {
500                return 1;
501            }
502        }
503    
504    
505        /**
506         * Compares two NameComponents. They are equals if : 
507         * - types are equals, case insensitive, 
508         * - values are equals, case insensitive
509         *
510         * @param object
511         * @return 0 if both NC are equals, otherwise a positive value if the
512         *         original NC is superior to the second one, a negative value if
513         *         the second NC is superior.
514         */
515        public int compareToIgnoreCase( Object object )
516        {
517            if ( object instanceof AVA )
518            {
519                AVA nc = ( AVA ) object;
520    
521                int res = compareType( normType, nc.normType );
522    
523                if ( res != 0 )
524                {
525                    return res;
526                }
527                else
528                {
529                    return compareValue( normValue, nc.normValue, CASE_INSENSITIVE );
530                }
531            }
532            else
533            {
534                return 1;
535            }
536        }
537    
538    
539        /**
540         * Compare two types, trimed and case insensitive
541         *
542         * @param val1 First String
543         * @param val2 Second String
544         * @return true if both strings are equals or null.
545         */
546        private int compareType( String val1, String val2 )
547        {
548            if ( StringTools.isEmpty( val1 ) )
549            {
550                return StringTools.isEmpty( val2 ) ? 0 : -1;
551            }
552            else if ( StringTools.isEmpty( val2 ) )
553            {
554                return 1;
555            }
556            else
557            {
558                return ( StringTools.trim( val1 ) ).compareToIgnoreCase( StringTools.trim( val2 ) );
559            }
560        }
561    
562    
563        /**
564         * Compare two values
565         *
566         * @param val1 First value
567         * @param val2 Second value
568         * @param sensitivity A flag to define the case sensitivity
569         * @return -1 if the first value is inferior to the second one, +1 if
570         * its superior, 0 if both values are equal
571         */
572        private int compareValue( Value<?> val1, Value<?> val2, boolean sensitivity )
573        {
574            if ( !val1.isBinary() )
575            {
576                if ( !val2.isBinary() )
577                {
578                    int val = ( sensitivity == CASE_SENSITIVE ) ? 
579                        ( val1.getString() ).compareTo( val2.getString() )
580                        : ( val1.getString() ).compareToIgnoreCase( val2.getString() );
581    
582                    return ( val < 0 ? -1 : ( val > 0 ? 1 : val ) );
583                }
584                else
585                {
586                    return 1;
587                }
588            }
589            else
590            {
591                if ( val2.isBinary() )
592                {
593                    if ( Arrays.equals( val1.getBytes(), val2.getBytes() ) )
594                    {
595                        return 0;
596                    }
597                    else
598                    {
599                        return 1;
600                    }
601                }
602                else
603                {
604                    return 1;
605                }
606            }
607        }
608    
609        private static final boolean[] DN_ESCAPED_CHARS = new boolean[]
610            {
611            true,  true,  true,  true,  true,  true,  true,  true,  // 0x00 -> 0x07
612            true,  true,  true,  true,  true,  true,  true,  true,  // 0x08 -> 0x0F
613            true,  true,  true,  true,  true,  true,  true,  true,  // 0x10 -> 0x17
614            true,  true,  true,  true,  true,  true,  true,  true,  // 0x18 -> 0x1F
615            true,  false, true,  true,  false, false, false, false, // 0x20 -> 0x27 ' ', '"', '#'
616            false, false, false, true,  true,  false, false, false, // 0x28 -> 0x2F '+', ','
617            false, false, false, false, false, false, false, false, // 0x30 -> 0x37 
618            false, false, false, true,  true,  false, true,  false, // 0x38 -> 0x3F ';', '<', '>'
619            false, false, false, false, false, false, false, false, // 0x40 -> 0x47
620            false, false, false, false, false, false, false, false, // 0x48 -> 0x4F
621            false, false, false, false, false, false, false, false, // 0x50 -> 0x57
622            false, false, false, false, true,  false, false, false, // 0x58 -> 0x5F
623            false, false, false, false, false, false, false, false, // 0x60 -> 0x67
624            false, false, false, false, false, false, false, false, // 0x68 -> 0x6F
625            false, false, false, false, false, false, false, false, // 0x70 -> 0x77
626            false, false, false, false, false, false, false, false, // 0x78 -> 0x7F
627            };
628        
629        
630        public String normalizeValue()
631        {
632            // The result will be gathered in a stringBuilder
633            StringBuilder sb = new StringBuilder();
634            
635            String normalizedValue =  normValue.getString();
636            int valueLength = normalizedValue.length();
637    
638            if ( normalizedValue.length() > 0 )
639            {
640                char[] chars = normalizedValue.toCharArray();
641    
642                // Here, we have a char to escape. Start again the loop...
643                for ( int i = 0; i < valueLength; i++ )
644                {
645                    char c = chars[i];
646    
647                    if ( ( c >= 0 ) && ( c < DN_ESCAPED_CHARS.length ) && DN_ESCAPED_CHARS[ c ] ) 
648                    {
649                        // Some chars need to be escaped even if they are US ASCII
650                        // Just prefix them with a '\'
651                        // Special cases are ' ' (space), '#') which need a special
652                        // treatment.
653                        switch ( c )
654                        {
655                            case ' ' :
656                                if ( ( i == 0 ) || ( i == valueLength - 1 ) )
657                                {
658                                    sb.append( "\\ " );
659                                }
660                                else
661                                {
662                                    sb.append( ' ' );
663                                }
664        
665                                break;
666                                
667                            case '#' :
668                                if ( i == 0 )
669                                {
670                                    sb.append( "\\#" );
671                                    continue;
672                                }
673                                else
674                                {
675                                    sb.append( '#' );
676                                }
677                            
678                                break;
679    
680                            default :
681                                sb.append( '\\' ).append( c );
682                        }
683                    }
684                    else
685                    {
686                        // Standard ASCII chars are just appended
687                        sb.append( c );
688                    }
689                }
690            }
691            
692            return sb.toString();
693        }
694    
695        /**
696         * A Normalized String representation of a AVA : 
697         * - type is trimed and lowercased 
698         * - value is trimed and lowercased, and special characters
699         * are escaped if needed.
700         *
701         * @return A normalized string representing a AVA
702         */
703        public String normalize()
704        {
705            if ( !normValue.isBinary() )
706            {
707                // The result will be gathered in a stringBuilder
708                StringBuilder sb = new StringBuilder();
709                
710                // First, store the type and the '=' char
711                sb.append( normType ).append( '=' );
712                
713                String normalizedValue = normValue.getString();
714                
715                if ( normalizedValue.length() > 0 )
716                {
717                    sb.append( normalizeValue() );
718                }
719                
720                return sb.toString();
721            }
722            else
723            {
724                return normType + "=#"
725                    + StringTools.dumpHexPairs( normValue .getBytes() );
726            }
727        }
728    
729    
730        /**
731         * Gets the hashcode of this object.
732         *
733         * @see java.lang.Object#hashCode()
734         * @return The instance hash code
735         */
736        public int hashCode()
737        {
738            int result = 37;
739    
740            result = result*17 + ( normType != null ? normType.hashCode() : 0 );
741            result = result*17 + ( normValue != null ? normValue.hashCode() : 0 );
742    
743            return result;
744        }
745    
746        /**
747         * @see Object#equals(Object)
748         */
749        public boolean equals( Object obj )
750        {
751            if ( this == obj )
752            {
753                return true;
754            }
755            
756            if ( !( obj instanceof AVA ) )
757            {
758                return false;
759            }
760            
761            AVA instance = (AVA)obj;
762         
763            // Compare the type
764            if ( normType == null )
765            {
766                if ( instance.normType != null )
767                {
768                    return false;
769                }
770            }
771            else 
772            {
773                if ( !normType.equals( instance.normType ) )
774                {
775                    return false;
776                }
777            }
778                
779            // Compare the values
780            if ( normValue.isNull() )
781            {
782                return instance.normValue.isNull();
783            }
784            else
785            {
786                return normValue.equals( instance.normValue );
787            }
788        }
789    
790        
791        /**
792         * @see Externalizable#readExternal(ObjectInput)<p>
793         * 
794         * An AVA is composed of  a type and a value.
795         * The data are stored following the structure :
796         * 
797         * <li>upName</li> The User provided ATAV
798         * <li>start</li> The position of this ATAV in the DN
799         * <li>length</li> The ATAV length
800         * <li>upType</li> The user Provided Type
801         * <li>normType</li> The normalized AttributeType
802         * <li>isHR<li> Tells if the value is a String or not
803         * <p>
804         * if the value is a String :
805         * <li>upValue</li> The User Provided value.
806         * <li>value</li> The normalized value.
807         * <p>
808         * if the value is binary :
809         * <li>upValueLength</li>
810         * <li>upValue</li> The User Provided value.
811         * <li>valueLength</li>
812         * <li>value</li> The normalized value.
813         */
814        public void writeExternal( ObjectOutput out ) throws IOException
815        {
816            if ( StringTools.isEmpty( upName ) || 
817                 StringTools.isEmpty( upType ) ||
818                 StringTools.isEmpty( normType ) ||
819                 ( start < 0 ) ||
820                 ( length < 2 ) ||             // At least a type and '='
821                 ( upValue.isNull() ) ||
822                 ( normValue.isNull() ) )
823            {
824                String message = "Cannot serialize an wrong ATAV, ";
825                
826                if ( StringTools.isEmpty( upName ) )
827                {
828                    message += "the upName should not be null or empty";
829                }
830                else if ( StringTools.isEmpty( upType ) )
831                {
832                    message += "the upType should not be null or empty";
833                }
834                else if ( StringTools.isEmpty( normType ) )
835                {
836                    message += "the normType should not be null or empty";
837                }
838                else if ( start < 0 )
839                {
840                    message += "the start should not be < 0";
841                }
842                else if ( length < 2 )
843                {
844                    message += "the length should not be < 2";
845                }
846                else if ( upValue.isNull() )
847                {
848                    message += "the upValue should not be null";
849                }
850                else if ( normValue.isNull() )
851                {
852                    message += "the value should not be null";
853                }
854                    
855                LOG.error( message );
856                throw new IOException( message );
857            }
858            
859            out.writeUTF( upName );
860            out.writeInt( start );
861            out.writeInt( length );
862            out.writeUTF( upType );
863            out.writeUTF( normType );
864            
865            boolean isHR = !normValue.isBinary();
866            
867            out.writeBoolean( isHR );
868            
869            if ( isHR )
870            {
871                out.writeUTF( upValue.getString() );
872                out.writeUTF( normValue.getString() );
873            }
874            else
875            {
876                out.writeInt( upValue.length() );
877                out.write( upValue.getBytes() );
878                out.writeInt( normValue.length() );
879                out.write( normValue.getBytes() );
880            }
881        }
882        
883        
884        /**
885         * @see Externalizable#readExternal(ObjectInput)
886         * 
887         * We read back the data to create a new ATAV. The structure 
888         * read is exposed in the {@link AVA#writeExternal(ObjectOutput)} 
889         * method<p>
890         */
891        public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException
892        {
893            upName = in.readUTF();
894            start = in.readInt();
895            length = in.readInt();
896            upType = in.readUTF();
897            normType = in.readUTF();
898            
899            boolean isHR = in.readBoolean();
900            
901            if ( isHR )
902            {
903                upValue = new StringValue( in.readUTF() );
904                normValue = new StringValue( in.readUTF() );
905            }
906            else
907            {
908                int upValueLength = in.readInt();
909                byte[] upValueBytes = new byte[upValueLength];
910                in.readFully( upValueBytes );
911                upValue = new BinaryValue( upValueBytes );
912    
913                int valueLength = in.readInt();
914                byte[] normValueBytes = new byte[valueLength];
915                in.readFully( normValueBytes );
916                normValue = new BinaryValue( normValueBytes );
917            }
918        }
919        
920        
921        /**
922         * A String representation of a AVA.
923         *
924         * @return A string representing a AVA
925         */
926        public String toString()
927        {
928            StringBuffer sb = new StringBuffer();
929    
930            if ( StringTools.isEmpty( normType ) || StringTools.isEmpty( normType.trim() ) )
931            {
932                return "";
933            }
934    
935            sb.append( normType ).append( "=" );
936    
937            if ( normValue != null )
938            {
939                sb.append( normValue.getString() );
940            }
941    
942            return sb.toString();
943        }
944    }