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