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 }