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