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; 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.asn1.primitives.OID; 030 import org.apache.directory.shared.i18n.I18n; 031 import org.apache.directory.shared.ldap.entry.client.DefaultClientAttribute; 032 import org.apache.directory.shared.ldap.exception.LdapException; 033 import org.apache.directory.shared.ldap.schema.AttributeType; 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 entry attribute aware of schema. 041 * 042 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 043 * @version $Rev$, $Date$ 044 */ 045 public final class DefaultServerAttribute extends DefaultClientAttribute implements EntryAttribute 046 { 047 public static final long serialVersionUID = 1L; 048 049 /** logger for reporting errors that might not be handled properly upstream */ 050 private static final Logger LOG = LoggerFactory.getLogger( DefaultServerAttribute.class ); 051 052 //------------------------------------------------------------------------- 053 // Constructors 054 //------------------------------------------------------------------------- 055 /** 056 * 057 * Creates a new instance of DefaultServerAttribute, by copying 058 * another attribute, which can be a ClientAttribute. If the other 059 * attribute is a ServerAttribute, it will be copied. 060 * 061 * @param attributeType The attribute's type 062 * @param attribute The attribute to be copied 063 */ 064 public DefaultServerAttribute( AttributeType attributeType, EntryAttribute attribute ) 065 { 066 // Copy the common values. isHR is only available on a ServerAttribute 067 this.attributeType = attributeType; 068 this.id = attribute.getId(); 069 this.upId = attribute.getUpId(); 070 071 if ( attributeType == null ) 072 { 073 isHR = attribute.isHR(); 074 075 // Copy all the values 076 for ( Value<?> value:attribute ) 077 { 078 add( value.clone() ); 079 } 080 } 081 else 082 { 083 084 isHR = attributeType.getSyntax().isHumanReadable(); 085 086 // Copy all the values 087 for ( Value<?> clientValue:attribute ) 088 { 089 Value<?> serverValue = null; 090 091 // We have to convert the value first 092 if ( clientValue instanceof StringValue ) 093 { 094 if ( isHR ) 095 { 096 serverValue = new StringValue( attributeType, clientValue.getString() ); 097 } 098 else 099 { 100 // We have to convert the value to a binary value first 101 serverValue = new BinaryValue( attributeType, 102 clientValue.getBytes() ); 103 } 104 } 105 else if ( clientValue instanceof BinaryValue ) 106 { 107 if ( isHR ) 108 { 109 // We have to convert the value to a String value first 110 serverValue = new StringValue( attributeType, 111 clientValue.getString() ); 112 } 113 else 114 { 115 serverValue = new BinaryValue( attributeType, clientValue.getBytes() ); 116 } 117 } 118 119 add( serverValue ); 120 } 121 } 122 } 123 124 125 // maybe have some additional convenience constructors which take 126 // an initial value as a string or a byte[] 127 /** 128 * Create a new instance of a EntryAttribute, without ID nor value. 129 * 130 * @param attributeType the attributeType for the empty attribute added into the entry 131 */ 132 public DefaultServerAttribute( AttributeType attributeType ) 133 { 134 if ( attributeType == null ) 135 { 136 throw new IllegalArgumentException( I18n.err( I18n.ERR_04442 ) ); 137 } 138 139 setAttributeType( attributeType ); 140 } 141 142 143 /** 144 * Create a new instance of a EntryAttribute, without value. 145 * 146 * @param upId the ID for the added attributeType 147 * @param attributeType the added AttributeType 148 */ 149 public DefaultServerAttribute( String upId, AttributeType attributeType ) 150 { 151 if ( attributeType == null ) 152 { 153 String message = I18n.err( I18n.ERR_04442 ); 154 LOG.error( message ); 155 throw new IllegalArgumentException( message ); 156 } 157 158 setAttributeType( attributeType ); 159 setUpId( upId, attributeType ); 160 } 161 162 163 /** 164 * Doc me more! 165 * 166 * If the value does not correspond to the same attributeType, then it's 167 * wrapped value is copied into a new Value which uses the specified 168 * attributeType. 169 * 170 * @param attributeType the attribute type according to the schema 171 * @param vals an initial set of values for this attribute 172 */ 173 public DefaultServerAttribute( AttributeType attributeType, Value<?>... vals ) 174 { 175 this( null, attributeType, vals ); 176 } 177 178 179 /** 180 * Doc me more! 181 * 182 * If the value does not correspond to the same attributeType, then it's 183 * wrapped value is copied into a new Value which uses the specified 184 * attributeType. 185 * 186 * Otherwise, the value is stored, but as a reference. It's not a copy. 187 * 188 * @param upId the ID of the added attribute 189 * @param attributeType the attribute type according to the schema 190 * @param vals an initial set of values for this attribute 191 */ 192 public DefaultServerAttribute( String upId, AttributeType attributeType, Value<?>... vals ) 193 { 194 if ( attributeType == null ) 195 { 196 throw new IllegalArgumentException( I18n.err( I18n.ERR_04442 ) ); 197 } 198 199 setAttributeType( attributeType ); 200 setUpId( upId, attributeType ); 201 add( vals ); 202 } 203 204 205 /** 206 * Create a new instance of a EntryAttribute, without ID but with some values. 207 * 208 * @param attributeType The attributeType added on creation 209 * @param vals The added value for this attribute 210 */ 211 public DefaultServerAttribute( AttributeType attributeType, String... vals ) 212 { 213 this( null, attributeType, vals ); 214 } 215 216 217 /** 218 * Create a new instance of a EntryAttribute. 219 * 220 * @param upId the ID for the added attribute 221 * @param attributeType The attributeType added on creation 222 * @param vals the added values for this attribute 223 */ 224 public DefaultServerAttribute( String upId, AttributeType attributeType, String... vals ) 225 { 226 if ( attributeType == null ) 227 { 228 throw new IllegalArgumentException( I18n.err( I18n.ERR_04442 ) ); 229 } 230 231 setAttributeType( attributeType ); 232 add( vals ); 233 setUpId( upId, attributeType ); 234 } 235 236 237 /** 238 * Create a new instance of a EntryAttribute, with some byte[] values. 239 * 240 * @param attributeType The attributeType added on creation 241 * @param vals The value for the added attribute 242 */ 243 public DefaultServerAttribute( AttributeType attributeType, byte[]... vals ) 244 { 245 this( null, attributeType, vals ); 246 } 247 248 249 /** 250 * Create a new instance of a EntryAttribute, with some byte[] values. 251 * 252 * @param upId the ID for the added attribute 253 * @param attributeType the AttributeType to be added 254 * @param vals the values for the added attribute 255 */ 256 public DefaultServerAttribute( String upId, AttributeType attributeType, byte[]... vals ) 257 { 258 if ( attributeType == null ) 259 { 260 throw new IllegalArgumentException( I18n.err( I18n.ERR_04442 ) ); 261 } 262 263 setAttributeType( attributeType ); 264 add( vals ); 265 setUpId( upId, attributeType ); 266 } 267 268 269 //------------------------------------------------------------------------- 270 // API 271 //------------------------------------------------------------------------- 272 /** 273 * <p> 274 * Adds some values to this attribute. If the new values are already present in 275 * the attribute values, the method has no effect. 276 * </p> 277 * <p> 278 * The new values are added at the end of list of values. 279 * </p> 280 * <p> 281 * This method returns the number of values that were added. 282 * </p> 283 * <p> 284 * If the value's type is different from the attribute's type, 285 * the value is not added. 286 * </p> 287 * It's the responsibility of the caller to check if the stored 288 * values are consistent with the attribute's type. 289 * <p> 290 * 291 * @param vals some new values to be added which may be null 292 * @return the number of added values, or 0 if none has been added 293 */ 294 public int add( byte[]... vals ) 295 { 296 if ( !isHR ) 297 { 298 int nbAdded = 0; 299 300 for ( byte[] val:vals ) 301 { 302 Value<?> value = new BinaryValue( attributeType, val ); 303 304 try 305 { 306 value.normalize(); 307 } 308 catch( LdapException ne ) 309 { 310 // The value can't be normalized : we don't add it. 311 LOG.error( I18n.err( I18n.ERR_04449, StringTools.dumpBytes( val ) ) ); 312 return 0; 313 } 314 315 if ( add( value ) != 0 ) 316 { 317 nbAdded++; 318 } 319 else 320 { 321 LOG.error( I18n.err( I18n.ERR_04450, StringTools.dumpBytes( val ) ) ); 322 } 323 } 324 325 return nbAdded; 326 } 327 else 328 { 329 // We can't add Binary values into a String serverAttribute 330 return 0; 331 } 332 } 333 334 335 /** 336 * <p> 337 * Adds some values to this attribute. If the new values are already present in 338 * the attribute values, the method has no effect. 339 * </p> 340 * <p> 341 * The new values are added at the end of list of values. 342 * </p> 343 * <p> 344 * This method returns the number of values that were added. 345 * </p> 346 * If the value's type is different from the attribute's type, 347 * the value is not added. 348 * 349 * @param vals some new values to be added which may be null 350 * @return the number of added values, or 0 if none has been added 351 */ 352 public int add( String... vals ) 353 { 354 if ( isHR ) 355 { 356 int nbAdded = 0; 357 358 for ( String val:vals ) 359 { 360 Value<String> newValue = new StringValue( attributeType, val ); 361 362 if ( add( newValue ) != 0 ) 363 { 364 nbAdded++; 365 } 366 else 367 { 368 LOG.error( I18n.err( I18n.ERR_04450, val ) ); 369 } 370 } 371 372 return nbAdded; 373 } 374 else 375 { 376 // We can't add String values into a Binary serverAttribute 377 return 0; 378 } 379 } 380 381 382 /** 383 * @see EntryAttribute#add(org.apache.directory.shared.ldap.entry.Value...) 384 * 385 * @return the number of added values into this attribute 386 */ 387 public int add( Value<?>... vals ) 388 { 389 int nbAdded = 0; 390 391 for ( Value<?> val:vals ) 392 { 393 if ( attributeType.getSyntax().isHumanReadable() ) 394 { 395 if ( ( val == null ) || val.isNull() ) 396 { 397 Value<String> nullSV = new StringValue( attributeType, (String)null ); 398 399 if ( values.add( nullSV ) ) 400 { 401 nbAdded++; 402 } 403 } 404 else if ( val instanceof StringValue ) 405 { 406 StringValue stringValue = (StringValue)val; 407 408 if ( stringValue.getAttributeType() == null ) 409 { 410 stringValue.apply( attributeType ); 411 } 412 413 if ( values.add( val ) ) 414 { 415 nbAdded++; 416 } 417 } 418 else 419 { 420 String message = I18n.err( I18n.ERR_04451 ); 421 LOG.error( message ); 422 } 423 } 424 else 425 { 426 if ( val == null ) 427 { 428 Value<byte[]> nullSV = new BinaryValue( attributeType, (byte[])null ); 429 430 if ( values.add( nullSV ) ) 431 { 432 nbAdded++; 433 } 434 } 435 else 436 { 437 if ( val instanceof BinaryValue ) 438 { 439 BinaryValue binaryValue = (BinaryValue)val; 440 441 if ( binaryValue.getAttributeType() == null ) 442 { 443 binaryValue = new BinaryValue( attributeType, val.getBytes() ); 444 } 445 446 if ( values.add( binaryValue ) ) 447 { 448 nbAdded++; 449 } 450 } 451 else 452 { 453 String message = I18n.err( I18n.ERR_04452 ); 454 LOG.error( message ); 455 } 456 } 457 } 458 } 459 460 return nbAdded; 461 } 462 463 464 /** 465 * Remove all the values from this attribute type, including a 466 * null value. 467 */ 468 public void clear() 469 { 470 values.clear(); 471 } 472 473 474 /** 475 * <p> 476 * Indicates whether all the specified values are attribute's values. If 477 * at least one value is not an attribute's value, this method will return 478 * <code>false</code> 479 * </p> 480 * <p> 481 * If the Attribute is HR, this method will returns <code>false</code> 482 * </p> 483 * 484 * @param vals the values 485 * @return true if this attribute contains all the values, otherwise false 486 */ 487 public boolean contains( byte[]... vals ) 488 { 489 if ( !isHR ) 490 { 491 // Iterate through all the values, and quit if we 492 // don't find one in the values 493 for ( byte[] val:vals ) 494 { 495 BinaryValue value = new BinaryValue( attributeType, val ); 496 497 try 498 { 499 value.normalize(); 500 } 501 catch ( LdapException ne ) 502 { 503 return false; 504 } 505 506 if ( !values.contains( value ) ) 507 { 508 return false; 509 } 510 } 511 512 return true; 513 } 514 else 515 { 516 return false; 517 } 518 } 519 520 521 /** 522 * <p> 523 * Indicates whether all the specified values are attribute's values. If 524 * at least one value is not an attribute's value, this method will return 525 * <code>false</code> 526 * </p> 527 * <p> 528 * If the Attribute is not HR, this method will returns <code>false</code> 529 * </p> 530 * 531 * @param vals the values 532 * @return true if this attribute contains all the values, otherwise false 533 */ 534 public boolean contains( String... vals ) 535 { 536 if ( isHR ) 537 { 538 // Iterate through all the values, and quit if we 539 // don't find one in the values 540 for ( String val:vals ) 541 { 542 StringValue value = new StringValue( attributeType, val ); 543 544 if ( !values.contains( value ) ) 545 { 546 return false; 547 } 548 } 549 550 return true; 551 } 552 else 553 { 554 return false; 555 } 556 } 557 558 559 /** 560 * <p> 561 * Indicates whether the specified values are some of the attribute's values. 562 * </p> 563 * <p> 564 * If the Attribute is HR, te metho will only accept String Values. Otherwise, 565 * it will only accept Binary values. 566 * </p> 567 * 568 * @param vals the values 569 * @return true if this attribute contains all the values, otherwise false 570 */ 571 public boolean contains( Value<?>... vals ) 572 { 573 // Iterate through all the values, and quit if we 574 // don't find one in the values. We have to separate the check 575 // depending on the isHR flag value. 576 if ( isHR ) 577 { 578 for ( Value<?> val:vals ) 579 { 580 if ( val instanceof StringValue ) 581 { 582 StringValue stringValue = (StringValue)val; 583 584 if ( stringValue.getAttributeType() == null ) 585 { 586 stringValue.apply( attributeType ); 587 } 588 589 if ( !values.contains( val ) ) 590 { 591 return false; 592 } 593 } 594 else 595 { 596 // Not a String value 597 return false; 598 } 599 } 600 } 601 else 602 { 603 for ( Value<?> val:vals ) 604 { 605 if ( val instanceof BinaryValue ) 606 { 607 if ( !values.contains( val ) ) 608 { 609 return false; 610 } 611 } 612 else 613 { 614 // Not a Binary value 615 return false; 616 } 617 } 618 } 619 620 return true; 621 } 622 623 624 /** 625 * <p> 626 * Checks to see if this attribute is valid along with the values it contains. 627 * </p> 628 * <p> 629 * An attribute is valid if : 630 * <li>All of its values are valid with respect to the attributeType's syntax checker</li> 631 * <li>If the attributeType is SINGLE-VALUE, then no more than a value should be present</li> 632 *</p> 633 * @return true if the attribute and it's values are valid, false otherwise 634 * @throws NamingException if there is a failure to check syntaxes of values 635 */ 636 public boolean isValid() throws LdapException 637 { 638 // First check if the attribute has more than one value 639 // if the attribute is supposed to be SINGLE_VALUE 640 if ( attributeType.isSingleValued() && ( values.size() > 1 ) ) 641 { 642 return false; 643 } 644 645 // Check that we can have no value for this attributeType 646 if ( values.size() == 0 ) 647 { 648 return attributeType.getSyntax().getSyntaxChecker().isValidSyntax( null ); 649 } 650 651 for ( Value<?> value : values ) 652 { 653 if ( ! value.isValid() ) 654 { 655 return false; 656 } 657 } 658 659 return true; 660 } 661 662 663 /** 664 * @see EntryAttribute#remove(byte[]...) 665 * 666 * @return <code>true</code> if all the values shave been removed from this attribute 667 */ 668 public boolean remove( byte[]... vals ) 669 { 670 if ( isHR ) 671 { 672 return false; 673 } 674 675 boolean removed = true; 676 677 for ( byte[] val:vals ) 678 { 679 BinaryValue value = new BinaryValue( attributeType, val ); 680 removed &= values.remove( value ); 681 } 682 683 return removed; 684 } 685 686 687 /** 688 * @see EntryAttribute#remove(String...) 689 * 690 * @return <code>true</code> if all the values shave been removed from this attribute 691 */ 692 public boolean remove( String... vals ) 693 { 694 if ( !isHR ) 695 { 696 return false; 697 } 698 699 boolean removed = true; 700 701 for ( String val:vals ) 702 { 703 StringValue value = new StringValue( attributeType, val ); 704 removed &= values.remove( value ); 705 } 706 707 return removed; 708 } 709 710 711 /** 712 * @see EntryAttribute#remove(org.apache.directory.shared.ldap.entry.Value...) 713 * 714 * @return <code>true</code> if all the values shave been removed from this attribute 715 */ 716 public boolean remove( Value<?>... vals ) 717 { 718 boolean removed = true; 719 720 // Loop through all the values to remove. If one of 721 // them is not present, the method will return false. 722 // As the attribute may be HR or not, we have two separated treatments 723 if ( isHR ) 724 { 725 for ( Value<?> val:vals ) 726 { 727 if ( val instanceof StringValue ) 728 { 729 StringValue stringValue = (StringValue)val; 730 731 if ( stringValue.getAttributeType() == null ) 732 { 733 stringValue.apply( attributeType ); 734 } 735 736 removed &= values.remove( stringValue ); 737 } 738 else 739 { 740 removed = false; 741 } 742 } 743 } 744 else 745 { 746 for ( Value<?> val:vals ) 747 { 748 if ( val instanceof BinaryValue ) 749 { 750 BinaryValue binaryValue = (BinaryValue)val; 751 752 if ( binaryValue.getAttributeType() == null ) 753 { 754 binaryValue = new BinaryValue( attributeType, (byte[])val.get() ); 755 } 756 757 removed &= values.remove( binaryValue ); 758 } 759 else 760 { 761 removed = false; 762 } 763 } 764 } 765 766 return removed; 767 } 768 769 770 771 /** 772 * <p> 773 * Overload the ClientAttribte isHR method : we can't change this flag 774 * for a ServerAttribute, as the HR is already set using the AttributeType. 775 * Set the attribute to Human Readable or to Binary. 776 * </p> 777 * 778 * @param isHR <code>true</code> for a Human Readable attribute, 779 * <code>false</code> for a Binary attribute. 780 */ 781 public void setHR( boolean isHR ) 782 { 783 // Do nothing... 784 } 785 786 787 /** 788 * <p> 789 * Overload the {@link DefaultClientAttribute#setId(String)} method. 790 * </p> 791 * <p> 792 * As the attributeType has already been set, we have to be sure that the 793 * argument is compatible with the attributeType's name. 794 * </p> 795 * <p> 796 * If the given ID is not compatible with the attributeType's possible 797 * names, the previously loaded ID will be kept. 798 * </p> 799 * 800 * @param id The attribute ID 801 */ 802 public void setId( String id ) 803 { 804 if ( !StringTools.isEmpty( StringTools.trim( id ) ) ) 805 { 806 if ( attributeType.getName() == null ) 807 { 808 // If the name is null, then we may have to store an OID 809 if ( OID.isOID( id ) && attributeType.getOid().equals( id ) ) 810 { 811 // Everything is fine, store the upId. 812 // This should not happen... 813 super.setId( id ); 814 } 815 } 816 else 817 { 818 // We have at least one name. Check that the normalized upId 819 // is one of those names. Otherwise, the upId may be an OID too. 820 // In this case, it must be equals to the attributeType OID. 821 String normId = StringTools.lowerCaseAscii( StringTools.trim( id ) ); 822 823 for ( String atName:attributeType.getNames() ) 824 { 825 if ( atName.equalsIgnoreCase( normId ) ) 826 { 827 // Found ! We can store the upId and get out 828 super.setId( normId ); 829 return; 830 } 831 } 832 833 // Last case, the UpId is an OID 834 if ( OID.isOID( normId ) && attributeType.getOid().equals( normId ) ) 835 { 836 // We have an OID : stores it 837 super.setUpId( normId ); 838 } 839 else 840 { 841 // The id is incorrect : this is not allowed 842 throw new IllegalArgumentException( I18n.err( I18n.ERR_04455, id, attributeType.getName() ) ); 843 } 844 } 845 } 846 else 847 { 848 throw new IllegalArgumentException( I18n.err( I18n.ERR_04456 ) ); 849 } 850 } 851 852 853 /** 854 * <p> 855 * Overload the {@link DefaultClientAttribute#setUpId(String)} method. 856 * </p> 857 * <p> 858 * As the attributeType has already been set, we have to be sure that the 859 * argument is compatible with the attributeType's name. 860 * </p> 861 * <p> 862 * If the given ID is not compatible with the attributeType's possible 863 * names, the previously loaded ID will be kept. 864 * </p> 865 * 866 * @param upId The attribute ID 867 * 868 public void setUpId( String upId ) 869 { 870 if ( !StringTools.isEmpty( StringTools.trim( upId ) ) ) 871 { 872 if ( attributeType.getName() == null ) 873 { 874 // If the name is null, then we may have to store an OID 875 if ( OID.isOID( upId ) && attributeType.getOid().equals( upId ) ) 876 { 877 // Everything is fine, store the upId. 878 // This should not happen... 879 super.setUpId( upId ); 880 return; 881 } 882 } 883 else 884 { 885 // We have at least one name. Check that the normalized upId 886 // is one of those names. Otherwise, the upId may be an OID too. 887 // In this case, it must be equals to the attributeType OID. 888 String normUpId = StringTools.lowerCaseAscii( StringTools.trim( upId ) ); 889 890 for ( String atId:attributeType.getNames() ) 891 { 892 if ( atId.equalsIgnoreCase( normUpId ) ) 893 { 894 // Found ! We can store the upId and get out 895 super.setUpId( upId ); 896 return; 897 } 898 } 899 900 // Last case, the UpId is an OID 901 if ( OID.isOID( normUpId ) && attributeType.getOid().equals( normUpId ) ) 902 { 903 // We have an OID : stores it 904 super.setUpId( upId ); 905 return; 906 } 907 908 return; 909 } 910 } 911 912 return; 913 } 914 915 916 //------------------------------------------------------------------------- 917 // Serialization methods 918 //------------------------------------------------------------------------- 919 920 /** 921 * @see java.io.Externalizable#writeExternal(ObjectOutput) 922 * 923 * We can't use this method for a ServerAttribute, as we have to feed the value 924 * with an AttributeType object 925 */ 926 public void writeExternal( ObjectOutput out ) throws IOException 927 { 928 throw new IllegalStateException( I18n.err( I18n.ERR_04454 ) ); 929 } 930 931 932 /** 933 * @see Externalizable#writeExternal(ObjectOutput) 934 * <p> 935 * 936 * This is the place where we serialize attributes, and all theirs 937 * elements. 938 * 939 * The inner structure is the same as the client attribute, but we can't call 940 * it as we won't be able to serialize the serverValues 941 * 942 */ 943 public void serialize( ObjectOutput out ) throws IOException 944 { 945 // Write the UPId (the id will be deduced from the upID) 946 out.writeUTF( upId ); 947 948 // Write the HR flag, if not null 949 if ( isHR != null ) 950 { 951 out.writeBoolean( true ); 952 out.writeBoolean( isHR ); 953 } 954 else 955 { 956 out.writeBoolean( false ); 957 } 958 959 // Write the number of values 960 out.writeInt( size() ); 961 962 if ( size() > 0 ) 963 { 964 // Write each value 965 for ( Value<?> value:values ) 966 { 967 // Write the value, using the correct method 968 if ( value instanceof StringValue ) 969 { 970 ((StringValue)value).serialize( out ); 971 } 972 else 973 { 974 ((BinaryValue)value).serialize( out ); 975 } 976 } 977 } 978 } 979 980 981 /** 982 * @see java.io.Externalizable#readExternal(ObjectInput) 983 * 984 * We can't use this method for a ServerAttribute, as we have to feed the value 985 * with an AttributeType object 986 */ 987 public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException 988 { 989 throw new IllegalStateException( I18n.err( I18n.ERR_04454 ) ); 990 } 991 992 993 /** 994 * @see Externalizable#readExternal(ObjectInput) 995 */ 996 public void deserialize( ObjectInput in ) throws IOException, ClassNotFoundException 997 { 998 // Read the ID and the UPId 999 upId = in.readUTF(); 1000 1001 // Compute the id 1002 setUpId( upId ); 1003 1004 // Read the HR flag, if not null 1005 if ( in.readBoolean() ) 1006 { 1007 isHR = in.readBoolean(); 1008 } 1009 1010 // Read the number of values 1011 int nbValues = in.readInt(); 1012 1013 if ( nbValues > 0 ) 1014 { 1015 for ( int i = 0; i < nbValues; i++ ) 1016 { 1017 Value<?> value = null; 1018 1019 if ( isHR ) 1020 { 1021 value = new StringValue( attributeType ); 1022 ((StringValue)value).deserialize( in ); 1023 } 1024 else 1025 { 1026 value = new BinaryValue( attributeType ); 1027 ((BinaryValue)value).deserialize( in ); 1028 } 1029 1030 try 1031 { 1032 value.normalize(); 1033 } 1034 catch ( LdapException ne ) 1035 { 1036 // Do nothing... 1037 } 1038 1039 values.add( value ); 1040 } 1041 } 1042 } 1043 1044 1045 //------------------------------------------------------------------------- 1046 // Overloaded Object class methods 1047 //------------------------------------------------------------------------- 1048 /** 1049 * Clone an attribute. All the element are duplicated, so a modification on 1050 * the original object won't affect the cloned object, as a modification 1051 * on the cloned object has no impact on the original object 1052 * 1053 * @return a clone of the current attribute 1054 */ 1055 public EntryAttribute clone() 1056 { 1057 // clone the structure by cloner the inherited class 1058 EntryAttribute clone = (EntryAttribute)super.clone(); 1059 1060 // We are done ! 1061 return clone; 1062 } 1063 1064 1065 /** 1066 * @see Object#equals(Object) 1067 * 1068 * @return <code>true</code> if the two objects are equal 1069 */ 1070 public boolean equals( Object obj ) 1071 { 1072 if ( obj == this ) 1073 { 1074 return true; 1075 } 1076 1077 if ( ! (obj instanceof EntryAttribute ) ) 1078 { 1079 return false; 1080 } 1081 1082 EntryAttribute other = (EntryAttribute)obj; 1083 1084 if ( !attributeType.equals( other.getAttributeType() ) ) 1085 { 1086 return false; 1087 } 1088 1089 if ( values.size() != other.size() ) 1090 { 1091 return false; 1092 } 1093 1094 for ( Value<?> val:values ) 1095 { 1096 if ( ! other.contains( val ) ) 1097 { 1098 return false; 1099 } 1100 } 1101 1102 return true; 1103 } 1104 1105 1106 /** 1107 * The hashCode is based on the id, the isHR flag and 1108 * on the internal values. 1109 * 1110 * @see Object#hashCode() 1111 * 1112 * @return the instance's hash code 1113 */ 1114 public int hashCode() 1115 { 1116 int h = super.hashCode(); 1117 1118 if ( attributeType != null ) 1119 { 1120 h = h*17 + attributeType.hashCode(); 1121 } 1122 1123 return h; 1124 } 1125 1126 1127 /** 1128 * @see Object#toString() 1129 * 1130 * @return A String representation of this instance 1131 */ 1132 public String toString() 1133 { 1134 StringBuilder sb = new StringBuilder(); 1135 1136 if ( ( values != null ) && ( values.size() != 0 ) ) 1137 { 1138 for ( Value<?> value:values ) 1139 { 1140 sb.append( " " ).append( upId ).append( ": " ); 1141 1142 if ( value.isNull() ) 1143 { 1144 sb.append( "''" ); 1145 } 1146 else 1147 { 1148 sb.append( value ); 1149 } 1150 1151 sb.append( '\n' ); 1152 } 1153 } 1154 else 1155 { 1156 sb.append( " " ).append( upId ).append( ": (null)\n" ); 1157 } 1158 1159 return sb.toString(); 1160 } 1161 }