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    package org.apache.directory.shared.ldap.entry;
020    
021    
022    import java.io.Externalizable;
023    import java.io.IOException;
024    import java.io.ObjectInput;
025    import java.io.ObjectOutput;
026    
027    import org.apache.directory.shared.ldap.exception.LdapException;
028    
029    import org.apache.directory.shared.i18n.I18n;
030    import org.apache.directory.shared.ldap.NotImplementedException;
031    import org.apache.directory.shared.ldap.schema.AttributeType;
032    import org.apache.directory.shared.ldap.schema.LdapComparator;
033    import org.apache.directory.shared.ldap.schema.Normalizer;
034    import org.apache.directory.shared.ldap.util.StringTools;
035    import org.slf4j.Logger;
036    import org.slf4j.LoggerFactory;
037    
038    
039    /**
040     * A server side schema aware wrapper around a String attribute value.
041     * This value wrapper uses schema information to syntax check values,
042     * and to compare them for equality and ordering.  It caches results
043     * and invalidates them when the wrapped value changes.
044     *
045     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
046     * @version $Rev$, $Date$
047     */
048    public class StringValue extends AbstractValue<String>
049    {
050        /** Used for serialization */
051        private static final long serialVersionUID = 2L;
052        
053        /** logger for reporting errors that might not be handled properly upstream */
054        protected static final Logger LOG = LoggerFactory.getLogger( StringValue.class );
055    
056    
057        // -----------------------------------------------------------------------
058        // Constructors
059        // -----------------------------------------------------------------------
060        /**
061         * Creates a StringValue without an initial wrapped value.
062         */
063        public StringValue()
064        {
065            normalized = false;
066            valid = null;
067        }
068    
069    
070        /**
071         * Creates a StringValue without an initial wrapped value.
072         *
073         * @param attributeType the schema type associated with this StringValue
074         */
075        public StringValue( AttributeType attributeType )
076        {
077            if ( attributeType == null )
078            {
079                throw new IllegalArgumentException( I18n.err( I18n.ERR_04442 ) );
080            }
081    
082            if ( attributeType.getSyntax() == null )
083            {
084                throw new IllegalArgumentException( I18n.err( I18n.ERR_04445 ) );
085            }
086    
087            if ( ! attributeType.getSyntax().isHumanReadable() )
088            {
089                LOG.warn( "Treating a value of a binary attribute {} as a String: " +
090                        "\nthis could cause data corruption!", attributeType.getName() );
091            }
092    
093            this.attributeType = attributeType;
094        }
095    
096    
097        /**
098         * Creates a StringValue with an initial wrapped String value.
099         *
100         * @param value the value to wrap which can be null
101         */
102        public StringValue( String value )
103        {
104            this.wrappedValue = value;
105            normalized = false;
106            valid = null;
107        }
108    
109    
110        /**
111         * Creates a StringValue with an initial wrapped String value.
112         *
113         * @param attributeType the schema type associated with this StringValue
114         * @param wrapped the value to wrap which can be null
115         */
116        public StringValue( AttributeType attributeType, String value )
117        {
118            this( attributeType );
119            this.wrappedValue = value;
120        }
121    
122    
123        // -----------------------------------------------------------------------
124        // Value<String> Methods
125        // -----------------------------------------------------------------------
126        /**
127         * Get a copy of the stored value.
128         *
129         * @return A copy of the stored value.
130         */
131        public String get()
132        {
133            // The String is immutable, we can safely return the internal
134            // object without copying it.
135            return wrappedValue;
136        }
137        
138        
139        /**
140         * Gets the normalized (canonical) representation for the wrapped string.
141         * If the wrapped String is null, null is returned, otherwise the normalized
142         * form is returned.  If the normalizedValue is null, then this method
143         * will attempt to generate it from the wrapped value: repeated calls to
144         * this method do not unnecessarily normalize the wrapped value.  Only changes
145         * to the wrapped value result in attempts to normalize the wrapped value.
146         *
147         * @return gets the normalized value
148         */
149        public String getNormalizedValue()
150        {
151            if ( isNull() )
152            {
153                normalized = true;
154                return null;
155            }
156    
157            if ( !normalized )
158            {
159                try
160                {
161                    normalize();
162                }
163                catch ( LdapException ne )
164                {
165                    String message = "Cannot normalize the value :" + ne.getLocalizedMessage();
166                    LOG.info( message );
167                    normalized = false;
168                }
169            }
170            
171            if ( normalizedValue == null )
172            {
173                return wrappedValue;
174            }
175    
176            return normalizedValue;
177        }
178        
179        
180        /**
181         * Gets a copy of the the normalized (canonical) representation 
182         * for the wrapped value.
183         *
184         * @return gets a copy of the normalized value
185         */
186        public String getNormalizedValueCopy()
187        {
188            return getNormalizedValue();
189        }
190    
191    
192        /**
193         * Compute the normalized (canonical) representation for the wrapped string.
194         * If the wrapped String is null, the normalized form will be null too.  
195         *
196         * @throws LdapException if the value cannot be properly normalized
197         */
198        public void normalize() throws LdapException
199        {
200            // If the value is already normalized, get out.
201            if ( normalized )
202            {
203                return;
204            }
205            
206            if ( attributeType != null )
207            {
208                Normalizer normalizer = getNormalizer();
209        
210                if ( normalizer == null )
211                {
212                    normalizedValue = wrappedValue;
213                }
214                else
215                {
216                    normalizedValue = ( String ) normalizer.normalize( wrappedValue );
217                }
218        
219                normalized = true;
220            }
221        }
222    
223        
224        /**
225         * Normalize the value. For a client String value, applies the given normalizer.
226         * 
227         * It supposes that the client has access to the schema in order to select the
228         * appropriate normalizer.
229         * 
230         * @param Normalizer The normalizer to apply to the value
231         * @exception LdapException If the value cannot be normalized
232         */
233        public final void normalize( Normalizer normalizer ) throws LdapException
234        {
235            if ( normalizer != null )
236            {
237                normalizedValue = (String)normalizer.normalize( wrappedValue );
238                normalized = true;
239            }
240        }
241    
242        
243        // -----------------------------------------------------------------------
244        // Comparable<String> Methods
245        // -----------------------------------------------------------------------
246        /**
247         * @see ServerValue#compareTo(ServerValue)
248         * @throws IllegalStateException on failures to extract the comparator, or the
249         * normalizers needed to perform the required comparisons based on the schema
250         */
251        public int compareTo( Value<String> value )
252        {
253            if ( isNull() )
254            {
255                if ( ( value == null ) || value.isNull() )
256                {
257                    return 0;
258                }
259                else
260                {
261                    return -1;
262                }
263            }
264            else if ( ( value == null ) || value.isNull() )
265            {
266                return 1;
267            }
268    
269            if ( !( value instanceof StringValue ) )
270            {
271                String message = I18n.err( I18n.ERR_04128, toString(), value.getClass() );
272                LOG.error( message );
273                throw new NotImplementedException( message );
274            }
275            
276            StringValue stringValue = ( StringValue ) value;
277            
278            if ( attributeType != null )
279            {
280                if ( stringValue.getAttributeType() == null )
281                {
282                    return getNormalizedValue().compareTo( stringValue.getNormalizedValue() );
283                }
284                else
285                {
286                    if ( !attributeType.equals( stringValue.getAttributeType() ) )
287                    {
288                        String message = I18n.err( I18n.ERR_04128, toString(), value.getClass() );
289                        LOG.error( message );
290                        throw new NotImplementedException( message );
291                    }
292                }
293            }
294            else 
295            {
296                return getNormalizedValue().compareTo( stringValue.getNormalizedValue() );
297            }
298                
299            try
300            {
301                return getLdapComparator().compare( getNormalizedValue(), stringValue.getNormalizedValue() );
302            }
303            catch ( LdapException e )
304            {
305                String msg = I18n.err( I18n.ERR_04443, this, value );
306                LOG.error( msg, e );
307                throw new IllegalStateException( msg, e );
308            }
309        }
310    
311    
312        // -----------------------------------------------------------------------
313        // Cloneable methods
314        // -----------------------------------------------------------------------
315        /**
316         * Get a clone of the Client Value
317         * 
318         * @return a copy of the current value
319         */
320        public StringValue clone()
321        {
322            return (StringValue)super.clone();
323        }
324    
325    
326        // -----------------------------------------------------------------------
327        // Object Methods
328        // -----------------------------------------------------------------------
329        /**
330         * @see Object#hashCode()
331         * @return the instance's hashcode 
332         */
333        public int hashCode()
334        {
335            // return zero if the value is null so only one null value can be
336            // stored in an attribute - the binary version does the same 
337            if ( isNull() )
338            {
339                if ( attributeType != null )
340                {
341                    // return the OID hashcode if the value is null. 
342                    return attributeType.getOid().hashCode();
343                }
344                
345                return 0;
346            }
347    
348            // If the normalized value is null, will default to wrapped
349            // which cannot be null at this point.
350            // If the normalized value is null, will default to wrapped
351            // which cannot be null at this point.
352            int h = 0;
353    
354            String normalized = getNormalizedValue();
355            
356            if ( normalized != null )
357            {
358                h = normalized.hashCode();
359            }
360            else
361            {
362                h = 17;
363            }
364            
365            // Add the OID hashcode if we have an AttributeType
366            if ( attributeType != null )
367            {
368                h = h*37 + attributeType.getOid().hashCode();
369            }
370            
371            return h;
372        }
373    
374    
375        /**
376         * @see Object#equals(Object)
377         * 
378         * Two StringValue are equals if their normalized values are equal
379         */
380        public boolean equals( Object obj )
381        {
382            if ( this == obj )
383            {
384                return true;
385            }
386    
387            if ( ! ( obj instanceof StringValue ) )
388            {
389                return false;
390            }
391    
392            StringValue other = ( StringValue ) obj;
393            
394            if ( this.isNull() )
395            {
396                return other.isNull();
397            }
398           
399            // If we have an attributeType, it must be equal
400            // We should also use the comparator if we have an AT
401            if ( attributeType != null )
402            {
403                if ( other.attributeType != null )
404                {
405                    if ( !attributeType.equals( other.attributeType ) )
406                    {
407                        return false;
408                    }
409                }
410                else
411                {
412                    return this.getNormalizedValue().equals( other.getNormalizedValue() );
413                }
414            }
415            else if ( other.attributeType != null )
416            {
417                return this.getNormalizedValue().equals( other.getNormalizedValue() );
418            }
419    
420            // Shortcut : compare the values without normalization
421            // If they are equal, we may avoid a normalization.
422            // Note : if two values are equal, then their normalized
423            // value are equal too if their attributeType are equal. 
424            if ( getReference().equals( other.getReference() ) )
425            {
426                return true;
427            }
428    
429            if ( attributeType != null )
430            {
431                try
432                {
433                    LdapComparator<String> comparator = getLdapComparator();
434    
435                    // Compare normalized values
436                    if ( comparator == null )
437                    {
438                        return getNormalizedValue().equals( other.getNormalizedValue() );
439                    }
440                    else
441                    {
442                        if ( isNormalized() )
443                        {
444                            return comparator.compare( getNormalizedValue(), other.getNormalizedValue() ) == 0;
445                        }
446                        else
447                        {
448                            Normalizer normalizer = attributeType.getEquality().getNormalizer();
449                            return comparator.compare( normalizer.normalize( get() ), normalizer.normalize( other.get() ) ) == 0;
450                        }
451                    }
452                }
453                catch ( LdapException ne )
454                {
455                    return false;
456                }
457            }
458            else
459            {
460                return this.getNormalizedValue().equals( other.getNormalizedValue() );
461            }
462        }
463        
464        
465        /**
466         * Tells if the current value is Binary or String
467         * 
468         * @return <code>true</code> if the value is Binary, <code>false</code> otherwise
469         */
470        public boolean isBinary()
471        {
472            return false;
473        }
474    
475        
476        /**
477         * @return The length of the interned value
478         */
479        public int length()
480        {
481            return wrappedValue != null ? wrappedValue.length() : 0;
482        }
483        
484        
485        /**
486         * Get the wrapped value as a byte[].
487         * @return the wrapped value as a byte[]
488         */
489        public byte[] getBytes()
490        {
491            return StringTools.getBytesUtf8( wrappedValue );
492        }
493        
494        
495        /**
496         * Get the wrapped value as a String.
497         *
498         * @return the wrapped value as a String
499         */
500        public String getString()
501        {
502            return wrappedValue != null ? wrappedValue : "";
503        }
504        
505        
506        /**
507         * @see Externalizable#readExternal(ObjectInput)
508         */
509        public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException
510        {
511            // Read the wrapped value, if it's not null
512            if ( in.readBoolean() )
513            {
514                wrappedValue = in.readUTF();
515            }
516            
517            // Read the isNormalized flag
518            normalized = in.readBoolean();
519            
520            if ( normalized )
521            {
522                // Read the normalized value, if not null
523                if ( in.readBoolean() )
524                {
525                    normalizedValue = in.readUTF();
526                }
527            }
528        }
529    
530        
531        /**
532         * @see Externalizable#writeExternal(ObjectOutput)
533         */
534        public void writeExternal( ObjectOutput out ) throws IOException
535        {
536            // Write the wrapped value, if it's not null
537            if ( wrappedValue != null )
538            {
539                out.writeBoolean( true );
540                out.writeUTF( wrappedValue );
541            }
542            else
543            {
544                out.writeBoolean( false );
545            }
546            
547            // Write the isNormalized flag
548            if ( normalized )
549            {
550                out.writeBoolean( true );
551                
552                // Write the normalized value, if not null
553                if ( normalizedValue != null )
554                {
555                    out.writeBoolean( true );
556                    out.writeUTF( normalizedValue );
557                }
558                else
559                {
560                    out.writeBoolean( false );
561                }
562            }
563            else
564            {
565                out.writeBoolean( false );
566            }
567            
568            // and flush the data
569            out.flush();
570        }
571    
572        
573        /**
574         * We will write the value and the normalized value, only
575         * if the normalized value is different.
576         * 
577         * If the value is empty, a flag is written at the beginning with 
578         * the value true, otherwise, a false is written.
579         * 
580         * The data will be stored following this structure :
581         *  [empty value flag]
582         *  [UP value]
583         *  [normalized] (will be false if the value can't be normalized)
584         *  [same] (a flag set to true if the normalized value equals the UP value)
585         *  [Norm value] (the normalized value if different from the UP value)
586         *  
587         *  @param out the buffer in which we will stored the serialized form of the value
588         *  @throws IOException if we can't write into the buffer
589         */
590        public void serialize( ObjectOutput out ) throws IOException
591        {
592            if ( wrappedValue != null )
593            {
594                // write a flag indicating that the value is not null
595                out.writeBoolean( true );
596                
597                // Write the data
598                out.writeUTF( wrappedValue );
599                
600                // Normalize the data
601                try
602                {
603                    normalize();
604                    out.writeBoolean( true );
605                    
606                    if ( wrappedValue.equals( normalizedValue ) )
607                    {
608                        out.writeBoolean( true );
609                    }
610                    else
611                    {
612                        out.writeBoolean( false );
613                        out.writeUTF( normalizedValue );
614                    }
615                }
616                catch ( LdapException ne )
617                {
618                    // The value can't be normalized, we don't write the 
619                    // normalized value.
620                    normalizedValue = null;
621                    out.writeBoolean( false );
622                }
623            }
624            else
625            {
626                // Write a flag indicating that the value is null
627                out.writeBoolean( false );
628            }
629            
630            out.flush();
631        }
632    
633        
634        /**
635         * Deserialize a StringValue. 
636         *
637         * @param in the buffer containing the bytes with the serialized value
638         * @throws IOException 
639         * @throws ClassNotFoundException
640         */
641        public void deserialize( ObjectInput in ) throws IOException, ClassNotFoundException
642        {
643            // If the value is null, the flag will be set to false
644            if ( !in.readBoolean() )
645            {
646                wrappedValue = null;
647                normalizedValue = null;
648                return;
649            }
650            
651            // Read the value
652            String wrapped = in.readUTF();
653            
654            wrappedValue = wrapped;
655            
656            // Read the normalized flag
657            normalized = in.readBoolean();
658            
659            if ( normalized )
660            {
661                normalized = true;
662    
663                // Read the 'same' flag
664                if ( in.readBoolean() )
665                {
666                    normalizedValue = wrapped;
667                }
668                else
669                {
670                    // The normalized value is different. Read it
671                    normalizedValue = in.readUTF();
672                }
673            }
674        }
675    
676    
677        /**
678         * @see Object#toString()
679         */
680        public String toString()
681        {
682            return wrappedValue == null ? "null": wrappedValue;
683        }
684    }