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}