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.api.ldap.model.entry;
021
022
023import java.io.Externalizable;
024import java.io.IOException;
025import java.io.ObjectInput;
026import java.io.ObjectOutput;
027import java.util.Arrays;
028
029import org.apache.directory.api.i18n.I18n;
030import org.apache.directory.api.ldap.model.exception.LdapException;
031import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException;
032import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
033import org.apache.directory.api.ldap.model.schema.AttributeType;
034import org.apache.directory.api.ldap.model.schema.LdapComparator;
035import org.apache.directory.api.ldap.model.schema.LdapSyntax;
036import org.apache.directory.api.ldap.model.schema.MatchingRule;
037import org.apache.directory.api.ldap.model.schema.Normalizer;
038import org.apache.directory.api.ldap.model.schema.SyntaxChecker;
039import org.apache.directory.api.ldap.model.schema.comparators.StringComparator;
040import org.apache.directory.api.ldap.model.schema.normalizers.NoOpNormalizer;
041import org.apache.directory.api.util.Serialize;
042import org.apache.directory.api.util.Strings;
043import org.slf4j.Logger;
044import org.slf4j.LoggerFactory;
045
046
047/**
048 * A Class for wrapping attribute values stored into an Entry Attribute, or a AVA.
049 * 
050 * We keep the value as byte[] unless we need to convert them to a String (if we have
051 * a HR Value).
052 * 
053 * The serialized Value will be stored as :
054 * 
055 * <pre>
056 *  +---------+
057 *  | boolean | isHR flag
058 *  +---------+
059 *  | boolean | TRUE if the value is not null, FALSE otherwise
060 *  +---------+
061 * [|   int   |]  If the previous flag is TRUE, the length of the value
062 * [+---------+]
063 * [| byte[]  |] The value itself
064 * [+---------+]
065 *  | boolean | TRUE if we have a prepared String
066 *  +---------+
067 * [| String  |] The prepared String if we have it
068 * [+---------+]
069 * </pre>
070 *
071 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
072 */
073public class Value implements Cloneable, Externalizable, Comparable<Value>
074{
075    /** Used for serialization */
076    private static final long serialVersionUID = 2L;
077
078    /** logger for reporting errors that might not be handled properly upstream */
079    private static final Logger LOG = LoggerFactory.getLogger( Value.class );
080
081    /** reference to the attributeType associated with the value */
082    private transient AttributeType attributeType;
083
084    /** the User Provided value if it's a String */
085    private String upValue;
086
087    /** the prepared representation of the user provided value if it's a String */
088    private String normValue;
089
090    /** The computed hashcode. We don't want to compute it each time the hashcode() method is called */
091    private volatile int h;
092
093    /** The UTF-8 bytes for this value (we use the UP value) */
094    private byte[] bytes;
095
096    /** Two flags used to tell if the value is HR or not in serialization */
097    private boolean isHR = true;
098    
099    /** A default comparator if we don't have an EQUALITY MR */
100    private static StringComparator stringComparator = new StringComparator( null );
101    
102    // -----------------------------------------------------------------------
103    // Constructors
104    // -----------------------------------------------------------------------
105    /**
106     * Creates a Value with an initial user provided String value.
107     *
108     * @param upValue the value to wrap. It can be null
109     */
110    public Value( String upValue )
111    {
112        this.upValue = upValue;
113        
114        // We can't normalize the value, we store it as is
115        normValue = upValue;
116        
117        if ( upValue != null )
118        {
119            bytes = Strings.getBytesUtf8( upValue );
120        }
121        
122        hashCode();
123    }
124    
125    
126    /**
127     * Creates a Value with an initial user provided binary value.
128     *
129     * @param value the binary value to wrap which may be null, or a zero length byte array
130     */
131    public Value( byte[] value )
132    {
133        if ( value != null )
134        {
135            bytes = new byte[value.length];
136            System.arraycopy( value, 0, bytes, 0, value.length );
137        }
138        else
139        {
140            bytes = null;
141        }
142        
143        isHR = false;
144
145        hashCode();
146    }
147
148
149    /**
150     * Creates a schema aware binary Value with an initial value.
151     *
152     * @param attributeType the schema type associated with this Value
153     * @param upValue the value to wrap
154     * @throws LdapInvalidAttributeValueException If the added value is invalid accordingly
155     * to the schema
156     */
157    public Value( AttributeType attributeType, byte[] upValue ) throws LdapInvalidAttributeValueException
158    {
159        init( attributeType );
160        
161        if ( upValue != null )
162        {
163            bytes = new byte[upValue.length];
164            System.arraycopy( upValue, 0, bytes, 0, upValue.length );
165
166            if ( isHR )
167            {
168                this.upValue = Strings.utf8ToString( upValue );
169            }
170        }
171        else
172        {
173            bytes = null;
174        }
175        
176        if ( ( attributeType != null ) && !attributeType.isRelaxed() )
177        {
178            // Check the value
179            SyntaxChecker syntaxChecker = attributeType.getSyntax().getSyntaxChecker();
180
181            if ( syntaxChecker != null )
182            {
183                if ( !syntaxChecker.isValidSyntax( bytes ) )
184                {
185                    throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, 
186                        I18n.err( I18n.ERR_13246_INVALID_VALUE_PER_SYNTAX ) );
187                }
188            }
189            else
190            {
191                // We should always have a SyntaxChecker
192                throw new IllegalArgumentException( I18n.err( I18n.ERR_13219_NULL_SYNTAX_CHECKER, normValue ) );
193            }
194        }
195
196        hashCode();
197    }
198    
199    
200    private void init( AttributeType attributeType )
201    {
202        if ( attributeType != null )
203        {
204            if ( attributeType.getSyntax() == null )
205            {
206                // Some broken LDAP servers do not have proper syntax definitions, default to HR
207                // Log this on trace level only. Otherwise we get logs full of errors when working
208                // with AD and similar not-really-LDAP-compliant servers.
209                if ( LOG.isTraceEnabled() )
210                {
211                    LOG.trace( I18n.err( I18n.ERR_13225_NO_SYNTAX ) );
212                }
213                
214                isHR = true;
215            }
216            else
217            {
218                isHR = attributeType.getSyntax().isHumanReadable();
219            }
220        }
221        else
222        {
223            if ( LOG.isWarnEnabled() )
224            {
225                LOG.warn( I18n.msg( I18n.MSG_13202_AT_IS_NULL ) );
226            }
227        }
228        
229        this.attributeType = attributeType;
230    }
231
232
233    /**
234     * Creates a schema aware binary Value with an initial value. This method is
235     * only to be used by deserializers.
236     *
237     * @param attributeType the schema type associated with this Value
238     */
239    /* Package protected*/ Value( AttributeType attributeType )
240    {
241        init( attributeType );
242    }
243    
244    
245    /**
246     * Creates a schema aware StringValue with an initial user provided String value.
247     *
248     * @param attributeType the schema type associated with this StringValue
249     * @param upValue the value to wrap
250     * @throws LdapInvalidAttributeValueException If the added value is invalid accordingly
251     * to the schema
252     */
253    public Value( AttributeType attributeType, String upValue ) throws LdapInvalidAttributeValueException
254    {
255        init( attributeType );
256        this.upValue = upValue;
257        
258        if ( upValue != null )
259        {
260            bytes = Strings.getBytesUtf8( upValue );
261        }
262        else
263        {
264            bytes = null;
265        }
266        
267        try
268        {
269            computeNormValue();
270        }
271        catch ( LdapException le )
272        {
273            LOG.error( le.getMessage() );
274            throw new IllegalArgumentException( I18n.err( I18n.ERR_13247_INVALID_VALUE_CANT_NORMALIZE ) );
275        }
276        
277        if ( !attributeType.isRelaxed() )
278        {
279            // Check the value
280            LdapSyntax syntax = attributeType.getSyntax();
281            
282            if ( ( syntax != null ) && ( syntax.getSyntaxChecker() != null ) ) 
283            {
284                if ( !attributeType.getSyntax().getSyntaxChecker().isValidSyntax( upValue ) )
285                {
286                    throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, 
287                        I18n.err( I18n.ERR_13246_INVALID_VALUE_PER_SYNTAX ) );
288                }
289            }
290            else
291            {
292                // We should always have a SyntaxChecker
293                throw new IllegalArgumentException( I18n.err( I18n.ERR_13219_NULL_SYNTAX_CHECKER, normValue ) );
294            }
295        }
296        
297        hashCode();
298    }
299    
300    
301    /**
302     * Creates a schema aware StringValue with an initial user provided String value and 
303     * its normalized Value
304     *
305     * @param attributeType the schema type associated with this StringValue
306     * @param upValue the value to wrap
307     * @param normValue the normalized value to wrap
308     * @throws LdapInvalidAttributeValueException If the added value is invalid accordingly
309     * to the schema
310     */
311    public Value( AttributeType attributeType, String upValue, String normValue ) throws LdapInvalidAttributeValueException
312    {
313        init( attributeType );
314        this.upValue = upValue;
315        
316        if ( upValue != null )
317        {
318            bytes = Strings.getBytesUtf8( upValue );
319        }
320        else
321        {
322            bytes = null;
323        }
324        
325        this.normValue = normValue;
326        
327        if ( !attributeType.isRelaxed() )
328        {
329            // Check the value
330            if ( attributeType.getSyntax().getSyntaxChecker() != null )
331            {
332                if ( !attributeType.getSyntax().getSyntaxChecker().isValidSyntax( upValue ) )
333                {
334                    throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, 
335                        I18n.err( I18n.ERR_13246_INVALID_VALUE_PER_SYNTAX ) );
336                }
337            }
338            else
339            {
340                // We should always have a SyntaxChecker
341                throw new IllegalArgumentException( I18n.err( I18n.ERR_13219_NULL_SYNTAX_CHECKER, normValue ) );
342            }
343        }
344        
345        hashCode();
346    }
347
348
349    /**
350     * Creates a Value from an existing Value with an AttributeType
351     *
352     * @param attributeType the schema attribute type associated with this StringValue
353     * @param value the original Value
354     * @throws LdapInvalidAttributeValueException If the value is invalid
355     */
356    public Value( AttributeType attributeType, Value value ) throws LdapInvalidAttributeValueException
357    {
358        init( attributeType );
359        
360        if ( isHR )
361        {
362            this.upValue = value.upValue;
363        }
364
365        try
366        {
367            computeNormValue();
368        }
369        catch ( LdapException le )
370        {
371            LOG.error( le.getMessage() );
372            throw new IllegalArgumentException( I18n.err( I18n.ERR_13247_INVALID_VALUE_CANT_NORMALIZE ) );
373        }
374        
375        // Check the normValue
376        if ( !attributeType.isRelaxed() )
377        {
378            // Check the value
379            if ( attributeType.getSyntax().getSyntaxChecker() != null )
380            {
381                attributeType.getSyntax().getSyntaxChecker().isValidSyntax( value.normValue );
382            }
383            else
384            {
385                // We should always have a SyntaxChecker
386                throw new IllegalArgumentException( I18n.err( I18n.ERR_13219_NULL_SYNTAX_CHECKER, normValue ) );
387            }
388        }
389            
390        // We have to copy the byte[], they are just referenced by super.clone()
391        if ( value.bytes != null )
392        {
393            bytes = new byte[value.bytes.length];
394            System.arraycopy( value.bytes, 0, bytes, 0, value.bytes.length );
395        }
396
397        hashCode();
398    }
399
400    
401    /**
402     * Create a Value with an AttributeType. It will not contain anything and will only be used by
403     * the deserializer.
404     * 
405     * @param attributeType The ATttributeType to use
406     * @return An instance of value.
407     */
408    public static Value createValue( AttributeType attributeType )
409    {
410        return new Value( attributeType );
411    }
412    
413
414    /**
415     * Clone a Value
416     * 
417     * @return A cloned value
418     */
419    @Override
420    public Value clone()
421    {
422        try
423        {
424            Value clone = ( Value ) super.clone();
425            
426            if ( isHR )
427            {
428                return clone;
429            }
430            else
431            {
432                // We have to copy the byte[], they are just referenced by suoer.clone()
433                if ( bytes != null )
434                {
435                    clone.bytes = new byte[bytes.length];
436                    System.arraycopy( bytes, 0, clone.bytes, 0, bytes.length );
437                }
438            }
439            
440            return clone;
441        }
442        catch ( CloneNotSupportedException cnse )
443        {
444            // Do nothing
445            return null;
446        }
447    }
448
449
450    /**
451     * Check if the contained value is null or not
452     * 
453     * @return <code>true</code> if the inner value is null.
454     */
455    public boolean isNull()
456    {
457        if ( isHR )
458        {
459            return upValue == null;
460        }
461        else
462        {
463            return bytes == null;
464        }
465    }
466
467
468    /**
469     * Get the associated AttributeType
470     * 
471     * @return The AttributeType
472     */
473    public AttributeType getAttributeType()
474    {
475        return attributeType;
476    }
477
478
479    /**
480     * Check if the value is stored into an instance of the given
481     * AttributeType, or one of its ascendant.
482     * 
483     * For instance, if the Value is associated with a CommonName,
484     * checking for Name will match.
485     * 
486     * @param attributeType The AttributeType we are looking at
487     * @return <code>true</code> if the value is associated with the given
488     * attributeType or one of its ascendant
489     */
490    public boolean isInstanceOf( AttributeType attributeType )
491    {
492        return ( attributeType != null )
493            && ( this.attributeType.equals( attributeType ) || this.attributeType.isDescendantOf( attributeType ) );
494    }
495
496
497    /**
498     * Get the User Provided value. If the value is Human Readable, it will return
499     * the stored String, otherwise it will returns a String based on the bytes - which may be 
500     * invalid if the value is a pure binary -.
501     *
502     * @return The user provided value
503     */
504    public String getString()
505    {
506        if ( isHR )
507        {
508            return upValue;
509        }
510        else
511        {
512            return Strings.utf8ToString( bytes );
513        }
514    }
515
516
517    /**
518     * Compute the normalized value
519     * 
520     * @throws LdapException If we were'nt able to normalize the value
521     */
522    private void computeNormValue() throws LdapException
523    {
524        if ( upValue == null )
525        {
526            return;
527        }
528        
529        Normalizer normalizer;
530        
531        // We should have a Equality MatchingRule
532        MatchingRule equality = attributeType.getEquality();
533        
534        if ( equality == null )
535        {
536            // Let's try with the Substring MatchingRule
537            MatchingRule subString = attributeType.getSubstring();
538            
539            if ( subString == null )
540            {
541                // last chance : ordering matching rule
542                MatchingRule ordering = attributeType.getOrdering();
543                
544                if ( ordering == null )
545                {
546                    // Ok, no luck. Use a NoOp normalizer
547                    normalizer = new NoOpNormalizer();
548                }
549                else
550                {
551                    normalizer = ordering.getNormalizer();
552                }
553            }
554            else
555            {
556                normalizer = subString.getNormalizer();
557            }
558        }
559        else
560        {
561            normalizer = equality.getNormalizer();
562        }
563        
564        if ( normalizer == null )
565        {
566            throw new IllegalArgumentException( I18n.err( I18n.ERR_13220_NO_NORMALIZER ) );
567        }
568
569        // Now, normalize the upValue
570        normValue = normalizer.normalize( upValue );
571    }
572    
573    
574    /**
575     * @return The normalized value
576     */
577    public String getNormalized()
578    {
579        return normValue;
580    }
581
582
583    /**
584     * Get the wrapped value as a byte[], if and only if the Value is binary,
585     * otherwise returns null.
586     *
587     * @return the wrapped value as a byte[]
588     */
589    public byte[] getBytes()
590    {
591        if ( bytes == null )
592        {
593            return null;
594        }
595        
596        if ( bytes.length == 0 )
597        {
598            return Strings.EMPTY_BYTES;
599        }
600        
601        byte[] copy = new byte[bytes.length];
602        System.arraycopy( bytes, 0, copy, 0, bytes.length );
603        
604        return copy;
605    }
606
607
608    /**
609     * Tells if the value is schema aware or not.
610     *
611     * @return <code>true</code> if the value is sxhema aware
612     */
613    public boolean isSchemaAware()
614    {
615        return attributeType != null;
616    }
617
618
619    /**
620     * Uses the syntaxChecker associated with the attributeType to check if the
621     * value is valid.
622     * 
623     * @param syntaxChecker the SyntaxChecker to use to validate the value
624     * @return <code>true</code> if the value is valid
625     * @exception LdapInvalidAttributeValueException if the value cannot be validated
626     */
627    public final boolean isValid( SyntaxChecker syntaxChecker ) throws LdapInvalidAttributeValueException
628    {
629        if ( syntaxChecker == null )
630        {
631            String message = I18n.err( I18n.ERR_13219_NULL_SYNTAX_CHECKER, toString() );
632            LOG.error( message );
633            throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, message );
634        }
635
636        // No attributeType, or it's in relaxed mode
637        if ( isHR )
638        {
639            // We need to prepare the String in this case
640            return syntaxChecker.isValidSyntax( getString() );
641        }
642        else
643        {
644            return syntaxChecker.isValidSyntax( bytes );
645        }
646    }
647
648
649    /**
650     * Tells if the current value is Human Readable
651     * 
652     * @return <code>true</code> if the value is a String, <code>false</code> otherwise
653     */
654    public boolean isHumanReadable()
655    {
656        return isHR;
657    }
658
659
660    /**
661     * @return The length of the interned value
662     */
663    public int length()
664    {
665        if ( isHR )
666        {
667            return upValue != null ? upValue.length() : 0;
668        }
669        else
670        {
671            return bytes != null ? bytes.length : 0;
672        }
673    }
674    
675    
676    /**
677     * Gets a comparator using getMatchingRule() to resolve the matching
678     * that the comparator is extracted from.
679     *
680     * @return a comparator associated with the attributeType or null if one cannot be found
681     */
682    private LdapComparator<?> getLdapComparator()
683    {
684        if ( attributeType != null )
685        {
686            MatchingRule mr = attributeType.getEquality();
687
688            if ( mr != null )
689            {
690                return mr.getLdapComparator();
691            }
692        }
693
694        return null;
695    }
696
697
698    /**
699     * Serialize the Value into a buffer at the given position.
700     * 
701     * @param buffer The buffer which will contain the serialized StringValue
702     * @param pos The position in the buffer for the serialized value
703     * @return The new position in the buffer
704     */
705    public int serialize( byte[] buffer, int pos )
706    {
707        // Compute the length : the isHR flag first, the value and prepared value presence flags
708        int length = 1;
709        byte[] preparedBytes = null;
710
711        if ( isHR )
712        { 
713            if ( upValue != null )
714            {
715                // The presence flag, the length and the value
716                length += 1 + 4 + bytes.length;
717            }
718
719            if ( normValue != null )
720            {
721                // The presence flag, the length and the value
722                preparedBytes = Strings.getBytesUtf8( normValue );
723                length += 1 + 4 + preparedBytes.length;
724            }
725        }
726        else
727        {
728            if ( bytes != null )
729            {
730                length = 1 + 1 + 4 + bytes.length;
731            }
732            else
733            {
734                length = 1 + 1;
735            }
736        }
737
738        // Check that we will be able to store the data in the buffer
739        if ( buffer.length - pos < length )
740        {
741            throw new ArrayIndexOutOfBoundsException();
742        }
743
744        if ( isHR )
745        {
746            buffer[pos++] = Serialize.TRUE;
747
748            // Write the user provided value, if not null
749            if ( bytes != null )
750            {
751                buffer[pos++] = Serialize.TRUE;
752                pos = Serialize.serialize( bytes, buffer, pos );
753            }
754            else
755            {
756                buffer[pos++] = Serialize.FALSE;
757            }
758    
759            // Write the prepared value, if not null
760            if ( normValue != null )
761            {
762                buffer[pos++] = Serialize.TRUE;
763                pos = Serialize.serialize( preparedBytes, buffer, pos );
764            }
765            else
766            {
767                buffer[pos++] = Serialize.FALSE;
768            }
769        }
770        else
771        {
772            buffer[pos++] = Serialize.FALSE;
773
774            if ( bytes != null )
775            {
776                buffer[pos++] = Serialize.TRUE;
777                pos = Serialize.serialize( bytes, buffer, pos );
778            }
779            else
780            {
781                buffer[pos++] = Serialize.FALSE;
782            }
783        }
784
785        return pos;
786    }
787    
788    
789    /**
790     * Deserialize a Value. It will return a new Value instance.
791     * 
792     * @param in The input stream
793     * @return A new Value instance
794     * @throws IOException If the stream can't be read
795     * @throws ClassNotFoundException If we can't instanciate a Value
796     */
797    public static Value deserialize( ObjectInput in ) throws IOException, ClassNotFoundException
798    {
799        Value value = new Value( ( AttributeType ) null );
800        value.readExternal( in );
801
802        return value;
803    }
804
805    
806    /**
807     * Deserialize a Value. It will return a new Value instance.
808     * 
809     * @param attributeType The AttributeType associated with the Value. Can be null
810     * @param in The input stream
811     * @return A new Value instance
812     * @throws IOException If the stream can't be read
813     * @throws ClassNotFoundException If we can't instanciate a Value
814     */
815    public static Value deserialize( AttributeType attributeType, ObjectInput in ) throws IOException, ClassNotFoundException
816    {
817        Value value = new Value( attributeType );
818        value.readExternal( in );
819
820        return value;
821    }
822
823
824    /**
825     * Deserialize a StringValue from a byte[], starting at a given position
826     * 
827     * @param buffer The buffer containing the StringValue
828     * @param pos The position in the buffer
829     * @return The new position
830     * @throws IOException If the serialized value is not a StringValue
831     * @throws LdapInvalidAttributeValueException If the value is invalid
832     */
833    public int deserialize( byte[] buffer, int pos ) throws IOException, LdapInvalidAttributeValueException
834    {
835        if ( ( pos < 0 ) || ( pos >= buffer.length ) )
836        {
837            throw new ArrayIndexOutOfBoundsException();
838        }
839
840        // Read the isHR flag
841        isHR = Serialize.deserializeBoolean( buffer, pos );
842        pos++;
843
844        if ( isHR )
845        {
846            // Read the user provided value, if it's not null
847            boolean hasValue = Serialize.deserializeBoolean( buffer, pos );
848            pos++;
849    
850            if ( hasValue )
851            {
852                bytes = Serialize.deserializeBytes( buffer, pos );
853                pos += 4 + bytes.length;
854
855                upValue = Strings.utf8ToString( bytes );
856            }
857
858            // Read the prepared value, if not null
859            boolean hasPreparedValue = Serialize.deserializeBoolean( buffer, pos );
860            pos++;
861    
862            if ( hasPreparedValue )
863            {
864                byte[] preparedBytes = Serialize.deserializeBytes( buffer, pos );
865                pos += 4 + preparedBytes.length;
866                normValue = Strings.utf8ToString( preparedBytes );
867            }
868        }
869        else
870        {
871            // Read the user provided value, if it's not null
872            boolean hasBytes = Serialize.deserializeBoolean( buffer, pos );
873            pos++;
874    
875            if ( hasBytes )
876            {
877                bytes = Serialize.deserializeBytes( buffer, pos );
878                pos += 4 + bytes.length;
879            }
880
881        }
882        
883        if ( attributeType != null )
884        {
885            try
886            {
887                computeNormValue();
888            }
889            catch ( LdapException le )
890            {
891                throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, le.getMessage() );
892            }
893        }
894        
895        hashCode();
896
897        return pos;
898    }
899    
900    
901    /**
902     * {@inheritDoc}
903     */
904    @Override
905    public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException
906    {
907        // Read the isHR flag
908        isHR = in.readBoolean();
909
910        if ( isHR )
911        {
912            // Read the value if any
913            if ( in.readBoolean() )
914            {
915                int length = in.readInt();
916                bytes = new byte[length];
917                
918                if ( length != 0 )
919                {
920                    in.readFully( bytes );
921                }
922    
923                upValue = Strings.utf8ToString( bytes );
924            }
925    
926            // Read the prepared String if any
927            if ( in.readBoolean() )
928            {
929                normValue = in.readUTF();
930            }
931        }
932        else
933        {
934            if ( in.readBoolean() )
935            {
936                int length = in.readInt();
937                bytes = new byte[length];
938                
939                if ( length != 0 )
940                {
941                    in.readFully( bytes );
942                }
943            }
944        }
945        
946        hashCode();
947    }
948
949
950    /**
951     * {@inheritDoc}
952     */
953    @Override
954    public void writeExternal( ObjectOutput out ) throws IOException
955    {
956        // Write a boolean for the HR flag
957        out.writeBoolean( isHR );
958
959        if ( isHR )
960        { 
961            // Write the value if any
962            out.writeBoolean( upValue != null );
963    
964            if ( upValue != null )
965            {
966                // Write the value
967                out.writeInt( bytes.length );
968    
969                if ( bytes.length > 0 )
970                {
971                    out.write( bytes );
972                }
973            }
974
975            // Write the prepared value if any
976            out.writeBoolean( normValue != null );
977    
978            if ( normValue != null )
979            {
980                // Write the value
981                out.writeUTF( normValue );
982            }
983        }
984        else
985        {
986            // Just write the bytes if not null
987            out.writeBoolean( bytes != null );
988
989            if ( bytes != null )
990            {
991                out.writeInt( bytes.length );
992                
993                if ( bytes.length > 0 )
994                {
995                    out.write( bytes );
996                }
997            }
998        }
999
1000        // and flush the data
1001        out.flush();
1002    }
1003
1004    
1005    /**
1006     * Compare the current value with a String.
1007     * 
1008     * @param other the String we want to compare the current value with
1009     * @return a positive value if the current value is above the provided String, a negative value
1010     * if it's below, 0 if they are equal.
1011     * @throws IllegalStateException on failures to extract the comparator, or the
1012     * normalizers needed to perform the required comparisons based on the schema
1013     */
1014    public int compareTo( String other )
1015    {
1016        if ( !isHR )
1017        {
1018            String msg = I18n.err( I18n.ERR_13224_FAILED_TO_COMPARE_NORM_VALUES, this, other );
1019            LOG.error( msg );
1020            throw new IllegalStateException( msg );
1021        }
1022        
1023        // Check if both value are null
1024        if ( bytes == null )
1025        {
1026            if ( other == null )
1027            {
1028                return 0;
1029            }
1030            else
1031            {
1032                return -1;
1033            }
1034        }
1035        else if ( other == null )
1036        {
1037            return 1;
1038        }
1039        
1040        // We have HR values. We may have an attributeType for the base Value
1041        // It actually does not matter if the second value has an attributeType
1042        // which is different
1043        try
1044        {
1045            if ( attributeType != null )
1046            {
1047                // No normalization. Use the base AttributeType to normalize
1048                // the other value
1049                String normalizedOther = attributeType.getEquality().getNormalizer().normalize( other );
1050                
1051                return normValue.compareTo( normalizedOther );
1052            }
1053            else
1054            {
1055                // No AtributeType... Compare the normValue
1056                return normValue.compareTo( other );
1057            }
1058        }
1059        catch ( LdapException le )
1060        {
1061            return -1;
1062        }
1063    }
1064
1065    
1066    /**
1067     * Compare two values. We compare the stored bytes
1068     * 
1069     * @param other the byte[] we want to compare the current value with
1070     * @return a positive value if the current value is above the provided byte[], a negative value
1071     * if it's below, 0 if they are equal.
1072     * @throws IllegalStateException on failures to extract the comparator, or the
1073     * normalizers needed to perform the required comparisons based on the schema
1074     */
1075    public int compareTo( byte[] other )
1076    {
1077        if ( isHR )
1078        {
1079            String msg = I18n.err( I18n.ERR_13224_FAILED_TO_COMPARE_NORM_VALUES, this, other );
1080            LOG.error( msg );
1081            throw new IllegalStateException( msg );
1082        }
1083        
1084        // Check if both value are null
1085        if ( bytes == null )
1086        {
1087            if ( other == null )
1088            {
1089                return 0;
1090            }
1091            else
1092            {
1093                return -1;
1094            }
1095        }
1096        else if ( other == null )
1097        {
1098            return 1;
1099        }
1100
1101        // Default : compare the bytes
1102        return Strings.compare( bytes, other );
1103    }
1104
1105    
1106    /**
1107     * Compare two values. We either compare the stored bytes, or we use the 
1108     * AttributeType Comparator, if we have an Ordered MatchingRule. 
1109     * 
1110     * @param other The other Value we want to compare the current value with
1111     * @return a positive value if the current value is above the provided value, a negative value
1112     * if it's below, 0 if they are equal.
1113     * @throws IllegalStateException on failures to extract the comparator, or the
1114     * normalizers needed to perform the required comparisons based on the schema
1115     */
1116    @Override
1117    public int compareTo( Value other )
1118    {
1119        // The two values must have the same type
1120        if ( isHR != other.isHR )
1121        {
1122            String msg = I18n.err( I18n.ERR_13224_FAILED_TO_COMPARE_NORM_VALUES, this, other );
1123            LOG.error( msg );
1124            throw new IllegalStateException( msg );
1125        }
1126        
1127        // Check if both value are null
1128        if ( bytes == null )
1129        {
1130            if ( other.bytes == null )
1131            {
1132                return 0;
1133            }
1134            else
1135            {
1136                return -1;
1137            }
1138        }
1139        else if ( other.bytes == null )
1140        {
1141            return 1;
1142        }
1143        
1144        // Ok, neither this nor the other have null values.
1145        
1146        // Shortcut when the value are not HR
1147        if ( !isHR )
1148        {
1149            return Strings.compare( bytes, other.bytes );
1150        }
1151
1152        // We have HR values. We may have an attributeType for the base Value
1153        // It actually does not matter if the second value has an attributeType
1154        // which is different
1155        try
1156        {
1157            if ( attributeType != null )
1158            {
1159                // Check if the other value has been normalized or not
1160                if ( other.attributeType == null )
1161                {
1162                    // No normalization. Use the base AttributeType to normalize
1163                    // the other value
1164                    String normalizedOther = attributeType.getEquality().getNormalizer().normalize( other.upValue );
1165                    
1166                    return normValue.compareTo( normalizedOther );
1167                }
1168                else
1169                {
1170                    return normValue.compareTo( other.normValue );
1171                }
1172            }
1173            else
1174            {
1175                if ( other.attributeType != null )
1176                {
1177                    // Normalize the current value with the other value normalizer
1178                    String normalizedThis = other.attributeType.getEquality().getNormalizer().normalize( upValue );
1179                    
1180                    return normalizedThis.compareTo( other.normValue );
1181                }
1182                else
1183                {
1184                    // No AtributeType... Compare the normValue
1185                    return normValue.compareTo( other.normValue );
1186                }
1187            }
1188        }
1189        catch ( LdapException le )
1190        {
1191            return -1;
1192        }
1193    }
1194    
1195    
1196    /**
1197     * We compare two values using their Comparator, if any. 
1198     * 
1199     * @see Object#equals(Object)
1200     */
1201    @Override
1202    public boolean equals( Object obj )
1203    {
1204        if ( this == obj )
1205        {
1206            return true;
1207        }
1208
1209        if ( obj instanceof String )
1210        {
1211            String other = ( String ) obj;
1212            
1213            if ( !isHR )
1214            {
1215                return false;
1216            }
1217            
1218            if ( attributeType == null )
1219            {
1220                if ( upValue != null )
1221                {
1222                    return upValue.equals( other );
1223                }
1224                else
1225                {
1226                    return obj == null;
1227                }
1228            }
1229            else
1230            {
1231                // Use the comparator
1232                // We have an AttributeType, we use the associated comparator
1233                try
1234                {
1235                    LdapComparator<String> comparator = ( LdapComparator<String> ) getLdapComparator();
1236                    
1237                    Normalizer normalizer = null;
1238                    
1239                    if ( attributeType.getEquality() != null )
1240                    {
1241                        normalizer = attributeType.getEquality().getNormalizer();
1242                    }
1243
1244                    if ( normalizer == null )
1245                    {
1246                        if ( comparator == null )
1247                        {
1248                            return normValue.equals( other );
1249                        }
1250                        else
1251                        {
1252                            return comparator.compare( normValue, other ) == 0;
1253                        }
1254                    }
1255                    
1256                    String thisNormValue = normValue;
1257                    String otherNormValue = normalizer.normalize( other );
1258                        
1259                    // Compare normalized values
1260                    if ( comparator == null )
1261                    {
1262                        return thisNormValue.equals( otherNormValue );
1263                    }
1264                    else
1265                    {
1266                        return comparator.compare( thisNormValue, otherNormValue ) == 0;
1267                    }
1268                }
1269                catch ( LdapException ne )
1270                {
1271                    return false;
1272                }
1273            }
1274        }
1275        
1276        if ( !( obj instanceof Value ) )
1277        {
1278            return false;
1279        }
1280
1281        Value other = ( Value ) obj;
1282
1283        // Check if the values aren't of the same type
1284        if ( isHR != other.isHR )
1285        {
1286            // Both values must be HR or not HR
1287            return false;
1288        }
1289        
1290        if ( !isHR )
1291        {
1292            // Shortcut for binary values
1293            return Arrays.equals( bytes, other.bytes );
1294        }
1295        
1296        // HR values
1297        if ( bytes == null )
1298        {
1299            return other.bytes == null;
1300        }
1301        
1302        // Special case
1303        if ( other.bytes == null )
1304        {
1305            return false;
1306        }
1307        
1308        // Not null, but empty. We try to avoid a spurious String Preparation
1309        if ( bytes.length == 0 )
1310        {
1311            return other.bytes.length == 0;
1312        }
1313        else if ( other.bytes.length == 0 )
1314        {
1315            return false;
1316        }
1317
1318        // Ok, now, let's see if we have an AttributeType at all. If both have one,
1319        // and if they aren't equal, then we get out. If one of them has an AttributeType and
1320        // not the other, we will assume that this is the AttributeType to use.
1321        MatchingRule equalityMR;
1322        
1323        if ( attributeType == null )
1324        {
1325            if ( other.attributeType != null )
1326            {
1327                // Use the Other value AT
1328                equalityMR = other.attributeType.getEquality();
1329 
1330                // We may not have an Equality MR, and in tjis case, we compare the bytes
1331                if ( equalityMR == null )
1332                {
1333                    return Arrays.equals( bytes, other.bytes );
1334                }
1335                
1336                LdapComparator<Object> ldapComparator = equalityMR.getLdapComparator();
1337                
1338                if ( ldapComparator == null )
1339                {
1340                    // This is an error !
1341                    LOG.error( I18n.err( I18n.ERR_13249_NO_COMPARATOR_FOR_AT, other.attributeType ) );
1342                    
1343                    return false;
1344                }
1345                
1346                return ldapComparator.compare( normValue, other.normValue ) == 0;
1347            }
1348            else
1349            {
1350                // Both are null. We will compare the prepared String if we have one, 
1351                // or the bytes otherwise.
1352                if ( upValue != null )
1353                {
1354                    return upValue.equals( other.upValue );
1355                }
1356                else
1357                {
1358                    return Arrays.equals( bytes, other.bytes );
1359                } 
1360            }
1361        }
1362        else 
1363        {
1364            if ( other.attributeType != null )
1365            {
1366                // Both attributeType must be equal
1367                if ( !attributeType.equals( other.attributeType ) )
1368                {
1369                    return false;
1370                }
1371                
1372                // Use the comparator
1373                // We have an AttributeType, we use the associated comparator
1374                LdapComparator<String> comparator = ( LdapComparator<String> ) getLdapComparator();
1375                
1376                if ( other.attributeType.getEquality() == null )
1377                {
1378                    // No equality ? Default to comparing using a String comparator
1379                    return stringComparator.compare( normValue, other.normValue ) == 0;
1380                }
1381                
1382                
1383                // Compare normalized values
1384                if ( comparator == null )
1385                {
1386                    return normValue.equals( other.normValue );
1387                }
1388                else
1389                {
1390                    return comparator.compare( normValue, other.normValue ) == 0;
1391                }
1392            }
1393            
1394            // No attributeType
1395            if ( normValue == null )
1396            {
1397                return other.normValue == null;
1398            }
1399            else
1400            {
1401                return normValue.equals( other.normValue );
1402            }
1403        }
1404    }
1405
1406    
1407    /**
1408     * @see Object#hashCode()
1409     * @return the instance's hashcode
1410     */
1411    @Override
1412    public int hashCode()
1413    {
1414        if ( h == 0 )
1415        {
1416            // return zero if the value is null so only one null value can be
1417            // stored in an attribute - the binary version does the same
1418            if ( isHR )
1419            {
1420                if ( normValue != null )
1421                {
1422                    h = normValue.hashCode();
1423                }
1424                else
1425                {
1426                    h = 0;
1427                }
1428            }
1429            else
1430            {
1431                h = Arrays.hashCode( bytes );
1432            }
1433        }
1434
1435        return h;
1436    }
1437
1438
1439    /**
1440     * @see Object#toString()
1441     */
1442    @Override
1443    public String toString()
1444    {
1445        if ( isHR )
1446        {
1447            return upValue == null ? "null" : upValue;
1448        }
1449        else
1450        {
1451             // Dumps binary in hex with label.
1452            if ( bytes == null )
1453            {
1454                return "null";
1455            }
1456            else if ( bytes.length > 16 )
1457            {
1458                // Just dump the first 16 bytes...
1459                byte[] copy = new byte[16];
1460
1461                System.arraycopy( bytes, 0, copy, 0, 16 );
1462
1463                return Strings.dumpBytes( copy ) + "...";
1464            }
1465            else
1466            {
1467                return Strings.dumpBytes( bytes );
1468            }
1469        }
1470    }
1471}