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 }