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.api.ldap.model.entry; 020 021 022import java.io.IOException; 023import java.io.ObjectInput; 024import java.io.ObjectOutput; 025import java.util.Comparator; 026 027import org.apache.directory.api.i18n.I18n; 028import org.apache.directory.api.ldap.model.exception.LdapException; 029import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException; 030import org.apache.directory.api.ldap.model.schema.AttributeType; 031import org.apache.directory.api.ldap.model.schema.MatchingRule; 032import org.apache.directory.api.ldap.model.schema.Normalizer; 033import org.apache.directory.api.util.Serialize; 034import org.apache.directory.api.util.Strings; 035import org.apache.directory.api.util.exception.NotImplementedException; 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 */ 046public class StringValue extends AbstractValue<String> 047{ 048 /** Used for serialization */ 049 private static final long serialVersionUID = 2L; 050 051 /** The UTF-8 bytes for this value */ 052 private byte[] bytes; 053 054 055 // ----------------------------------------------------------------------- 056 // Constructors 057 // ----------------------------------------------------------------------- 058 /** 059 * Creates a StringValue without an initial wrapped value. 060 * 061 * @param attributeType the schema attribute type associated with this StringValue 062 */ 063 public StringValue( AttributeType attributeType ) 064 { 065 if ( attributeType != null ) 066 { 067 // We must have a Syntax 068 if ( attributeType.getSyntax() == null ) 069 { 070 throw new IllegalArgumentException( I18n.err( I18n.ERR_04445 ) ); 071 } 072 073 if ( !attributeType.getSyntax().isHumanReadable() ) 074 { 075 LOG.warn( "Treating a value of a binary attribute {} as a String: " 076 + "\nthis could cause data corruption!", attributeType.getName() ); 077 } 078 079 this.attributeType = attributeType; 080 } 081 } 082 083 084 /** 085 * Creates a StringValue with an initial wrapped String value. 086 * 087 * @param value the value to wrap which can be null 088 */ 089 public StringValue( String value ) 090 { 091 this.wrappedValue = value; 092 this.normalizedValue = value; 093 bytes = Strings.getBytesUtf8( value ); 094 } 095 096 097 /** 098 * Creates a schema aware StringValue with an initial wrapped String value. 099 * 100 * @param attributeType the schema type associated with this StringValue 101 * @param value the value to wrap 102 * @throws LdapInvalidAttributeValueException If the added value is invalid accordingly 103 * to the schema 104 */ 105 public StringValue( AttributeType attributeType, String value ) throws LdapInvalidAttributeValueException 106 { 107 this( value ); 108 apply( attributeType ); 109 } 110 111 112 // ----------------------------------------------------------------------- 113 // Value<String> Methods 114 // ----------------------------------------------------------------------- 115 /** 116 * {@inheritDoc} 117 */ 118 public String getValue() 119 { 120 // The String is immutable, we can safely return the internal 121 // object without copying it. 122 return wrappedValue; 123 } 124 125 126 /** 127 * {@inheritDoc} 128 */ 129 public String getNormValue() 130 { 131 return normalizedValue; 132 } 133 134 135 // ----------------------------------------------------------------------- 136 // Comparable<String> Methods 137 // ----------------------------------------------------------------------- 138 /** 139 * @see ServerValue#compareTo(Value) 140 * @throws IllegalStateException on failures to extract the comparator, or the 141 * normalizers needed to perform the required comparisons based on the schema 142 */ 143 public int compareTo( Value<String> value ) 144 { 145 if ( isNull() ) 146 { 147 if ( ( value == null ) || value.isNull() ) 148 { 149 return 0; 150 } 151 else 152 { 153 return -1; 154 } 155 } 156 else if ( ( value == null ) || value.isNull() ) 157 { 158 return 1; 159 } 160 161 if ( !( value instanceof StringValue ) ) 162 { 163 String message = I18n.err( I18n.ERR_04128, toString(), value.getClass() ); 164 LOG.error( message ); 165 throw new NotImplementedException( message ); 166 } 167 168 StringValue stringValue = ( StringValue ) value; 169 170 if ( attributeType != null ) 171 { 172 if ( stringValue.getAttributeType() == null ) 173 { 174 return getNormValue().compareTo( stringValue.getNormValue() ); 175 } 176 else 177 { 178 if ( !attributeType.equals( stringValue.getAttributeType() ) ) 179 { 180 String message = I18n.err( I18n.ERR_04128, toString(), value.getClass() ); 181 LOG.error( message ); 182 throw new NotImplementedException( message ); 183 } 184 } 185 } 186 else 187 { 188 return getNormValue().compareTo( stringValue.getNormValue() ); 189 } 190 191 try 192 { 193 return getLdapComparator().compare( getNormValue(), stringValue.getNormValue() ); 194 } 195 catch ( LdapException e ) 196 { 197 String msg = I18n.err( I18n.ERR_04443, this, value ); 198 LOG.error( msg, e ); 199 throw new IllegalStateException( msg, e ); 200 } 201 } 202 203 204 // ----------------------------------------------------------------------- 205 // Cloneable methods 206 // ----------------------------------------------------------------------- 207 /** 208 * {@inheritDoc} 209 */ 210 public StringValue clone() 211 { 212 return ( StringValue ) super.clone(); 213 } 214 215 216 // ----------------------------------------------------------------------- 217 // Object Methods 218 // ----------------------------------------------------------------------- 219 /** 220 * @see Object#hashCode() 221 * @return the instance's hashcode 222 */ 223 public int hashCode() 224 { 225 if ( h == 0 ) 226 { 227 // return zero if the value is null so only one null value can be 228 // stored in an attribute - the binary version does the same 229 if ( isNull() ) 230 { 231 return 0; 232 } 233 234 // If the normalized value is null, will default to wrapped 235 // which cannot be null at this point. 236 // If the normalized value is null, will default to wrapped 237 // which cannot be null at this point. 238 String normalized = getNormValue(); 239 240 if ( normalized != null ) 241 { 242 h = normalized.hashCode(); 243 } 244 else 245 { 246 h = 17; 247 } 248 } 249 250 return h; 251 } 252 253 254 /** 255 * Two StringValue are equals if their normalized values are equal 256 * 257 * @see Object#equals(Object) 258 */ 259 public boolean equals( Object obj ) 260 { 261 if ( this == obj ) 262 { 263 return true; 264 } 265 266 if ( !( obj instanceof StringValue ) ) 267 { 268 return false; 269 } 270 271 StringValue other = ( StringValue ) obj; 272 273 // First check if we have an attrbuteType. 274 if ( attributeType != null ) 275 { 276 // yes : check for the other value 277 if ( other.attributeType != null ) 278 { 279 if ( attributeType.getOid().equals( other.getAttributeType().getOid() ) ) 280 { 281 // Both AttributeType have the same OID, we can assume they are 282 // equals. We don't check any further, because the unicity of OID 283 // makes it unlikely that the two AT are different. 284 // The values may be both null 285 if ( isNull() ) 286 { 287 return other.isNull(); 288 } 289 290 // Shortcut : if we have an AT for both the values, check the 291 // already normalized values 292 if ( wrappedValue.equals( other.wrappedValue ) ) 293 { 294 return true; 295 } 296 297 // We have an AttributeType, we use the associated comparator 298 try 299 { 300 Comparator<String> comparator = getLdapComparator(); 301 302 // Compare normalized values 303 if ( comparator == null ) 304 { 305 return getNormReference().equals( other.getNormReference() ); 306 } 307 else 308 { 309 return comparator.compare( getNormReference(), other.getNormReference() ) == 0; 310 } 311 } 312 catch ( LdapException ne ) 313 { 314 return false; 315 } 316 } 317 else 318 { 319 // We can't compare two values when the two ATs are different 320 return false; 321 } 322 } 323 else 324 { 325 // We only have one AT : we will assume that both values are for the 326 // same AT. 327 // The values may be both null 328 if ( isNull() ) 329 { 330 return other.isNull(); 331 } 332 333 // We have an AttributeType on the base value, we need to use its comparator 334 try 335 { 336 Comparator<String> comparator = getLdapComparator(); 337 338 // Compare normalized values. We have to normalized the other value, 339 // as it has no AT 340 MatchingRule equality = getAttributeType().getEquality(); 341 342 if ( equality == null ) 343 { 344 // No matching rule : compare the raw values 345 return getNormReference().equals( other.getNormReference() ); 346 } 347 348 Normalizer normalizer = equality.getNormalizer(); 349 350 StringValue otherValue = ( StringValue ) normalizer.normalize( other ); 351 352 if ( comparator == null ) 353 { 354 return getNormReference().equals( otherValue.getNormReference() ); 355 } 356 else 357 { 358 return comparator.compare( getNormReference(), otherValue.getNormReference() ) == 0; 359 } 360 } 361 catch ( LdapException ne ) 362 { 363 return false; 364 } 365 } 366 } 367 else 368 { 369 // No : check for the other value 370 if ( other.attributeType != null ) 371 { 372 // We only have one AT : we will assume that both values are for the 373 // same AT. 374 // The values may be both null 375 if ( isNull() ) 376 { 377 return other.isNull(); 378 } 379 380 try 381 { 382 Comparator<String> comparator = other.getLdapComparator(); 383 384 // Compare normalized values. We have to normalized the other value, 385 // as it has no AT 386 MatchingRule equality = other.getAttributeType().getEquality(); 387 388 if ( equality == null ) 389 { 390 // No matching rule : compare the raw values 391 return getNormReference().equals( other.getNormReference() ); 392 } 393 394 Normalizer normalizer = equality.getNormalizer(); 395 396 StringValue thisValue = ( StringValue ) normalizer.normalize( this ); 397 398 if ( comparator == null ) 399 { 400 return thisValue.getNormReference().equals( other.getNormReference() ); 401 } 402 else 403 { 404 return comparator.compare( thisValue.getNormReference(), other.getNormReference() ) == 0; 405 } 406 } 407 catch ( LdapException ne ) 408 { 409 return false; 410 } 411 } 412 else 413 { 414 // The values may be both null 415 if ( isNull() ) 416 { 417 return other.isNull(); 418 } 419 420 // Now check the normalized values 421 return getNormReference().equals( other.getNormReference() ); 422 } 423 } 424 } 425 426 427 /** 428 * {@inheritDoc} 429 */ 430 public boolean isHumanReadable() 431 { 432 return true; 433 } 434 435 436 /** 437 * @return The length of the interned value 438 */ 439 public int length() 440 { 441 return wrappedValue != null ? wrappedValue.length() : 0; 442 } 443 444 445 /** 446 * Get the wrapped value as a byte[]. 447 * @return the wrapped value as a byte[] 448 */ 449 public byte[] getBytes() 450 { 451 return bytes; 452 } 453 454 455 /** 456 * Get the wrapped value as a String. 457 * 458 * @return the wrapped value as a String 459 */ 460 public String getString() 461 { 462 return wrappedValue != null ? wrappedValue : ""; 463 } 464 465 466 /** 467 * Deserialize a StringValue. It will return a new StringValue instance. 468 * 469 * @param in The input stream 470 * @return A new StringValue instance 471 * @throws IOException If the stream can't be read 472 * @throws ClassNotFoundException If we can't instanciate a StringValue 473 */ 474 public static StringValue deserialize( ObjectInput in ) throws IOException, ClassNotFoundException 475 { 476 StringValue value = new StringValue( ( AttributeType ) null ); 477 value.readExternal( in ); 478 479 return value; 480 } 481 482 483 /** 484 * Deserialize a schemaAware StringValue. It will return a new StringValue instance. 485 * 486 * @param attributeType The AttributeType associated with the Value. Can be null 487 * @param in The input stream 488 * @return A new StringValue instance 489 * @throws IOException If the stream can't be read 490 * @throws ClassNotFoundException If we can't instanciate a StringValue 491 */ 492 public static StringValue deserialize( AttributeType attributeType, ObjectInput in ) throws IOException, 493 ClassNotFoundException 494 { 495 StringValue value = new StringValue( attributeType ); 496 value.readExternal( in ); 497 498 return value; 499 } 500 501 502 /** 503 * {@inheritDoc} 504 */ 505 public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException 506 { 507 // Read the STRING flag 508 boolean isHR = in.readBoolean(); 509 510 if ( !isHR ) 511 { 512 throw new IOException( "The serialized value is not a String value" ); 513 } 514 515 // Read the wrapped value, if it's not null 516 if ( in.readBoolean() ) 517 { 518 wrappedValue = in.readUTF(); 519 bytes = Strings.getBytesUtf8( wrappedValue ); 520 } 521 522 // Read the isNormalized flag 523 boolean normalized = in.readBoolean(); 524 525 if ( normalized ) 526 { 527 // Read the normalized value, if not null 528 if ( in.readBoolean() ) 529 { 530 normalizedValue = in.readUTF(); 531 } 532 } 533 else 534 { 535 if ( attributeType != null ) 536 { 537 try 538 { 539 MatchingRule equality = attributeType.getEquality(); 540 541 if ( equality == null ) 542 { 543 normalizedValue = wrappedValue; 544 } 545 else 546 { 547 Normalizer normalizer = equality.getNormalizer(); 548 549 if ( normalizer != null ) 550 { 551 normalizedValue = normalizer.normalize( wrappedValue ); 552 } 553 else 554 { 555 normalizedValue = wrappedValue; 556 } 557 } 558 } 559 catch ( LdapException le ) 560 { 561 normalizedValue = wrappedValue; 562 } 563 } 564 else 565 { 566 normalizedValue = wrappedValue; 567 } 568 } 569 570 // The hashCoe 571 h = in.readInt(); 572 } 573 574 575 /** 576 * Serialize the StringValue into a buffer at the given position. 577 * 578 * @param buffer The buffer which will contain the serialized StringValue 579 * @param pos The position in the buffer for the serialized value 580 * @return The new position in the buffer 581 */ 582 public int serialize( byte[] buffer, int pos ) 583 { 584 // Compute the length 585 int length = 1 + 1 + 1 + 4; // The value type, the wrappedValue presence flag, 586 // the normalizedValue presence flag and the hash length. 587 588 byte[] wrappedValueBytes = null; 589 byte[] normalizedValueBytes = null; 590 591 if ( wrappedValue != null ) 592 { 593 wrappedValueBytes = Strings.getBytesUtf8( wrappedValue ); 594 length += 4 + wrappedValueBytes.length; 595 } 596 597 if ( attributeType != null ) 598 { 599 if ( normalizedValue != null ) 600 { 601 normalizedValueBytes = Strings.getBytesUtf8( normalizedValue ); 602 length += 1 + 4 + normalizedValueBytes.length; 603 } 604 else 605 { 606 length += 1; 607 } 608 } 609 610 // Check that we will be able to store the data in the buffer 611 if ( buffer.length - pos < length ) 612 { 613 throw new ArrayIndexOutOfBoundsException(); 614 } 615 616 // The STRING flag 617 buffer[pos] = Serialize.TRUE; 618 pos++; 619 620 // Write the wrapped value, if it's not null 621 if ( wrappedValue != null ) 622 { 623 buffer[pos++] = Serialize.TRUE; 624 pos = Serialize.serialize( wrappedValueBytes, buffer, pos ); 625 } 626 else 627 { 628 buffer[pos++] = Serialize.FALSE; 629 } 630 631 // Write the isNormalized flag 632 if ( attributeType != null ) 633 { 634 // This flag is present to tell that we have a normalized value different 635 // from the upValue 636 637 buffer[pos++] = Serialize.TRUE; 638 639 // Write the normalized value, if not null 640 if ( normalizedValue != null ) 641 { 642 buffer[pos++] = Serialize.TRUE; 643 pos = Serialize.serialize( normalizedValueBytes, buffer, pos ); 644 } 645 else 646 { 647 buffer[pos++] = Serialize.FALSE; 648 } 649 } 650 else 651 { 652 // No normalized value 653 buffer[pos++] = Serialize.FALSE; 654 } 655 656 // Write the hashCode 657 pos = Serialize.serialize( h, buffer, pos ); 658 659 return pos; 660 } 661 662 663 /** 664 * Deserialize a StringValue from a byte[], starting at a given position 665 * 666 * @param buffer The buffer containing the StringValue 667 * @param pos The position in the buffer 668 * @return The new position 669 * @throws IOException If the serialized value is not a StringValue 670 */ 671 public int deserialize( byte[] buffer, int pos ) throws IOException 672 { 673 if ( ( pos < 0 ) || ( pos >= buffer.length ) ) 674 { 675 throw new ArrayIndexOutOfBoundsException(); 676 } 677 678 // Read the STRING flag 679 boolean isHR = Serialize.deserializeBoolean( buffer, pos ); 680 pos++; 681 682 if ( !isHR ) 683 { 684 throw new IOException( "The serialized value is not a String value" ); 685 } 686 687 // Read the wrapped value, if it's not null 688 boolean hasWrappedValue = Serialize.deserializeBoolean( buffer, pos ); 689 pos++; 690 691 if ( hasWrappedValue ) 692 { 693 byte[] wrappedValueBytes = Serialize.deserializeBytes( buffer, pos ); 694 pos += 4 + wrappedValueBytes.length; 695 wrappedValue = Strings.utf8ToString( wrappedValueBytes ); 696 } 697 698 // Read the isNormalized flag 699 boolean hasAttributeType = Serialize.deserializeBoolean( buffer, pos ); 700 pos++; 701 702 if ( hasAttributeType ) 703 { 704 // Read the normalized value, if not null 705 boolean hasNormalizedValue = Serialize.deserializeBoolean( buffer, pos ); 706 pos++; 707 708 if ( hasNormalizedValue ) 709 { 710 byte[] normalizedValueBytes = Serialize.deserializeBytes( buffer, pos ); 711 pos += 4 + normalizedValueBytes.length; 712 normalizedValue = Strings.utf8ToString( normalizedValueBytes ); 713 } 714 } 715 else 716 { 717 if ( attributeType != null ) 718 { 719 try 720 { 721 MatchingRule equality = attributeType.getEquality(); 722 723 if ( equality == null ) 724 { 725 normalizedValue = wrappedValue; 726 } 727 else 728 { 729 Normalizer normalizer = equality.getNormalizer(); 730 731 if ( normalizer != null ) 732 { 733 normalizedValue = normalizer.normalize( wrappedValue ); 734 } 735 else 736 { 737 normalizedValue = wrappedValue; 738 } 739 } 740 } 741 catch ( LdapException le ) 742 { 743 normalizedValue = wrappedValue; 744 } 745 } 746 else 747 { 748 normalizedValue = wrappedValue; 749 } 750 } 751 752 // The hashCode 753 h = Serialize.deserializeInt( buffer, pos ); 754 pos += 4; 755 756 return pos; 757 } 758 759 760 /** 761 * {@inheritDoc} 762 */ 763 public void writeExternal( ObjectOutput out ) throws IOException 764 { 765 // Write a boolean for the HR flag 766 out.writeBoolean( STRING ); 767 768 // Write the wrapped value, if it's not null 769 if ( wrappedValue != null ) 770 { 771 out.writeBoolean( true ); 772 out.writeUTF( wrappedValue ); 773 } 774 else 775 { 776 out.writeBoolean( false ); 777 } 778 779 // Write the isNormalized flag 780 if ( attributeType != null ) 781 { 782 // This flag is present to tell that we have a normalized value different 783 // from the upValue 784 out.writeBoolean( true ); 785 786 // Write the normalized value, if not null 787 if ( normalizedValue != null ) 788 { 789 out.writeBoolean( true ); 790 out.writeUTF( normalizedValue ); 791 } 792 else 793 { 794 out.writeBoolean( false ); 795 } 796 } 797 else 798 { 799 // No normalized value 800 out.writeBoolean( false ); 801 } 802 803 // Write the hashCode 804 out.writeInt( h ); 805 806 // and flush the data 807 out.flush(); 808 } 809 810 811 /** 812 * @see Object#toString() 813 */ 814 public String toString() 815 { 816 return wrappedValue == null ? "null" : wrappedValue; 817 } 818}