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 */
019package org.apache.directory.api.ldap.model.entry;
020
021
022import java.io.IOException;
023import java.io.ObjectInput;
024import java.io.ObjectOutput;
025import java.util.Comparator;
026
027import org.apache.directory.api.i18n.I18n;
028import org.apache.directory.api.ldap.model.exception.LdapException;
029import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException;
030import org.apache.directory.api.ldap.model.schema.AttributeType;
031import org.apache.directory.api.ldap.model.schema.MatchingRule;
032import org.apache.directory.api.ldap.model.schema.Normalizer;
033import org.apache.directory.api.util.Serialize;
034import org.apache.directory.api.util.Strings;
035import org.apache.directory.api.util.exception.NotImplementedException;
036
037
038/**
039 * A server side schema aware wrapper around a String attribute value.
040 * This value wrapper uses schema information to syntax check values,
041 * and to compare them for equality and ordering.  It caches results
042 * and invalidates them when the user provided value changes.
043 *
044 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
045 */
046public class StringValue extends AbstractValue<String>
047{
048    /** Used for serialization */
049    private static final long serialVersionUID = 2L;
050
051    /** The UTF-8 bytes for this value */
052    private byte[] bytes;
053
054
055    // -----------------------------------------------------------------------
056    // Constructors
057    // -----------------------------------------------------------------------
058    /**
059     * Creates a StringValue without an initial user provided value.
060     *
061     * @param attributeType the schema attribute type associated with this StringValue
062     */
063    public StringValue( AttributeType attributeType )
064    {
065        if ( attributeType != null )
066        {
067            // We must have a Syntax
068            if ( attributeType.getSyntax() == null )
069            {
070                throw new IllegalArgumentException( I18n.err( I18n.ERR_04445 ) );
071            }
072
073            if ( !attributeType.getSyntax().isHumanReadable() )
074            {
075                LOG.warn( "Treating a value of a binary attribute {} as a String: "
076                    + "\nthis could cause data corruption!", attributeType.getName() );
077            }
078
079            this.attributeType = attributeType;
080        }
081    }
082
083
084    /**
085     * Creates a StringValue with an initial user provided String value.
086     *
087     * @param value the value to wrap which can be null
088     */
089    public StringValue( String value )
090    {
091        this.upValue = value;
092        this.normalizedValue = value;
093        bytes = Strings.getBytesUtf8( value );
094    }
095
096
097    /**
098     * Creates a StringValue with an initial user provided String value and a normalized value.
099     *
100     * @param value the user provided value to wrap which can be null
101     * @param normalizedValue the normalized value to wrap which can be null
102     */
103    public StringValue( String value, String normalizedValue )
104    {
105        this.upValue = value;
106        this.normalizedValue = normalizedValue;
107        bytes = Strings.getBytesUtf8( normalizedValue );
108    }
109
110
111    /**
112     * Creates a schema aware StringValue with an initial user provided String value.
113     *
114     * @param attributeType the schema type associated with this StringValue
115     * @param value the value to wrap
116     * @throws LdapInvalidAttributeValueException If the added value is invalid accordingly
117     * to the schema
118     */
119    public StringValue( AttributeType attributeType, String value ) throws LdapInvalidAttributeValueException
120    {
121        this( value, value );
122        apply( attributeType );
123    }
124
125
126    /**
127     * Creates a schema aware StringValue with an initial user provided String value.
128     *
129     * @param attributeType the schema type associated with this StringValue
130     * @param value the value to wrap
131     * @param normValue The normalized form to store
132     * @throws LdapInvalidAttributeValueException If the added value is invalid accordingly
133     * to the schema
134     */
135    public StringValue( AttributeType attributeType, String value, String normValue ) throws LdapInvalidAttributeValueException
136    {
137        this( value, normValue );
138        apply( attributeType );
139    }
140
141
142    // -----------------------------------------------------------------------
143    // Value<String> Methods
144    // -----------------------------------------------------------------------
145    /**
146     * {@inheritDoc}
147     */
148    @Override
149    public String getValue()
150    {
151        // The String is immutable, we can safely return the internal
152        // object without copying it.
153        return upValue;
154    }
155
156
157    /**
158     * {@inheritDoc}
159     */
160    @Override
161    public String getNormValue()
162    {
163        return normalizedValue;
164    }
165
166
167    // -----------------------------------------------------------------------
168    // Comparable<String> Methods
169    // -----------------------------------------------------------------------
170    /**
171     * Compare the current value with a given value
172     * @param value The Value to compare to
173     * @return -1 if the current value is below the given value, 1 if it's abobe, 0 if it's equal
174     * @throws IllegalStateException on failures to extract the comparator, or the
175     * normalizers needed to perform the required comparisons based on the schema
176     */
177    @Override
178    public int compareTo( Value<String> value )
179    {
180        if ( isNull() )
181        {
182            if ( ( value == null ) || value.isNull() )
183            {
184                return 0;
185            }
186            else
187            {
188                return -1;
189            }
190        }
191        else if ( ( value == null ) || value.isNull() )
192        {
193            return 1;
194        }
195
196        if ( !( value instanceof StringValue ) )
197        {
198            String message = I18n.err( I18n.ERR_04128, toString(), value.getClass() );
199            LOG.error( message );
200            throw new NotImplementedException( message );
201        }
202
203        StringValue stringValue = ( StringValue ) value;
204
205        if ( attributeType != null )
206        {
207            if ( stringValue.getAttributeType() == null )
208            {
209                return getNormValue().compareTo( stringValue.getNormValue() );
210            }
211            else
212            {
213                if ( !attributeType.equals( stringValue.getAttributeType() ) )
214                {
215                    String message = I18n.err( I18n.ERR_04128, toString(), value.getClass() );
216                    LOG.error( message );
217                    throw new NotImplementedException( message );
218                }
219            }
220        }
221        else
222        {
223            return getNormValue().compareTo( stringValue.getNormValue() );
224        }
225
226        try
227        {
228            return getLdapComparator().compare( getNormValue(), stringValue.getNormValue() );
229        }
230        catch ( LdapException e )
231        {
232            String msg = I18n.err( I18n.ERR_04443, this, value );
233            LOG.error( msg, e );
234            throw new IllegalStateException( msg, e );
235        }
236    }
237
238
239    // -----------------------------------------------------------------------
240    // Cloneable methods
241    // -----------------------------------------------------------------------
242    /**
243     * {@inheritDoc}
244     */
245    @Override
246    public StringValue clone()
247    {
248        return ( StringValue ) super.clone();
249    }
250
251
252    // -----------------------------------------------------------------------
253    // Object Methods
254    // -----------------------------------------------------------------------
255    /**
256     * @see Object#hashCode()
257     * @return the instance's hashcode
258     */
259    @Override
260    public int hashCode()
261    {
262        if ( h == 0 )
263        {
264            // return zero if the value is null so only one null value can be
265            // stored in an attribute - the binary version does the same
266            if ( isNull() )
267            {
268                return 0;
269            }
270
271            // If the normalized value is null, will default to user provided
272            // which cannot be null at this point.
273            // If the normalized value is null, will default to user provided
274            // which cannot be null at this point.
275            String normalized = getNormValue();
276
277            if ( normalized != null )
278            {
279                h = normalized.hashCode();
280            }
281            else
282            {
283                h = 17;
284            }
285        }
286
287        return h;
288    }
289
290
291    /**
292     * Two StringValue are equals if their normalized values are equal
293     * 
294     * @see Object#equals(Object)
295     */
296    @Override
297    public boolean equals( Object obj )
298    {
299        if ( this == obj )
300        {
301            return true;
302        }
303
304        if ( !( obj instanceof StringValue ) )
305        {
306            return false;
307        }
308
309        StringValue other = ( StringValue ) obj;
310
311        // First check if we have an attrbuteType.
312        if ( attributeType != null )
313        {
314            // yes : check for the other value
315            if ( other.attributeType != null )
316            {
317                if ( attributeType.getOid().equals( other.getAttributeType().getOid() ) )
318                {
319                    // Both AttributeType have the same OID, we can assume they are 
320                    // equals. We don't check any further, because the unicity of OID
321                    // makes it unlikely that the two AT are different.
322                    // The values may be both null
323                    if ( isNull() )
324                    {
325                        return other.isNull();
326                    }
327
328                    // Shortcut : if we have an AT for both the values, check the 
329                    // already normalized values
330                    if ( upValue.equals( other.upValue ) )
331                    {
332                        return true;
333                    }
334
335                    // We have an AttributeType, we use the associated comparator
336                    try
337                    {
338                        Comparator<String> comparator = getLdapComparator();
339
340                        // Compare normalized values
341                        if ( comparator == null )
342                        {
343                            return getNormReference().equals( other.getNormReference() );
344                        }
345                        else
346                        {
347                            return comparator.compare( getNormReference(), other.getNormReference() ) == 0;
348                        }
349                    }
350                    catch ( LdapException ne )
351                    {
352                        return false;
353                    }
354                }
355                else
356                {
357                    // We can't compare two values when the two ATs are different
358                    return false;
359                }
360            }
361            else
362            {
363                // We only have one AT : we will assume that both values are for the 
364                // same AT.
365                // The values may be both null
366                if ( isNull() )
367                {
368                    return other.isNull();
369                }
370
371                // We have an AttributeType on the base value, we need to use its comparator
372                try
373                {
374                    Comparator<String> comparator = getLdapComparator();
375
376                    // Compare normalized values. We have to normalized the other value,
377                    // as it has no AT
378                    MatchingRule equality = getAttributeType().getEquality();
379
380                    if ( equality == null )
381                    {
382                        // No matching rule : compare the raw values
383                        return getNormReference().equals( other.getNormReference() );
384                    }
385
386                    Normalizer normalizer = equality.getNormalizer();
387
388                    StringValue otherValue = ( StringValue ) normalizer.normalize( other );
389
390                    if ( comparator == null )
391                    {
392                        return getNormReference().equals( otherValue.getNormReference() );
393                    }
394                    else
395                    {
396                        return comparator.compare( getNormReference(), otherValue.getNormReference() ) == 0;
397                    }
398                }
399                catch ( LdapException ne )
400                {
401                    return false;
402                }
403            }
404        }
405        else
406        {
407            // No : check for the other value
408            if ( other.attributeType != null )
409            {
410                // We only have one AT : we will assume that both values are for the 
411                // same AT.
412                // The values may be both null
413                if ( isNull() )
414                {
415                    return other.isNull();
416                }
417
418                try
419                {
420                    Comparator<String> comparator = other.getLdapComparator();
421
422                    // Compare normalized values. We have to normalized the other value,
423                    // as it has no AT
424                    MatchingRule equality = other.getAttributeType().getEquality();
425
426                    if ( equality == null )
427                    {
428                        // No matching rule : compare the raw values
429                        return getNormReference().equals( other.getNormReference() );
430                    }
431
432                    Normalizer normalizer = equality.getNormalizer();
433
434                    StringValue thisValue = ( StringValue ) normalizer.normalize( this );
435
436                    if ( comparator == null )
437                    {
438                        return thisValue.getNormReference().equals( other.getNormReference() );
439                    }
440                    else
441                    {
442                        return comparator.compare( thisValue.getNormReference(), other.getNormReference() ) == 0;
443                    }
444                }
445                catch ( LdapException ne )
446                {
447                    return false;
448                }
449            }
450            else
451            {
452                // The values may be both null
453                if ( isNull() )
454                {
455                    return other.isNull();
456                }
457
458                // Now check the normalized values
459                return getNormReference().equals( other.getNormReference() );
460            }
461        }
462    }
463
464
465    /**
466     * {@inheritDoc}
467     */
468    @Override
469    public boolean isHumanReadable()
470    {
471        return true;
472    }
473
474
475    /**
476     * @return The length of the interned value
477     */
478    @Override
479    public int length()
480    {
481        return upValue != null ? upValue.length() : 0;
482    }
483
484
485    /**
486     * Get the user provided value as a byte[].
487     * @return the user provided value as a byte[]
488     */
489    @Override
490    public byte[] getBytes()
491    {
492        return bytes;
493    }
494
495
496    /**
497     * Get the user provided value as a String.
498     *
499     * @return the user provided value as a String
500     */
501    @Override
502    public String getString()
503    {
504        return upValue != null ? upValue : "";
505    }
506
507
508    /**
509     * Deserialize a StringValue. It will return a new StringValue instance.
510     * 
511     * @param in The input stream
512     * @return A new StringValue instance
513     * @throws IOException If the stream can't be read
514     * @throws ClassNotFoundException If we can't instanciate a StringValue
515     */
516    public static StringValue deserialize( ObjectInput in ) throws IOException, ClassNotFoundException
517    {
518        StringValue value = new StringValue( ( AttributeType ) null );
519        value.readExternal( in );
520
521        return value;
522    }
523
524
525    /**
526     * Deserialize a schemaAware StringValue. It will return a new StringValue instance.
527     * 
528     * @param attributeType The AttributeType associated with the Value. Can be null
529     * @param in The input stream
530     * @return A new StringValue instance
531     * @throws IOException If the stream can't be read
532     * @throws ClassNotFoundException If we can't instanciate a StringValue
533     */
534    public static StringValue deserialize( AttributeType attributeType, ObjectInput in ) throws IOException,
535        ClassNotFoundException
536    {
537        StringValue value = new StringValue( attributeType );
538        value.readExternal( in );
539
540        return value;
541    }
542
543
544    /**
545     * {@inheritDoc}
546     */
547    @Override
548    public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException
549    {
550        // Read the STRING flag
551        boolean isHR = in.readBoolean();
552
553        if ( !isHR )
554        {
555            throw new IOException( "The serialized value is not a String value" );
556        }
557
558        // Read the user provided value, if it's not null
559        if ( in.readBoolean() )
560        {
561            upValue = in.readUTF();
562            bytes = Strings.getBytesUtf8( upValue );
563        }
564
565        // Read the isNormalized flag
566        boolean normalized = in.readBoolean();
567
568        if ( normalized )
569        {
570            // Read the normalized value, if not null
571            if ( in.readBoolean() )
572            {
573                normalizedValue = in.readUTF();
574            }
575        }
576        else
577        {
578            if ( attributeType != null )
579            {
580                try
581                {
582                    MatchingRule equality = attributeType.getEquality();
583
584                    if ( equality == null )
585                    {
586                        normalizedValue = upValue;
587                    }
588                    else
589                    {
590                        Normalizer normalizer = equality.getNormalizer();
591
592                        if ( normalizer != null )
593                        {
594                            normalizedValue = normalizer.normalize( upValue );
595                        }
596                        else
597                        {
598                            normalizedValue = upValue;
599                        }
600                    }
601                }
602                catch ( LdapException le )
603                {
604                    normalizedValue = upValue;
605                }
606            }
607            else
608            {
609                normalizedValue = upValue;
610            }
611        }
612
613        // The hashCoe
614        h = in.readInt();
615    }
616
617
618    /**
619     * Serialize the StringValue into a buffer at the given position.
620     * 
621     * @param buffer The buffer which will contain the serialized StringValue
622     * @param pos The position in the buffer for the serialized value
623     * @return The new position in the buffer
624     */
625    public int serialize( byte[] buffer, int pos )
626    {
627        // Compute the length
628        // The value type, the user provided value presence flag,
629        // the normalizedValue presence flag and the hash length.
630        int length = 1 + 1 + 1 + 4;
631
632        byte[] upValueBytes = null;
633        byte[] normalizedValueBytes = null;
634
635        if ( upValue != null )
636        {
637            upValueBytes = Strings.getBytesUtf8( upValue );
638            length += 4 + upValueBytes.length;
639        }
640
641        if ( attributeType != null )
642        {
643            if ( normalizedValue != null )
644            {
645                normalizedValueBytes = Strings.getBytesUtf8( normalizedValue );
646                length += 1 + 4 + normalizedValueBytes.length;
647            }
648            else
649            {
650                length += 1;
651            }
652        }
653
654        // Check that we will be able to store the data in the buffer
655        if ( buffer.length - pos < length )
656        {
657            throw new ArrayIndexOutOfBoundsException();
658        }
659
660        // The STRING flag
661        buffer[pos] = Serialize.TRUE;
662        pos++;
663
664        // Write the user provided value, if it's not null
665        if ( upValue != null )
666        {
667            buffer[pos++] = Serialize.TRUE;
668            pos = Serialize.serialize( upValueBytes, buffer, pos );
669        }
670        else
671        {
672            buffer[pos++] = Serialize.FALSE;
673        }
674
675        // Write the isNormalized flag
676        if ( attributeType != null )
677        {
678            // This flag is present to tell that we have a normalized value different
679            // from the upValue
680
681            buffer[pos++] = Serialize.TRUE;
682
683            // Write the normalized value, if not null
684            if ( normalizedValue != null )
685            {
686                buffer[pos++] = Serialize.TRUE;
687                pos = Serialize.serialize( normalizedValueBytes, buffer, pos );
688            }
689            else
690            {
691                buffer[pos++] = Serialize.FALSE;
692            }
693        }
694        else
695        {
696            // No normalized value
697            buffer[pos++] = Serialize.FALSE;
698        }
699
700        // Write the hashCode
701        pos = Serialize.serialize( h, buffer, pos );
702
703        return pos;
704    }
705
706
707    /**
708     * Deserialize a StringValue from a byte[], starting at a given position
709     * 
710     * @param buffer The buffer containing the StringValue
711     * @param pos The position in the buffer
712     * @return The new position
713     * @throws IOException If the serialized value is not a StringValue
714     */
715    public int deserialize( byte[] buffer, int pos ) throws IOException
716    {
717        if ( ( pos < 0 ) || ( pos >= buffer.length ) )
718        {
719            throw new ArrayIndexOutOfBoundsException();
720        }
721
722        // Read the STRING flag
723        boolean isHR = Serialize.deserializeBoolean( buffer, pos );
724        pos++;
725
726        if ( !isHR )
727        {
728            throw new IOException( "The serialized value is not a String value" );
729        }
730
731        // Read the user provided value, if it's not null
732        boolean hasWrappedValue = Serialize.deserializeBoolean( buffer, pos );
733        pos++;
734
735        if ( hasWrappedValue )
736        {
737            byte[] upValueBytes = Serialize.deserializeBytes( buffer, pos );
738            pos += 4 + upValueBytes.length;
739            upValue = Strings.utf8ToString( upValueBytes );
740        }
741
742        // Read the isNormalized flag
743        boolean hasAttributeType = Serialize.deserializeBoolean( buffer, pos );
744        pos++;
745
746        if ( hasAttributeType )
747        {
748            // Read the normalized value, if not null
749            boolean hasNormalizedValue = Serialize.deserializeBoolean( buffer, pos );
750            pos++;
751
752            if ( hasNormalizedValue )
753            {
754                byte[] normalizedValueBytes = Serialize.deserializeBytes( buffer, pos );
755                pos += 4 + normalizedValueBytes.length;
756                normalizedValue = Strings.utf8ToString( normalizedValueBytes );
757            }
758        }
759        else
760        {
761            if ( attributeType != null )
762            {
763                try
764                {
765                    MatchingRule equality = attributeType.getEquality();
766
767                    if ( equality == null )
768                    {
769                        normalizedValue = upValue;
770                    }
771                    else
772                    {
773                        Normalizer normalizer = equality.getNormalizer();
774
775                        if ( normalizer != null )
776                        {
777                            normalizedValue = normalizer.normalize( upValue );
778                        }
779                        else
780                        {
781                            normalizedValue = upValue;
782                        }
783                    }
784                }
785                catch ( LdapException le )
786                {
787                    normalizedValue = upValue;
788                }
789            }
790            else
791            {
792                normalizedValue = upValue;
793            }
794        }
795
796        // The hashCode
797        h = Serialize.deserializeInt( buffer, pos );
798        pos += 4;
799
800        return pos;
801    }
802
803
804    /**
805     * {@inheritDoc}
806     */
807    @Override
808    public void writeExternal( ObjectOutput out ) throws IOException
809    {
810        // Write a boolean for the HR flag
811        out.writeBoolean( STRING );
812
813        // Write the user provided value, if it's not null
814        if ( upValue != null )
815        {
816            out.writeBoolean( true );
817            out.writeUTF( upValue );
818        }
819        else
820        {
821            out.writeBoolean( false );
822        }
823
824        // Write the isNormalized flag
825        if ( attributeType != null )
826        {
827            // This flag is present to tell that we have a normalized value different
828            // from the upValue
829            out.writeBoolean( true );
830
831            // Write the normalized value, if not null
832            if ( normalizedValue != null )
833            {
834                out.writeBoolean( true );
835                out.writeUTF( normalizedValue );
836            }
837            else
838            {
839                out.writeBoolean( false );
840            }
841        }
842        else
843        {
844            // No normalized value
845            out.writeBoolean( false );
846        }
847
848        // Write the hashCode
849        out.writeInt( h );
850
851        // and flush the data
852        out.flush();
853    }
854
855
856    /**
857     * @see Object#toString()
858     */
859    @Override
860    public String toString()
861    {
862        return upValue == null ? "null" : upValue;
863    }
864}