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.shared.ldap.model.entry;
020
021
022import java.io.IOException;
023import java.io.ObjectInput;
024import java.io.ObjectOutput;
025
026import org.apache.directory.shared.i18n.I18n;
027import org.apache.directory.shared.ldap.model.exception.LdapException;
028import org.apache.directory.shared.ldap.model.exception.LdapInvalidAttributeValueException;
029import org.apache.directory.shared.ldap.model.schema.AttributeType;
030import org.apache.directory.shared.ldap.model.schema.LdapComparator;
031import org.apache.directory.shared.ldap.model.schema.Normalizer;
032import org.apache.directory.shared.util.Strings;
033import org.apache.directory.shared.util.exception.NotImplementedException;
034
035
036/**
037 * A server side schema aware wrapper around a String attribute value.
038 * This value wrapper uses schema information to syntax check values,
039 * and to compare them for equality and ordering.  It caches results
040 * and invalidates them when the wrapped value changes.
041 *
042 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
043 */
044public class StringValue extends AbstractValue<String>
045{
046    /** Used for serialization */
047    private static final long serialVersionUID = 2L;
048    
049    // -----------------------------------------------------------------------
050    // Constructors
051    // -----------------------------------------------------------------------
052    /**
053     * Creates a StringValue without an initial wrapped value.
054     *
055     * @param attributeType the schema attribute type associated with this StringValue
056     */
057    /* No protection*/ StringValue( AttributeType attributeType )
058    {
059        if ( attributeType != null )
060        {
061            // We must have a Syntax
062            if ( attributeType.getSyntax() == null )
063            {
064                throw new IllegalArgumentException( I18n.err( I18n.ERR_04445 ) );
065            }
066    
067            if ( ! attributeType.getSyntax().isHumanReadable() )
068            {
069                LOG.warn( "Treating a value of a binary attribute {} as a String: "
070                    + "\nthis could cause data corruption!", attributeType.getName() );
071            }
072    
073            this.attributeType = attributeType;
074        }
075    }
076
077
078    /**
079     * Creates a StringValue with an initial wrapped String value.
080     *
081     * @param value the value to wrap which can be null
082     */
083    public StringValue( String value )
084    {
085        this.wrappedValue = value;
086        this.normalizedValue = value;
087    }
088
089
090    /**
091     * Creates a schema aware StringValue with an initial wrapped String value.
092     *
093     * @param attributeType the schema type associated with this StringValue
094     * @param value the value to wrap
095     * @throws LdapInvalidAttributeValueException If the added value is invalid accordingly 
096     * to the schema
097     */
098    public StringValue( AttributeType attributeType, String value ) throws LdapInvalidAttributeValueException
099    {
100        this( value );
101        apply( attributeType );
102    }
103
104
105    // -----------------------------------------------------------------------
106    // Value<String> Methods
107    // -----------------------------------------------------------------------
108    /**
109     * {@inheritDoc}
110     */
111    public String getValue()
112    {
113        // The String is immutable, we can safely return the internal
114        // object without copying it.
115        return wrappedValue;
116    }
117    
118    
119    /**
120     * {@inheritDoc}
121     */
122    public String getNormValue()
123    {
124        return normalizedValue;
125    }
126    
127    
128    // -----------------------------------------------------------------------
129    // Comparable<String> Methods
130    // -----------------------------------------------------------------------
131    /**
132     * @see ServerValue#compareTo(Value)
133     * @throws IllegalStateException on failures to extract the comparator, or the
134     * normalizers needed to perform the required comparisons based on the schema
135     */
136    public int compareTo( Value<String> value )
137    {
138        if ( isNull() )
139        {
140            if ( ( value == null ) || value.isNull() )
141            {
142                return 0;
143            }
144            else
145            {
146                return -1;
147            }
148        }
149        else if ( ( value == null ) || value.isNull() )
150        {
151            return 1;
152        }
153
154        if ( !( value instanceof StringValue ) )
155        {
156            String message = I18n.err( I18n.ERR_04128, toString(), value.getClass() );
157            LOG.error( message );
158            throw new NotImplementedException( message );
159        }
160        
161        StringValue stringValue = ( StringValue ) value;
162        
163        if ( attributeType != null )
164        {
165            if ( stringValue.getAttributeType() == null )
166            {
167                return getNormValue().compareTo( stringValue.getNormValue() );
168            }
169            else
170            {
171                if ( !attributeType.equals( stringValue.getAttributeType() ) )
172                {
173                    String message = I18n.err( I18n.ERR_04128, toString(), value.getClass() );
174                    LOG.error( message );
175                    throw new NotImplementedException( message );
176                }
177            }
178        }
179        else 
180        {
181            return getNormValue().compareTo( stringValue.getNormValue() );
182        }
183            
184        try
185        {
186            return getLdapComparator().compare( getNormValue(), stringValue.getNormValue() );
187        }
188        catch ( LdapException e )
189        {
190            String msg = I18n.err( I18n.ERR_04443, this, value );
191            LOG.error( msg, e );
192            throw new IllegalStateException( msg, e );
193        }
194    }
195
196
197    // -----------------------------------------------------------------------
198    // Cloneable methods
199    // -----------------------------------------------------------------------
200    /**
201     * {@inheritDoc}
202     */
203    public StringValue clone()
204    {
205        return (StringValue)super.clone();
206    }
207
208
209    // -----------------------------------------------------------------------
210    // Object Methods
211    // -----------------------------------------------------------------------
212    /**
213     * @see Object#hashCode()
214     * @return the instance's hashcode 
215     */
216    public int hashCode()
217    {
218        if ( h == 0 )
219        {
220            // return zero if the value is null so only one null value can be
221            // stored in an attribute - the binary version does the same 
222            if ( isNull() )
223            {
224                return 0;
225            }
226    
227            // If the normalized value is null, will default to wrapped
228            // which cannot be null at this point.
229            // If the normalized value is null, will default to wrapped
230            // which cannot be null at this point.
231            String normalized = getNormValue();
232            
233            if ( normalized != null )
234            {
235                h = normalized.hashCode();
236            }
237            else
238            {
239                h = 17;
240            }
241        }
242        
243        return h;
244    }
245
246
247    /**
248     * Two StringValue are equals if their normalized values are equal
249     * 
250     * @see Object#equals(Object)
251     */
252    public boolean equals( Object obj )
253    {
254        if ( this == obj )
255        {
256            return true;
257        }
258
259        if ( ! ( obj instanceof StringValue ) )
260        {
261            return false;
262        }
263
264        StringValue other = ( StringValue ) obj;
265        
266        if ( this.isNull() )
267        {
268            return other.isNull();
269        }
270       
271        // First check the upValue. If they are equal, the Values are equal
272        if ( wrappedValue == other.wrappedValue )
273        {
274            return true;
275        }
276        else if ( wrappedValue != null )
277        {
278            if ( wrappedValue.equals( other.wrappedValue ) )
279            {
280                return true;
281            }
282        }
283
284        // If we have an attributeType, it must be equal
285        // We should also use the comparator if we have an AT
286        if ( attributeType != null )
287        {
288            if ( other.attributeType != null )
289            {
290                if ( !attributeType.equals( other.attributeType ) )
291                {
292                    return false;
293                }
294            }
295            else
296            {
297                return this.getNormValue().equals( other.getNormValue() );
298            }
299        }
300        else if ( other.attributeType != null )
301        {
302            return this.getNormValue().equals( other.getNormValue() );
303        }
304
305        // Shortcut : compare the values without normalization
306        // If they are equal, we may avoid a normalization.
307        // Note : if two values are equal, then their normalized
308        // value are equal too if their attributeType are equal. 
309        if ( getReference().equals( other.getReference() ) )
310        {
311            return true;
312        }
313
314        if ( attributeType != null )
315        {
316            try
317            {
318                LdapComparator<String> comparator = getLdapComparator();
319
320                // Compare normalized values
321                if ( comparator == null )
322                {
323                    return getNormValue().equals( other.getNormValue() );
324                }
325                else
326                {
327                    if ( isSchemaAware() )
328                    {
329                        return comparator.compare( getNormValue(), other.getNormValue() ) == 0;
330                    }
331                    else
332                    {
333                        Normalizer normalizer = attributeType.getEquality().getNormalizer();
334                        return comparator.compare( normalizer.normalize( getValue() ), normalizer.normalize( other.getValue() ) ) == 0;
335                    }
336                }
337            }
338            catch ( LdapException ne )
339            {
340                return false;
341            }
342        }
343        else
344        {
345            return this.getNormValue().equals( other.getNormValue() );
346        }
347    }
348    
349    
350    /**
351     * {@inheritDoc}
352     */
353    public boolean isHumanReadable()
354    {
355        return true;
356    }
357
358    
359    /**
360     * @return The length of the interned value
361     */
362    public int length()
363    {
364        return wrappedValue != null ? wrappedValue.length() : 0;
365    }
366    
367    
368    /**
369     * Get the wrapped value as a byte[].
370     * @return the wrapped value as a byte[]
371     */
372    public byte[] getBytes()
373    {
374        return Strings.getBytesUtf8( wrappedValue );
375    }
376    
377    
378    /**
379     * Get the wrapped value as a String.
380     *
381     * @return the wrapped value as a String
382     */
383    public String getString()
384    {
385        return wrappedValue != null ? wrappedValue : "";
386    }
387    
388    
389    /**
390     * Deserialize a StringValue. It will return a new StringValue instance.
391     * 
392     * @param in The input stream
393     * @return A new StringValue instance
394     * @throws IOException If the stream can't be read
395     * @throws ClassNotFoundException If we can't instanciate a StringValue
396     */
397    public static StringValue deserialize( ObjectInput in ) throws IOException, ClassNotFoundException
398    {
399        StringValue value = new StringValue( (AttributeType)null );
400        value.readExternal( in );
401
402        return value;
403    }
404    
405    
406    /**
407     * Deserialize a schemaAware StringValue. It will return a new StringValue instance.
408     * 
409     * @param attributeType The AttributeType associated with the Value. Can be null
410     * @param in The input stream
411     * @return A new StringValue instance
412     * @throws IOException If the stream can't be read
413     * @throws ClassNotFoundException If we can't instanciate a StringValue
414     */
415    public static StringValue deserialize( AttributeType attributeType, ObjectInput in ) throws IOException, ClassNotFoundException
416    {
417        StringValue value = new StringValue( attributeType );
418        value.readExternal( in );
419
420        return value;
421    }
422    
423    
424    /**
425     * {@inheritDoc}
426     */
427    public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException
428    {
429        // Read the STRING flag
430        boolean isHR = in.readBoolean();
431        
432        if ( ! isHR )
433        {
434            throw new IOException( "The serialized value is not a String value" );
435        }
436        
437        // Read the wrapped value, if it's not null
438        if ( in.readBoolean() )
439        {
440            wrappedValue = in.readUTF();
441        }
442        
443        // Read the isNormalized flag
444        boolean normalized = in.readBoolean();
445        
446        if ( normalized ) 
447        {
448            // Read the normalized value, if not null
449            if ( in.readBoolean() )
450            {
451                normalizedValue = in.readUTF();
452            }
453        }
454        else
455        {
456            normalizedValue = wrappedValue;
457        }
458        
459        // The hashCoe
460        h = in.readInt();
461    }
462
463    
464    /**
465     * {@inheritDoc}
466     */
467    public void writeExternal( ObjectOutput out ) throws IOException
468    {
469        // Write a boolean for the HR flag
470        out.writeBoolean( STRING );
471        
472        // Write the wrapped value, if it's not null
473        if ( wrappedValue != null )
474        {
475            out.writeBoolean( true );
476            out.writeUTF( wrappedValue );
477        }
478        else
479        {
480            out.writeBoolean( false );
481        }
482        
483        // Write the isNormalized flag
484        if ( attributeType != null )
485        {
486            // This flag is present to tell that we have a normalized value different 
487            // from the upValue
488            out.writeBoolean( true );
489            
490            // Write the normalized value, if not null
491            if ( normalizedValue != null )
492            {
493                out.writeBoolean( true );
494                out.writeUTF( normalizedValue );
495            }
496            else
497            {
498                out.writeBoolean( false );
499            }
500        }
501        else
502        {
503            // No normalized value
504            out.writeBoolean( false );
505        }
506        
507        // Write the hashCode
508        out.writeInt( h );
509        
510        // and flush the data
511        out.flush();
512    }
513
514    
515    /**
516     * @see Object#toString()
517     */
518    public String toString()
519    {
520        return wrappedValue == null ? "null": wrappedValue;
521    }
522}