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.client;
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 javax.naming.NamingException;
028    
029    import org.apache.directory.shared.i18n.I18n;
030    import org.apache.directory.shared.ldap.NotImplementedException;
031    import org.apache.directory.shared.ldap.entry.AbstractValue;
032    import org.apache.directory.shared.ldap.entry.Value;
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 ClientStringValue extends AbstractValue<String>
049    {
050        /** Used for serialization */
051        private static final long serialVersionUID = 2L;
052        
053        
054        /** logger for reporting errors that might not be handled properly upstream */
055        private static final Logger LOG = LoggerFactory.getLogger( ClientStringValue.class );
056    
057        
058        // -----------------------------------------------------------------------
059        // Constructors
060        // -----------------------------------------------------------------------
061        /**
062         * Creates a ServerStringValue without an initial wrapped value.
063         */
064        public ClientStringValue()
065        {
066            normalized = false;
067            valid = null;
068        }
069    
070    
071        /**
072         * Creates a ServerStringValue with an initial wrapped String value.
073         *
074         * @param wrapped the value to wrap which can be null
075         */
076        public ClientStringValue( String wrapped )
077        {
078            this.wrapped = wrapped;
079            normalized = false;
080            valid = null;
081        }
082    
083    
084        // -----------------------------------------------------------------------
085        // Value<String> Methods
086        // -----------------------------------------------------------------------
087        /**
088         * Get a copy of the stored value.
089         *
090         * @return A copy of the stored value.
091         */
092        public String getCopy()
093        {
094            // The String is immutable, we can safely return the internal
095            // object without copying it.
096            return wrapped;
097        }
098        
099        
100        /**
101         * Sets the wrapped String value.  Has the side effect of setting the
102         * normalizedValue and the valid flags to null if the wrapped value is
103         * different than what is already set.  These cached values must be
104         * recomputed to be correct with different values.
105         *
106         * @see ServerValue#set(Object)
107         */
108        public final void set( String wrapped )
109        {
110            // Why should we invalidate the normalized value if it's we're setting the
111            // wrapper to it's current value?
112            if ( !StringTools.isEmpty( wrapped ) && wrapped.equals( getString() ) )
113            {
114                return;
115            }
116    
117            normalizedValue = null;
118            normalized = false;
119            valid = null;
120            this.wrapped = wrapped;
121        }
122    
123    
124        /**
125         * Gets the normalized (canonical) representation for the wrapped string.
126         * If the wrapped String is null, null is returned, otherwise the normalized
127         * form is returned.  If the normalizedValue is null, then this method
128         * will attempt to generate it from the wrapped value: repeated calls to
129         * this method do not unnecessarily normalize the wrapped value.  Only changes
130         * to the wrapped value result in attempts to normalize the wrapped value.
131         *
132         * @return gets the normalized value
133         */
134        public String getNormalizedValue()
135        {
136            if ( isNull() )
137            {
138                return null;
139            }
140    
141            if ( normalizedValue == null )
142            {
143                return wrapped;
144            }
145    
146            return normalizedValue;
147        }
148    
149    
150        /**
151         * Gets a copy of the the normalized (canonical) representation 
152         * for the wrapped value.
153         *
154         * @return gets a copy of the normalized value
155         */
156        public String getNormalizedValueCopy()
157        {
158            return getNormalizedValue();
159        }
160    
161    
162        /**
163         * Normalize the value. For a client String value, applies the given normalizer.
164         * 
165         * It supposes that the client has access to the schema in order to select the
166         * appropriate normalizer.
167         * 
168         * @param Normalizer The normalizer to apply to the value
169         * @exception NamingException If the value cannot be normalized
170         */
171        public final void normalize( Normalizer normalizer ) throws NamingException
172        {
173            if ( normalizer != null )
174            {
175                normalizedValue = (String)normalizer.normalize( wrapped );
176                normalized = true;
177            }
178        }
179    
180        
181        // -----------------------------------------------------------------------
182        // Comparable<String> Methods
183        // -----------------------------------------------------------------------
184        /**
185         * @see ServerValue#compareTo(ServerValue)
186         * @throws IllegalStateException on failures to extract the comparator, or the
187         * normalizers needed to perform the required comparisons based on the schema
188         */
189        public int compareTo( Value<String> value )
190        {
191            if ( isNull() )
192            {
193                if ( ( value == null ) || value.isNull() )
194                {
195                    return 0;
196                }
197                else
198                {
199                    return -1;
200                }
201            }
202            else if ( ( value == null ) || value.isNull() )
203            {
204                return 1;
205            }
206    
207            if ( value instanceof ClientStringValue )
208            {
209                ClientStringValue stringValue = ( ClientStringValue ) value;
210                
211                return getNormalizedValue().compareTo( stringValue.getNormalizedValue() );
212            }
213            else 
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    
222        // -----------------------------------------------------------------------
223        // Cloneable methods
224        // -----------------------------------------------------------------------
225        /**
226         * Get a clone of the Client Value
227         * 
228         * @return a copy of the current value
229         */
230        public ClientStringValue clone()
231        {
232            return (ClientStringValue)super.clone();
233        }
234    
235    
236        // -----------------------------------------------------------------------
237        // Object Methods
238        // -----------------------------------------------------------------------
239        /**
240         * @see Object#hashCode()
241         * @return the instance's hashcode 
242         */
243        public int hashCode()
244        {
245            // return zero if the value is null so only one null value can be
246            // stored in an attribute - the binary version does the same 
247            if ( isNull() )
248            {
249                return 0;
250            }
251    
252            // If the normalized value is null, will default to wrapped
253            // which cannot be null at this point.
254            return getNormalizedValue().hashCode();
255        }
256    
257    
258        /**
259         * @see Object#equals(Object)
260         * 
261         * Two ClientStringValue are equals if their normalized values are equal
262         */
263        public boolean equals( Object obj )
264        {
265            if ( this == obj )
266            {
267                return true;
268            }
269    
270            if ( ! ( obj instanceof ClientStringValue ) )
271            {
272                return false;
273            }
274    
275            ClientStringValue other = ( ClientStringValue ) obj;
276            
277            if ( this.isNull() )
278            {
279                return other.isNull();
280            }
281            
282            // Test the normalized values
283            return this.getNormalizedValue().equals( other.getNormalizedValue() );
284        }
285        
286        
287        /**
288         * Tells if the current value is Binary or String
289         * 
290         * @return <code>true</code> if the value is Binary, <code>false</code> otherwise
291         */
292        public boolean isBinary()
293        {
294            return false;
295        }
296        
297        
298        /**
299         * @return The length of the interned value
300         */
301        public int length()
302        {
303            return wrapped != null ? wrapped.length() : 0;
304        }
305        
306        
307        /**
308         * Get the wrapped value as a byte[].
309         * @return the wrapped value as a byte[]
310         */
311        public byte[] getBytes()
312        {
313            return StringTools.getBytesUtf8( wrapped );
314        }
315        
316        
317        /**
318         * Get the wrapped value as a String.
319         *
320         * @return the wrapped value as a String
321         */
322        public String getString()
323        {
324            return wrapped != null ? wrapped : "";
325        }
326        
327        
328        /**
329         * @see Externalizable#readExternal(ObjectInput)
330         */
331        public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException
332        {
333            // Read the wrapped value, if it's not null
334            if ( in.readBoolean() )
335            {
336                wrapped = in.readUTF();
337            }
338            
339            // Read the isNormalized flag
340            normalized = in.readBoolean();
341            
342            if ( normalized )
343            {
344                // Read the normalized value, if not null
345                if ( in.readBoolean() )
346                {
347                    normalizedValue = in.readUTF();
348                }
349            }
350        }
351    
352        
353        /**
354         * @see Externalizable#writeExternal(ObjectOutput)
355         */
356        public void writeExternal( ObjectOutput out ) throws IOException
357        {
358            // Write the wrapped value, if it's not null
359            if ( wrapped != null )
360            {
361                out.writeBoolean( true );
362                out.writeUTF( wrapped );
363            }
364            else
365            {
366                out.writeBoolean( false );
367            }
368            
369            // Write the isNormalized flag
370            if ( normalized )
371            {
372                out.writeBoolean( true );
373                
374                // Write the normalized value, if not null
375                if ( normalizedValue != null )
376                {
377                    out.writeBoolean( true );
378                    out.writeUTF( normalizedValue );
379                }
380                else
381                {
382                    out.writeBoolean( false );
383                }
384            }
385            else
386            {
387                out.writeBoolean( false );
388            }
389            
390            // and flush the data
391            out.flush();
392        }
393        
394        
395        /**
396         * @see Object#toString()
397         */
398        public String toString()
399        {
400            return wrapped == null ? "null": wrapped;
401        }
402    }