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 */ 020package org.apache.directory.api.ldap.model.name; 021 022 023import java.io.Externalizable; 024import java.io.IOException; 025import java.io.ObjectInput; 026import java.io.ObjectOutput; 027import java.util.ArrayList; 028import java.util.Collection; 029import java.util.Iterator; 030import java.util.List; 031 032import org.apache.commons.collections.MultiMap; 033import org.apache.commons.collections.map.MultiValueMap; 034import org.apache.directory.api.i18n.I18n; 035import org.apache.directory.api.ldap.model.entry.StringValue; 036import org.apache.directory.api.ldap.model.entry.Value; 037import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException; 038import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException; 039import org.apache.directory.api.ldap.model.schema.AttributeType; 040import org.apache.directory.api.ldap.model.schema.SchemaManager; 041import org.apache.directory.api.util.Chars; 042import org.apache.directory.api.util.Hex; 043import org.apache.directory.api.util.Serialize; 044import org.apache.directory.api.util.Strings; 045import org.apache.directory.api.util.Unicode; 046import org.slf4j.Logger; 047import org.slf4j.LoggerFactory; 048 049 050/** 051 * This class store the name-component part or the following BNF grammar (as of 052 * RFC2253, par. 3, and RFC1779, fig. 1) : <br> - <name-component> ::= 053 * <attributeType> <spaces> '=' <spaces> 054 * <attributeValue> <attributeTypeAndValues> <br> - 055 * <attributeTypeAndValues> ::= <spaces> '+' <spaces> 056 * <attributeType> <spaces> '=' <spaces> 057 * <attributeValue> <attributeTypeAndValues> | e <br> - 058 * <attributeType> ::= [a-zA-Z] <keychars> | <oidPrefix> [0-9] 059 * <digits> <oids> | [0-9] <digits> <oids> <br> - 060 * <keychars> ::= [a-zA-Z] <keychars> | [0-9] <keychars> | '-' 061 * <keychars> | e <br> - <oidPrefix> ::= 'OID.' | 'oid.' | e <br> - 062 * <oids> ::= '.' [0-9] <digits> <oids> | e <br> - 063 * <attributeValue> ::= <pairs-or-strings> | '#' <hexstring> 064 * |'"' <quotechar-or-pairs> '"' <br> - <pairs-or-strings> ::= '\' 065 * <pairchar> <pairs-or-strings> | <stringchar> 066 * <pairs-or-strings> | e <br> - <quotechar-or-pairs> ::= 067 * <quotechar> <quotechar-or-pairs> | '\' <pairchar> 068 * <quotechar-or-pairs> | e <br> - <pairchar> ::= ',' | '=' | '+' | 069 * '<' | '>' | '#' | ';' | '\' | '"' | [0-9a-fA-F] [0-9a-fA-F] <br> - 070 * <hexstring> ::= [0-9a-fA-F] [0-9a-fA-F] <hexpairs> <br> - 071 * <hexpairs> ::= [0-9a-fA-F] [0-9a-fA-F] <hexpairs> | e <br> - 072 * <digits> ::= [0-9] <digits> | e <br> - <stringchar> ::= 073 * [0x00-0xFF] - [,=+<>#;\"\n\r] <br> - <quotechar> ::= [0x00-0xFF] - 074 * [\"] <br> - <separator> ::= ',' | ';' <br> - <spaces> ::= ' ' 075 * <spaces> | e <br> 076 * <br> 077 * A Rdn is a part of a Dn. It can be composed of many types, as in the Rdn 078 * following Rdn :<br> 079 * ou=value + cn=other value<br> 080 * <br> 081 * or <br> 082 * ou=value + ou=another value<br> 083 * <br> 084 * In this case, we have to store an 'ou' and a 'cn' in the Rdn.<br> 085 * <br> 086 * The types are case insensitive. <br> 087 * Spaces before and after types and values are not stored.<br> 088 * Spaces before and after '+' are not stored.<br> 089 * <br> 090 * Thus, we can consider that the following RDNs are equals :<br> 091 * <br> 092 * 'ou=test 1'<br> ' ou=test 1'<br> 093 * 'ou =test 1'<br> 094 * 'ou= test 1'<br> 095 * 'ou=test 1 '<br> ' ou = test 1 '<br> 096 * <br> 097 * So are the following :<br> 098 * <br> 099 * 'ou=test 1+cn=test 2'<br> 100 * 'ou = test 1 + cn = test 2'<br> ' ou =test 1+ cn =test 2 ' <br> 101 * 'cn = test 2 +ou = test 1'<br> 102 * <br> 103 * but the following are not equal :<br> 104 * 'ou=test 1' <br> 105 * 'ou=test 1'<br> 106 * because we have more than one spaces inside the value.<br> 107 * <br> 108 * The Rdn is composed of one or more Ava. Those Avas 109 * are ordered in the alphabetical natural order : a < b < c ... < z As the type 110 * are not case sensitive, we can say that a = A 111 * <br> 112 * This class is immutable. 113 * 114 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 115 */ 116public class Rdn implements Cloneable, Externalizable, Iterable<Ava>, Comparable<Rdn> 117{ 118 /** The LoggerFactory used by this class */ 119 protected static final Logger LOG = LoggerFactory.getLogger( Rdn.class ); 120 121 /** An empty Rdn */ 122 public static final Rdn EMPTY_RDN = new Rdn(); 123 124 /** 125 * Declares the Serial Version Uid. 126 * 127 * @see <a 128 * href="http://c2.com/cgi/wiki?AlwaysDeclareSerialVersionUid">Always 129 * Declare Serial Version Uid</a> 130 */ 131 private static final long serialVersionUID = 1L; 132 133 /** The User Provided Rdn */ 134 private String upName = null; 135 136 /** The normalized Rdn */ 137 private String normName = null; 138 139 /** 140 * Stores all couple type = value. We may have more than one type, if the 141 * '+' character appears in the Ava. This is a TreeSet, 142 * because we want the Avas to be sorted. An Ava may contain more than one 143 * value. In this case, the values are String stored in a List. 144 */ 145 private List<Ava> avas = null; 146 147 /** 148 * We also keep a set of types, in order to use manipulations. A type is 149 * connected with the Ava it represents. 150 * 151 * Note : there is no Generic available classes in commons-collection... 152 */ 153 private MultiMap avaTypes = new MultiValueMap(); 154 155 /** 156 * We keep the type for a single valued Rdn, to avoid the creation of an HashMap 157 */ 158 private String avaType = null; 159 160 /** 161 * A simple Ava is used to store the Rdn for the simple 162 * case where we only have a single type=value. This will be 99.99% the 163 * case. This avoids the creation of a HashMap. 164 */ 165 protected Ava ava = null; 166 167 /** 168 * The number of Avas. We store this number here to avoid complex 169 * manipulation of Ava and Avas 170 */ 171 private int nbAvas = 0; 172 173 /** CompareTo() results */ 174 public static final int UNDEFINED = Integer.MAX_VALUE; 175 176 /** Constant used in comparisons */ 177 public static final int SUPERIOR = 1; 178 179 /** Constant used in comparisons */ 180 public static final int INFERIOR = -1; 181 182 /** Constant used in comparisons */ 183 public static final int EQUAL = 0; 184 185 /** A flag used to tell if the Rdn has been normalized */ 186 private boolean normalized = false; 187 188 /** the schema manager */ 189 private SchemaManager schemaManager; 190 191 /** The computed hashcode */ 192 private volatile int h; 193 194 195 /** 196 * A empty constructor. 197 */ 198 public Rdn() 199 { 200 this( ( SchemaManager ) null ); 201 } 202 203 204 /** 205 * 206 * Creates a new schema aware instance of Rdn. 207 * 208 * @param schemaManager the schema manager 209 */ 210 public Rdn( SchemaManager schemaManager ) 211 { 212 // Don't waste space... This is not so often we have multiple 213 // name-components in a Rdn... So we won't initialize the Map and the 214 // treeSet. 215 this.schemaManager = schemaManager; 216 upName = ""; 217 normName = ""; 218 normalized = false; 219 h = 0; 220 } 221 222 223 /** 224 * A constructor that parse a String representing a schema aware Rdn. 225 * 226 * @param schemaManager the schema manager 227 * @param rdn the String containing the Rdn to parse 228 * @throws LdapInvalidDnException if the Rdn is invalid 229 */ 230 public Rdn( SchemaManager schemaManager, String rdn ) throws LdapInvalidDnException 231 { 232 if ( Strings.isNotEmpty( rdn ) ) 233 { 234 // Parse the string. The Rdn will be updated. 235 parse( rdn, this ); 236 237 // create the internal normalized form 238 // and store the user provided form 239 if ( schemaManager != null ) 240 { 241 this.schemaManager = schemaManager; 242 apply( schemaManager ); 243 normalized = true; 244 } 245 else 246 { 247 normalize(); 248 normalized = false; 249 } 250 251 if ( upName.length() < rdn.length() ) 252 { 253 throw new LdapInvalidDnException( "Invalid RDN" ); 254 } 255 256 upName = rdn; 257 } 258 else 259 { 260 upName = ""; 261 normName = ""; 262 normalized = false; 263 } 264 265 hashCode(); 266 } 267 268 269 /** 270 * A constructor that parse a String representing a Rdn. 271 * 272 * @param rdn the String containing the Rdn to parse 273 * @throws LdapInvalidDnException if the Rdn is invalid 274 */ 275 public Rdn( String rdn ) throws LdapInvalidDnException 276 { 277 this( ( SchemaManager ) null, rdn ); 278 } 279 280 281 /** 282 * A constructor that constructs a schema aware Rdn from a type and a value. 283 * <p> 284 * The string attribute values are not interpreted as RFC 414 formatted Rdn 285 * strings. That is, the values are used literally (not parsed) and assumed 286 * to be un-escaped. 287 * 288 * @param schemaManager the schema manager 289 * @param upType the user provided type of the Rdn 290 * @param upValue the user provided value of the Rdn 291 * @throws LdapInvalidDnException if the Rdn is invalid 292 */ 293 public Rdn( SchemaManager schemaManager, String upType, String upValue ) throws LdapInvalidDnException 294 { 295 addAVA( schemaManager, upType, upType, new StringValue( upValue ) ); 296 297 upName = upType + '=' + upValue; 298 299 if ( schemaManager != null ) 300 { 301 this.schemaManager = schemaManager; 302 apply( schemaManager ); 303 normalized = true; 304 } 305 else 306 { 307 // create the internal normalized form 308 normalize(); 309 310 // As strange as it seems, the Rdn is *not* normalized against the schema at this point 311 normalized = false; 312 } 313 314 hashCode(); 315 } 316 317 318 /** 319 * A constructor that constructs a Rdn from a type and a value. 320 * 321 * @param upType the user provided type of the Rdn 322 * @param upValue the user provided value of the Rdn 323 * @throws LdapInvalidDnException if the Rdn is invalid 324 * @see #Rdn( SchemaManager, String, String ) 325 */ 326 public Rdn( String upType, String upValue ) throws LdapInvalidDnException 327 { 328 this( null, upType, upValue ); 329 } 330 331 332 /** 333 * A constructor that constructs a Schema aware Rdn from some values. 334 * 335 * @param schemaManager The schemaManager to use 336 * @param avas The list of values 337 * @throws LdapInvalidDnException If the Rdn is invalid 338 */ 339 public Rdn( SchemaManager schemaManager, Ava... avas ) throws LdapInvalidDnException 340 { 341 StringBuilder buffer = new StringBuilder(); 342 343 for ( int i = 0; i < avas.length; i++ ) 344 { 345 if ( i > 0 ) 346 { 347 buffer.append( '+' ); 348 } 349 350 addAVA( schemaManager, avas[i] ); 351 buffer.append( avas[i].getName() ); 352 } 353 354 setUpName( buffer.toString() ); 355 normalize(); 356 } 357 358 359 /** 360 * A constructor that constructs a Rdn from some values. 361 * 362 * @param avas The list of values 363 * @throws LdapInvalidDnException If the Rdn is invalid 364 */ 365 public Rdn( Ava... avas ) throws LdapInvalidDnException 366 { 367 this( null, avas ); 368 } 369 370 371 /** 372 * Constructs an Rdn from the given rdn. The content of the rdn is simply 373 * copied into the newly created Rdn. 374 * 375 * @param rdn The non-null Rdn to be copied. 376 */ 377 public Rdn( Rdn rdn ) 378 { 379 nbAvas = rdn.size(); 380 this.normName = rdn.normName; 381 this.upName = rdn.getName(); 382 normalized = rdn.normalized; 383 schemaManager = rdn.schemaManager; 384 385 switch ( rdn.size() ) 386 { 387 case 0: 388 hashCode(); 389 390 return; 391 392 case 1: 393 this.ava = rdn.ava.clone(); 394 hashCode(); 395 396 return; 397 398 default: 399 // We must duplicate the treeSet and the hashMap 400 avas = new ArrayList<>(); 401 avaTypes = new MultiValueMap(); 402 403 for ( Ava currentAva : rdn.avas ) 404 { 405 avas.add( currentAva.clone() ); 406 avaTypes.put( currentAva.getNormType(), currentAva ); 407 } 408 409 hashCode(); 410 411 return; 412 } 413 } 414 415 416 /** 417 * Transform the external representation of the current Rdn to an internal 418 * normalized form where : 419 * - types are trimmed and lower cased 420 * - values are trimmed and lower cased 421 */ 422 // WARNING : The protection level is left unspecified on purpose. 423 // We need this method to be visible from the DnParser class, but not 424 // from outside this package. 425 /* Unspecified protection */void normalize() 426 { 427 switch ( nbAvas ) 428 { 429 case 0: 430 // An empty Rdn 431 normName = ""; 432 break; 433 434 case 1: 435 // We have a single Ava 436 // We will trim and lowercase type and value. 437 if ( ava.getValue().isHumanReadable() ) 438 { 439 normName = ava.getNormName(); 440 } 441 else 442 { 443 normName = ava.getNormType() + "=#" + Strings.dumpHexPairs( ava.getValue().getBytes() ); 444 } 445 446 break; 447 448 default: 449 // We have more than one Ava 450 StringBuilder sb = new StringBuilder(); 451 452 boolean isFirst = true; 453 454 for ( Ava ata : avas ) 455 { 456 if ( isFirst ) 457 { 458 isFirst = false; 459 } 460 else 461 { 462 sb.append( '+' ); 463 } 464 465 sb.append( ata.getNormName() ); 466 } 467 468 normName = sb.toString(); 469 break; 470 } 471 472 hashCode(); 473 } 474 475 476 /** 477 * Transform a Rdn by changing the value to its OID counterpart and 478 * normalizing the value accordingly to its type. The modified Rdn is 479 * a new instance, as the Rdn class is immutable. 480 * 481 * @param schemaManager the SchemaManager 482 * @return this Rdn, normalized 483 * @throws LdapInvalidDnException if the Rdn is invalid 484 */ 485 public Rdn apply( SchemaManager schemaManager ) throws LdapInvalidDnException 486 { 487 if ( normalized ) 488 { 489 return this; 490 } 491 492 String savedUpName = getName(); 493 Dn.rdnOidToName( this, schemaManager ); 494 normalize(); 495 this.upName = savedUpName; 496 normalized = true; 497 this.schemaManager = schemaManager; 498 hashCode(); 499 500 return this; 501 } 502 503 504 /** 505 * Add an Ava to the current Rdn 506 * 507 * @param upType The user provided type of the added Rdn. 508 * @param type The normalized provided type of the added Rdn. 509 * @param upValue The user provided value of the added Rdn 510 * @param value The normalized provided value of the added Rdn 511 * @throws LdapInvalidDnException 512 * If the Rdn is invalid 513 */ 514 private void addAVA( SchemaManager schemaManager, String upType, String type, Value<?> value ) throws LdapInvalidDnException 515 { 516 // First, let's normalize the type 517 AttributeType attributeType; 518 String normalizedType = Strings.lowerCaseAscii( type ); 519 this.schemaManager = schemaManager; 520 521 if ( schemaManager != null ) 522 { 523 attributeType = schemaManager.getAttributeType( normalizedType ); 524 525 try 526 { 527 value.apply( attributeType ); 528 } 529 catch ( LdapInvalidAttributeValueException liave ) 530 { 531 throw new LdapInvalidDnException( liave.getMessage(), liave ); 532 } 533 } 534 535 switch ( nbAvas ) 536 { 537 case 0: 538 // This is the first Ava. Just stores it. 539 ava = new Ava( schemaManager, upType, normalizedType, value ); 540 nbAvas = 1; 541 avaType = normalizedType; 542 hashCode(); 543 544 return; 545 546 case 1: 547 // We already have an Ava. We have to put it in the HashMap 548 // before adding a new one. 549 // First, create the HashMap, 550 avas = new ArrayList<>(); 551 552 // and store the existing Ava into it. 553 avas.add( ava ); 554 avaTypes = new MultiValueMap(); 555 avaTypes.put( avaType, ava ); 556 557 ava = null; 558 559 // Now, fall down to the commmon case 560 // NO BREAK !!! 561 562 default: 563 // add a new Ava 564 Ava newAva = new Ava( schemaManager, upType, normalizedType, value ); 565 avas.add( newAva ); 566 avaTypes.put( normalizedType, newAva ); 567 nbAvas++; 568 hashCode(); 569 570 return; 571 572 } 573 } 574 575 576 /** 577 * Replace an Ava into a Rdn at a given position 578 * 579 * @param value The modified Ava 580 * @param pos The position of the Ava in the Rdn 581 * @exception LdapInvalidDnException If the position is not valid 582 */ 583 // WARNING : The protection level is left unspecified intentionally. 584 // We need this method to be visible from the DnParser class, but not 585 // from outside this package. 586 /* Unspecified protection */void replaceAva( Ava value, int pos ) throws LdapInvalidDnException 587 { 588 if ( ( pos < 0 ) || ( pos > nbAvas ) ) 589 { 590 throw new LdapInvalidDnException( "Cannot set the AVA at position " + pos ); 591 } 592 593 String normalizedType = value.getNormType(); 594 595 if ( nbAvas == 1 ) 596 { 597 // This is the first Ava. Just stores it. 598 ava = value; 599 avaType = normalizedType; 600 } 601 else 602 { 603 Ava oldAva = avas.get( pos ); 604 avas.set( pos, value ); 605 avaTypes.remove( oldAva.getType() ); 606 avaTypes.put( normalizedType, value ); 607 } 608 609 h = 0; 610 hashCode(); 611 } 612 613 614 /** 615 * Add an Ava to the current schema aware Rdn 616 * 617 * @param value The added Ava 618 */ 619 // WARNING : The protection level is left unspecified intentionally. 620 // We need this method to be visible from the DnParser class, but not 621 // from outside this package. 622 /* Unspecified protection */void addAVA( SchemaManager schemaManager, Ava value ) throws LdapInvalidDnException 623 { 624 this.schemaManager = schemaManager; 625 String normalizedType = value.getNormType(); 626 627 switch ( nbAvas ) 628 { 629 case 0: 630 // This is the first Ava. Just stores it. 631 ava = value; 632 nbAvas = 1; 633 avaType = normalizedType; 634 hashCode(); 635 636 return; 637 638 case 1: 639 // We already have an Ava. We have to put it in the HashMap 640 // before adding a new one. 641 // Check that the first AVA is not for the same attribute 642 if ( avaType.equals( normalizedType ) && ava.getValue().equals( value.getValue() ) ) 643 { 644 throw new LdapInvalidDnException( "Invalid RDN: the " + normalizedType 645 + " is already present in the RDN" ); 646 } 647 648 // First, create the HashMap, 649 avas = new ArrayList<>(); 650 651 // and store the existing Ava into it. 652 avas.add( ava ); 653 avaTypes = new MultiValueMap(); 654 avaTypes.put( avaType, ava ); 655 656 this.ava = null; 657 658 // Now, fall down to the commmon case 659 // NO BREAK !!! 660 661 default: 662 // Check that the AT is not already present 663 if ( avaTypes.containsKey( normalizedType ) ) 664 { 665 Collection<Ava> atavList = ( Collection<Ava> ) avaTypes.get( normalizedType ); 666 667 if ( atavList.contains( value ) ) 668 { 669 throw new LdapInvalidDnException( "Invalid RDN: the " + normalizedType 670 + " is already present in the RDN" ); 671 } 672 else 673 { 674 // Add the value to the list 675 atavList.add( value ); 676 nbAvas++; 677 } 678 } 679 else 680 { 681 // add a new Ava 682 avas.add( value ); 683 avaTypes.put( normalizedType, value ); 684 nbAvas++; 685 hashCode(); 686 } 687 688 break; 689 } 690 } 691 692 693 /** 694 * Clear the Rdn, removing all the Avas. 695 */ 696 // WARNING : The protection level is left unspecified intentionally. 697 // We need this method to be visible from the DnParser class, but not 698 // from outside this package. 699 /* No protection */void clear() 700 { 701 ava = null; 702 avas = null; 703 avaType = null; 704 avaTypes.clear(); 705 nbAvas = 0; 706 normName = ""; 707 upName = ""; 708 normalized = false; 709 h = 0; 710 } 711 712 713 /** 714 * Get the value of the Ava which type is given as an 715 * argument. 716 * 717 * @param type the type of the NameArgument 718 * @return the value to be returned, or null if none found. 719 * @throws LdapInvalidDnException if the Rdn is invalid 720 */ 721 public Object getValue( String type ) throws LdapInvalidDnException 722 { 723 // First, let's normalize the type 724 String normalizedType = Strings.lowerCaseAscii( Strings.trim( type ) ); 725 726 if ( schemaManager != null ) 727 { 728 AttributeType attributeType = schemaManager.getAttributeType( normalizedType ); 729 730 if ( attributeType != null ) 731 { 732 normalizedType = attributeType.getOid(); 733 } 734 } 735 736 switch ( nbAvas ) 737 { 738 case 0: 739 return ""; 740 741 case 1: 742 if ( Strings.equals( ava.getNormType(), normalizedType ) ) 743 { 744 return ava.getValue().getValue(); 745 } 746 747 return ""; 748 749 default: 750 if ( avaTypes.containsKey( normalizedType ) ) 751 { 752 @SuppressWarnings("unchecked") 753 Collection<Ava> atavList = ( Collection<Ava> ) avaTypes.get( normalizedType ); 754 StringBuilder sb = new StringBuilder(); 755 boolean isFirst = true; 756 757 for ( Ava elem : atavList ) 758 { 759 if ( isFirst ) 760 { 761 isFirst = false; 762 } 763 else 764 { 765 sb.append( ',' ); 766 } 767 768 sb.append( elem.getValue() ); 769 } 770 771 return sb.toString(); 772 } 773 774 return ""; 775 } 776 } 777 778 779 /** 780 * Get the normalized value of the Ava which type is given as an 781 * argument. 782 * 783 * @param type the type of the NameArgument 784 * @return the normalized value to be returned, or null if none found. 785 * @throws LdapInvalidDnException if the Rdn is invalid 786 */ 787 public Object getNormValue( String type ) throws LdapInvalidDnException 788 { 789 // First, let's normalize the type 790 String normalizedType = Strings.lowerCaseAscii( Strings.trim( type ) ); 791 792 if ( schemaManager != null ) 793 { 794 AttributeType attributeType = schemaManager.getAttributeType( normalizedType ); 795 796 if ( attributeType != null ) 797 { 798 normalizedType = attributeType.getOid(); 799 } 800 } 801 802 switch ( nbAvas ) 803 { 804 case 0: 805 return ""; 806 807 case 1: 808 if ( Strings.equals( ava.getNormType(), normalizedType ) ) 809 { 810 return ava.getValue().getNormValue(); 811 } 812 813 return ""; 814 815 default: 816 if ( avaTypes.containsKey( normalizedType ) ) 817 { 818 @SuppressWarnings("unchecked") 819 Collection<Ava> atavList = ( Collection<Ava> ) avaTypes.get( normalizedType ); 820 StringBuilder sb = new StringBuilder(); 821 boolean isFirst = true; 822 823 for ( Ava elem : atavList ) 824 { 825 if ( isFirst ) 826 { 827 isFirst = false; 828 } 829 else 830 { 831 sb.append( ',' ); 832 } 833 834 sb.append( elem.getValue().getNormValue() ); 835 } 836 837 return sb.toString(); 838 } 839 840 return ""; 841 } 842 } 843 844 845 /** 846 * Get the Ava which type is given as an argument. If we 847 * have more than one value associated with the type, we will return only 848 * the first one. 849 * 850 * @param type 851 * The type of the NameArgument to be returned 852 * @return The Ava, of null if none is found. 853 */ 854 public Ava getAva( String type ) 855 { 856 // First, let's normalize the type 857 String normalizedType = Strings.lowerCaseAscii( Strings.trim( type ) ); 858 859 switch ( nbAvas ) 860 { 861 case 0: 862 return null; 863 864 case 1: 865 if ( ava.getNormType().equals( normalizedType ) ) 866 { 867 return ava; 868 } 869 870 return null; 871 872 default: 873 if ( avaTypes.containsKey( normalizedType ) ) 874 { 875 @SuppressWarnings("unchecked") 876 Collection<Ava> atavList = ( Collection<Ava> ) avaTypes.get( normalizedType ); 877 return atavList.iterator().next(); 878 } 879 880 return null; 881 } 882 } 883 884 885 /** 886 * Retrieves the components of this Rdn as an iterator of Avas. 887 * The effect on the iterator of updates to this Rdn is undefined. If the 888 * Rdn has zero components, an empty (non-null) iterator is returned. 889 * 890 * @return an iterator of the components of this Rdn, each an Ava 891 */ 892 @Override 893 public Iterator<Ava> iterator() 894 { 895 if ( ( nbAvas == 1 ) || ( nbAvas == 0 ) ) 896 { 897 return new Iterator<Ava>() 898 { 899 private boolean hasMoreElement = nbAvas == 1; 900 901 902 @Override 903 public boolean hasNext() 904 { 905 return hasMoreElement; 906 } 907 908 909 @Override 910 public Ava next() 911 { 912 Ava obj = ava; 913 hasMoreElement = false; 914 return obj; 915 } 916 917 918 @Override 919 public void remove() 920 { 921 // nothing to do 922 } 923 }; 924 } 925 else 926 { 927 return avas.iterator(); 928 } 929 } 930 931 932 /** 933 * Clone the Rdn 934 * 935 * @return A clone of the current Rdn 936 */ 937 @Override 938 public Rdn clone() 939 { 940 try 941 { 942 Rdn rdn = ( Rdn ) super.clone(); 943 rdn.normalized = normalized; 944 945 // The Ava is immutable. We won't clone it 946 947 switch ( rdn.size() ) 948 { 949 case 0: 950 break; 951 952 case 1: 953 rdn.ava = this.ava.clone(); 954 rdn.avaTypes = avaTypes; 955 break; 956 957 default: 958 // We must duplicate the treeSet and the hashMap 959 rdn.avaTypes = new MultiValueMap(); 960 rdn.avas = new ArrayList<>(); 961 962 for ( Ava currentAva : this.avas ) 963 { 964 rdn.avas.add( currentAva.clone() ); 965 rdn.avaTypes.put( currentAva.getNormType(), currentAva ); 966 } 967 968 break; 969 } 970 971 return rdn; 972 } 973 catch ( CloneNotSupportedException cnse ) 974 { 975 throw new Error( "Assertion failure", cnse ); 976 } 977 } 978 979 980 /** 981 * @return the user provided name 982 */ 983 public String getName() 984 { 985 return upName; 986 } 987 988 989 /** 990 * @return The normalized name 991 */ 992 public String getNormName() 993 { 994 return normName == null ? "" : normName; 995 } 996 997 998 /** 999 * Set the User Provided Name. 1000 * 1001 * Package private because Rdn is immutable, only used by the Dn parser. 1002 * 1003 * @param upName the User Provided dame 1004 */ 1005 void setUpName( String upName ) 1006 { 1007 this.upName = upName; 1008 } 1009 1010 1011 /** 1012 * Return the unique Ava, or the first one of we have more 1013 * than one 1014 * 1015 * @return The first Ava of this Rdn 1016 */ 1017 public Ava getAva() 1018 { 1019 switch ( nbAvas ) 1020 { 1021 case 0: 1022 return null; 1023 1024 case 1: 1025 return ava; 1026 1027 default: 1028 return avas.get( 0 ); 1029 } 1030 } 1031 1032 1033 /** 1034 * Return the user provided type, or the first one of we have more than one (the lowest) 1035 * 1036 * @return The first user provided type of this Rdn 1037 */ 1038 public String getType() 1039 { 1040 switch ( nbAvas ) 1041 { 1042 case 0: 1043 return null; 1044 1045 case 1: 1046 return ava.getType(); 1047 1048 default: 1049 return avas.get( 0 ).getType(); 1050 } 1051 } 1052 1053 1054 /** 1055 * Return the normalized type, or the first one of we have more than one (the lowest) 1056 * 1057 * @return The first normalized type of this Rdn 1058 */ 1059 public String getNormType() 1060 { 1061 switch ( nbAvas ) 1062 { 1063 case 0: 1064 return null; 1065 1066 case 1: 1067 return ava.getNormType(); 1068 1069 default: 1070 return avas.get( 0 ).getNormType(); 1071 } 1072 } 1073 1074 1075 /** 1076 * Return the User Provided value, as a String 1077 * 1078 * @return The first User provided value of this Rdn 1079 */ 1080 public String getValue() 1081 { 1082 switch ( nbAvas ) 1083 { 1084 case 0: 1085 return null; 1086 1087 case 1: 1088 return ava.getValue().getString(); 1089 1090 default: 1091 return avas.get( 0 ).getValue().getString(); 1092 } 1093 } 1094 1095 1096 /** 1097 * Return the Normalized value, as a String 1098 * 1099 * @return The first Normalized value of this Rdn 1100 */ 1101 public String getNormValue() 1102 { 1103 switch ( nbAvas ) 1104 { 1105 case 0: 1106 return null; 1107 1108 case 1: 1109 return ava.getValue().getNormValue().toString(); 1110 1111 default: 1112 return avas.get( 0 ).getValue().getNormValue().toString(); 1113 } 1114 } 1115 1116 1117 /** 1118 * Compares the specified Object with this Rdn for equality. Returns true if 1119 * the given object is also a Rdn and the two Rdns represent the same 1120 * attribute type and value mappings. The order of components in 1121 * multi-valued Rdns is not significant. 1122 * 1123 * @param that Rdn to be compared for equality with this Rdn 1124 * @return true if the specified object is equal to this Rdn 1125 */ 1126 @Override 1127 public boolean equals( Object that ) 1128 { 1129 if ( this == that ) 1130 { 1131 return true; 1132 } 1133 1134 if ( !( that instanceof Rdn ) ) 1135 { 1136 return false; 1137 } 1138 1139 Rdn rdn = ( Rdn ) that; 1140 1141 // Short cut : compare the normalized Rdn 1142 if ( normName.equals( rdn.normName ) ) 1143 { 1144 return true; 1145 } 1146 1147 // Short cut : compare the normalized Rdn 1148 if ( normName.equals( rdn.normName ) ) 1149 { 1150 return true; 1151 } 1152 1153 if ( rdn.nbAvas != nbAvas ) 1154 { 1155 // We don't have the same number of Avas. The Rdn which 1156 // has the higher number of Ava is the one which is 1157 // superior 1158 return false; 1159 } 1160 1161 switch ( nbAvas ) 1162 { 1163 case 0: 1164 return true; 1165 1166 case 1: 1167 return ava.equals( rdn.ava ); 1168 1169 default: 1170 // We have more than one value. We will 1171 // go through all of them. 1172 1173 // the types are already normalized and sorted in the Avas Map 1174 // so we could compare the first element with all of the second 1175 // Ava elemnts, etc. 1176 Iterator<Ava> localIterator = avas.iterator(); 1177 1178 while ( localIterator.hasNext() ) 1179 { 1180 Iterator<Ava> paramIterator = rdn.avas.iterator(); 1181 1182 Ava localAva = localIterator.next(); 1183 boolean equals = false; 1184 1185 while ( paramIterator.hasNext() ) 1186 { 1187 Ava paramAva = paramIterator.next(); 1188 1189 if ( localAva.equals( paramAva ) ) 1190 { 1191 equals = true; 1192 break; 1193 } 1194 } 1195 1196 if ( !equals ) 1197 { 1198 return false; 1199 } 1200 } 1201 1202 return true; 1203 } 1204 } 1205 1206 1207 /** 1208 * Get the number of Avas of this Rdn 1209 * 1210 * @return The number of Avas in this Rdn 1211 */ 1212 public int size() 1213 { 1214 return nbAvas; 1215 } 1216 1217 1218 /** 1219 * Unescape the given string according to RFC 2253 If in <string> form, a 1220 * LDAP string representation asserted value can be obtained by replacing 1221 * (left-to-right, non-recursively) each <pair> appearing in the <string> as 1222 * follows: 1223 * <ul> 1224 * <li>replace <ESC><ESC> with <ESC></li> 1225 * <li>replace <ESC><special> with <special></li> 1226 * <li>replace <ESC><hexpair> with the octet indicated by the <hexpair></li> 1227 * </ul> 1228 * If in <hexstring> form, a BER representation can be obtained 1229 * from converting each <hexpair> of the <hexstring> to the octet indicated 1230 * by the <hexpair> 1231 * 1232 * @param value The value to be unescaped 1233 * @return Returns a string value as a String, and a binary value as a byte 1234 * array. 1235 * @throws IllegalArgumentException When an Illegal value is provided. 1236 */ 1237 public static Object unescapeValue( String value ) 1238 { 1239 if ( Strings.isEmpty( value ) ) 1240 { 1241 return ""; 1242 } 1243 1244 char[] chars = value.toCharArray(); 1245 1246 // If the value is contained into double quotes, return it as is. 1247 if ( ( chars[0] == '\"' ) && ( chars[chars.length - 1] == '\"' ) ) 1248 { 1249 return new String( chars, 1, chars.length - 2 ); 1250 } 1251 1252 if ( chars[0] == '#' ) 1253 { 1254 if ( chars.length == 1 ) 1255 { 1256 // The value is only containing a # 1257 return Strings.EMPTY_BYTES; 1258 } 1259 1260 if ( ( chars.length % 2 ) != 1 ) 1261 { 1262 throw new IllegalArgumentException( I18n.err( I18n.ERR_04213 ) ); 1263 } 1264 1265 // HexString form 1266 byte[] hexValue = new byte[( chars.length - 1 ) / 2]; 1267 int pos = 0; 1268 1269 for ( int i = 1; i < chars.length; i += 2 ) 1270 { 1271 if ( Chars.isHex( chars, i ) && Chars.isHex( chars, i + 1 ) ) 1272 { 1273 hexValue[pos++] = Hex.getHexValue( chars[i], chars[i + 1] ); 1274 } 1275 else 1276 { 1277 throw new IllegalArgumentException( I18n.err( I18n.ERR_04214 ) ); 1278 } 1279 } 1280 1281 return hexValue; 1282 } 1283 else 1284 { 1285 boolean escaped = false; 1286 boolean isHex = false; 1287 byte pair = -1; 1288 int pos = 0; 1289 1290 byte[] bytes = new byte[chars.length * 6]; 1291 1292 for ( int i = 0; i < chars.length; i++ ) 1293 { 1294 if ( escaped ) 1295 { 1296 escaped = false; 1297 1298 switch ( chars[i] ) 1299 { 1300 case '\\': 1301 case '"': 1302 case '+': 1303 case ',': 1304 case ';': 1305 case '<': 1306 case '>': 1307 case '#': 1308 case '=': 1309 case ' ': 1310 bytes[pos++] = ( byte ) chars[i]; 1311 break; 1312 1313 default: 1314 if ( Chars.isHex( chars, i ) ) 1315 { 1316 isHex = true; 1317 pair = ( byte ) ( Hex.getHexValue( chars[i] ) << 4 ); 1318 } 1319 1320 break; 1321 } 1322 } 1323 else 1324 { 1325 if ( isHex ) 1326 { 1327 if ( Chars.isHex( chars, i ) ) 1328 { 1329 pair += Hex.getHexValue( chars[i] ); 1330 bytes[pos++] = pair; 1331 isHex = false; 1332 pair = 0; 1333 } 1334 } 1335 else 1336 { 1337 switch ( chars[i] ) 1338 { 1339 case '\\': 1340 escaped = true; 1341 break; 1342 1343 // We must not have a special char 1344 // Specials are : '"', '+', ',', ';', '<', '>', ' ', 1345 // '#' and '=' 1346 case '"': 1347 case '+': 1348 case ',': 1349 case ';': 1350 case '<': 1351 case '>': 1352 case '#': 1353 if ( i != 0 ) 1354 { 1355 // '#' are allowed if not in first position 1356 bytes[pos++] = '#'; 1357 break; 1358 } 1359 case '=': 1360 throw new IllegalArgumentException( I18n.err( I18n.ERR_04215 ) ); 1361 1362 case ' ': 1363 if ( ( i == 0 ) || ( i == chars.length - 1 ) ) 1364 { 1365 throw new IllegalArgumentException( I18n.err( I18n.ERR_04215 ) ); 1366 } 1367 else 1368 { 1369 bytes[pos++] = ' '; 1370 break; 1371 } 1372 1373 default: 1374 if ( chars[i] < 128 ) 1375 { 1376 bytes[pos++] = ( byte ) chars[i]; 1377 } 1378 else 1379 { 1380 byte[] result = Unicode.charToBytes( chars[i] ); 1381 System.arraycopy( result, 0, bytes, pos, result.length ); 1382 pos += result.length; 1383 } 1384 1385 break; 1386 } 1387 } 1388 } 1389 } 1390 1391 return Strings.utf8ToString( bytes, pos ); 1392 } 1393 } 1394 1395 1396 /** 1397 * Transform a value in a String, accordingly to RFC 2253 1398 * 1399 * @param value The attribute value to be escaped 1400 * @return The escaped string value. 1401 */ 1402 public static String escapeValue( String value ) 1403 { 1404 if ( Strings.isEmpty( value ) ) 1405 { 1406 return ""; 1407 } 1408 1409 char[] chars = value.toCharArray(); 1410 char[] newChars = new char[chars.length * 3]; 1411 int pos = 0; 1412 1413 for ( int i = 0; i < chars.length; i++ ) 1414 { 1415 switch ( chars[i] ) 1416 { 1417 case ' ': 1418 if ( ( i > 0 ) && ( i < chars.length - 1 ) ) 1419 { 1420 newChars[pos++] = chars[i]; 1421 } 1422 else 1423 { 1424 newChars[pos++] = '\\'; 1425 newChars[pos++] = chars[i]; 1426 } 1427 1428 break; 1429 1430 case '#': 1431 if ( i != 0 ) 1432 { 1433 newChars[pos++] = chars[i]; 1434 } 1435 else 1436 { 1437 newChars[pos++] = '\\'; 1438 newChars[pos++] = chars[i]; 1439 } 1440 1441 break; 1442 1443 case '"': 1444 case '+': 1445 case ',': 1446 case ';': 1447 case '=': 1448 case '<': 1449 case '>': 1450 case '\\': 1451 newChars[pos++] = '\\'; 1452 newChars[pos++] = chars[i]; 1453 break; 1454 1455 case 0x7F: 1456 newChars[pos++] = '\\'; 1457 newChars[pos++] = '7'; 1458 newChars[pos++] = 'F'; 1459 break; 1460 1461 case 0x00: 1462 case 0x01: 1463 case 0x02: 1464 case 0x03: 1465 case 0x04: 1466 case 0x05: 1467 case 0x06: 1468 case 0x07: 1469 case 0x08: 1470 case 0x09: 1471 case 0x0A: 1472 case 0x0B: 1473 case 0x0C: 1474 case 0x0D: 1475 case 0x0E: 1476 case 0x0F: 1477 newChars[pos++] = '\\'; 1478 newChars[pos++] = '0'; 1479 newChars[pos++] = Strings.dumpHex( ( byte ) ( chars[i] & 0x0F ) ); 1480 break; 1481 1482 case 0x10: 1483 case 0x11: 1484 case 0x12: 1485 case 0x13: 1486 case 0x14: 1487 case 0x15: 1488 case 0x16: 1489 case 0x17: 1490 case 0x18: 1491 case 0x19: 1492 case 0x1A: 1493 case 0x1B: 1494 case 0x1C: 1495 case 0x1D: 1496 case 0x1E: 1497 case 0x1F: 1498 newChars[pos++] = '\\'; 1499 newChars[pos++] = '1'; 1500 newChars[pos++] = Strings.dumpHex( ( byte ) ( chars[i] & 0x0F ) ); 1501 break; 1502 1503 default: 1504 newChars[pos++] = chars[i]; 1505 break; 1506 } 1507 } 1508 1509 return new String( newChars, 0, pos ); 1510 } 1511 1512 1513 /** 1514 * Transform a value in a String, accordingly to RFC 2253 1515 * 1516 * @param attrValue 1517 * The attribute value to be escaped 1518 * @return The escaped string value. 1519 */ 1520 public static String escapeValue( byte[] attrValue ) 1521 { 1522 if ( Strings.isEmpty( attrValue ) ) 1523 { 1524 return ""; 1525 } 1526 1527 String value = Strings.utf8ToString( attrValue ); 1528 1529 return escapeValue( value ); 1530 } 1531 1532 1533 /** 1534 * Tells if the Rdn is schema aware. 1535 * 1536 * @return <code>true</code> if the Rdn is schema aware 1537 */ 1538 public boolean isSchemaAware() 1539 { 1540 return schemaManager != null; 1541 } 1542 1543 1544 /** 1545 * Validate a NameComponent : <br> 1546 * <p> 1547 * <name-component> ::= <attributeType> <spaces> '=' 1548 * <spaces> <attributeValue> <nameComponents> 1549 * </p> 1550 * 1551 * @param dn The string to parse 1552 * @return <code>true</code> if the Rdn is valid 1553 */ 1554 public static boolean isValid( String dn ) 1555 { 1556 Rdn rdn = new Rdn(); 1557 1558 try 1559 { 1560 parse( dn, rdn ); 1561 1562 return true; 1563 } 1564 catch ( LdapInvalidDnException e ) 1565 { 1566 return false; 1567 } 1568 } 1569 1570 1571 /** 1572 * Parse a NameComponent : <br> 1573 * <p> 1574 * <name-component> ::= <attributeType> <spaces> '=' 1575 * <spaces> <attributeValue> <nameComponents> 1576 * </p> 1577 * 1578 * @param dn The String to parse 1579 * @param rdn The Rdn to fill. Beware that if the Rdn is not empty, the new 1580 * AttributeTypeAndValue will be added. 1581 * @throws LdapInvalidDnException If the NameComponent is invalid 1582 */ 1583 private static void parse( String dn, Rdn rdn ) throws LdapInvalidDnException 1584 { 1585 try 1586 { 1587 FastDnParser.parseRdn( dn, rdn ); 1588 } 1589 catch ( TooComplexDnException e ) 1590 { 1591 rdn.clear(); 1592 new ComplexDnParser().parseRdn( dn, rdn ); 1593 } 1594 } 1595 1596 1597 /** 1598 * Gets the hashcode of this rdn. 1599 * 1600 * @see java.lang.Object#hashCode() 1601 * @return the instance's hash code 1602 */ 1603 @Override 1604 public int hashCode() 1605 { 1606 if ( h == 0 ) 1607 { 1608 h = 37; 1609 1610 switch ( nbAvas ) 1611 { 1612 case 0: 1613 // An empty Rdn 1614 break; 1615 1616 case 1: 1617 // We have a single Ava 1618 h = h * 17 + ava.hashCode(); 1619 break; 1620 1621 default: 1622 // We have more than one Ava 1623 1624 for ( Ava ata : avas ) 1625 { 1626 h = h * 17 + ata.hashCode(); 1627 } 1628 1629 break; 1630 } 1631 } 1632 1633 return h; 1634 } 1635 1636 1637 /** 1638 * Serialize a RDN into a byte[] 1639 * 1640 * @param buffer The buffer which will contain the serilaized form of this RDN 1641 * @param pos The position in the buffer where to store the RDN 1642 * @return The new position in the byte[] 1643 * @throws IOException If we had an error while serilaizing the RDN 1644 */ 1645 public int serialize( byte[] buffer, int pos ) throws IOException 1646 { 1647 // The nbAvas and the HashCode length 1648 int length = 4 + 4; 1649 1650 // The NnbAvas 1651 pos = Serialize.serialize( nbAvas, buffer, pos ); 1652 1653 // The upName 1654 byte[] upNameBytes = Strings.getBytesUtf8( upName ); 1655 length += 4 + upNameBytes.length; 1656 1657 byte[] normNameBytes = Strings.EMPTY_BYTES; 1658 length += 4; 1659 1660 if ( !upName.equals( normName ) ) 1661 { 1662 normNameBytes = Strings.getBytesUtf8( normName ); 1663 length += 4 + normNameBytes.length; 1664 } 1665 1666 // Check that we will be able to store the data in the buffer 1667 if ( buffer.length - pos < length ) 1668 { 1669 throw new ArrayIndexOutOfBoundsException(); 1670 } 1671 1672 // Write the upName 1673 pos = Serialize.serialize( upNameBytes, buffer, pos ); 1674 1675 // Write the normName 1676 pos = Serialize.serialize( normNameBytes, buffer, pos ); 1677 1678 // Write the AVAs 1679 switch ( nbAvas ) 1680 { 1681 case 0: 1682 break; 1683 1684 case 1: 1685 pos = ava.serialize( buffer, pos ); 1686 1687 break; 1688 1689 default: 1690 for ( Ava localAva : avas ) 1691 { 1692 pos = localAva.serialize( buffer, pos ); 1693 } 1694 1695 break; 1696 } 1697 1698 // The hash code 1699 pos = Serialize.serialize( h, buffer, pos ); 1700 1701 return pos; 1702 } 1703 1704 1705 /** 1706 * Deserialize a RDN from a byte[], starting at a given position 1707 * 1708 * @param buffer The buffer containing the RDN 1709 * @param pos The position in the buffer 1710 * @return The new position 1711 * @throws IOException If the serialized value is not a RDN 1712 * @throws LdapInvalidAttributeValueException If the serialized RDN is invalid 1713 */ 1714 public int deserialize( byte[] buffer, int pos ) throws IOException, LdapInvalidAttributeValueException 1715 { 1716 if ( ( pos < 0 ) || ( pos >= buffer.length ) ) 1717 { 1718 throw new ArrayIndexOutOfBoundsException(); 1719 } 1720 1721 // Read the nbAvas 1722 nbAvas = Serialize.deserializeInt( buffer, pos ); 1723 pos += 4; 1724 1725 // Read the upName 1726 byte[] upNameBytes = Serialize.deserializeBytes( buffer, pos ); 1727 pos += 4 + upNameBytes.length; 1728 upName = Strings.utf8ToString( upNameBytes ); 1729 1730 // Read the normName 1731 byte[] normNameBytes = Serialize.deserializeBytes( buffer, pos ); 1732 pos += 4 + normNameBytes.length; 1733 1734 if ( normNameBytes.length == 0 ) 1735 { 1736 normName = upName; 1737 } 1738 else 1739 { 1740 normName = Strings.utf8ToString( normNameBytes ); 1741 } 1742 1743 // Read the AVAs 1744 switch ( nbAvas ) 1745 { 1746 case 0: 1747 break; 1748 1749 case 1: 1750 ava = new Ava( schemaManager ); 1751 pos = ava.deserialize( buffer, pos ); 1752 avaType = ava.getNormType(); 1753 1754 break; 1755 1756 default: 1757 avas = new ArrayList<>(); 1758 1759 avaTypes = new MultiValueMap(); 1760 1761 for ( int i = 0; i < nbAvas; i++ ) 1762 { 1763 Ava newAva = new Ava( schemaManager ); 1764 pos = newAva.deserialize( buffer, pos ); 1765 avas.add( newAva ); 1766 avaTypes.put( newAva.getNormType(), newAva ); 1767 } 1768 1769 ava = null; 1770 avaType = null; 1771 1772 break; 1773 } 1774 1775 // Read the hashCode 1776 h = Serialize.deserializeInt( buffer, pos ); 1777 pos += 4; 1778 1779 return pos; 1780 } 1781 1782 1783 /** 1784 * A Rdn is composed of on to many Avas (AttributeType And Value). 1785 * We should write all those Avas sequencially, following the 1786 * structure : 1787 * <ul> 1788 * <li> 1789 * <b>parentId</b> The parent entry's Id 1790 * </li> 1791 * <li> 1792 * <b>nbAvas</b> The number of Avas to write. Can't be 0. 1793 * </li> 1794 * <li> 1795 * <b>upName</b> The User provided Rdn 1796 * </li> 1797 * <li> 1798 * <b>normName</b> The normalized Rdn. It can be empty if the normalized 1799 * name equals the upName. 1800 * </li> 1801 * <li> 1802 * <b>Avas</b> 1803 * </li> 1804 * </ul> 1805 * <br> 1806 * For each Ava : 1807 * <ul> 1808 * <li> 1809 * <b>start</b> The position of this Ava in the upName string 1810 * </li> 1811 * <li> 1812 * <b>length</b> The Ava user provided length 1813 * </li> 1814 * <li> 1815 * <b>Call the Ava write method</b> The Ava itself 1816 * </li> 1817 * </ul> 1818 * 1819 * @see Externalizable#readExternal(ObjectInput) 1820 * @param out The stream into which the serialized Rdn will be put 1821 * @throws IOException If the stream can't be written 1822 */ 1823 @Override 1824 public void writeExternal( ObjectOutput out ) throws IOException 1825 { 1826 out.writeInt( nbAvas ); 1827 out.writeUTF( upName ); 1828 1829 if ( upName.equals( normName ) ) 1830 { 1831 out.writeUTF( "" ); 1832 } 1833 else 1834 { 1835 out.writeUTF( normName ); 1836 } 1837 1838 switch ( nbAvas ) 1839 { 1840 case 0: 1841 break; 1842 1843 case 1: 1844 ava.writeExternal( out ); 1845 break; 1846 1847 default: 1848 for ( Ava localAva : avas ) 1849 { 1850 localAva.writeExternal( out ); 1851 } 1852 1853 break; 1854 } 1855 1856 out.writeInt( h ); 1857 1858 out.flush(); 1859 } 1860 1861 1862 /** 1863 * We read back the data to create a new RDB. The structure 1864 * read is exposed in the {@link Rdn#writeExternal(ObjectOutput)} 1865 * method 1866 * 1867 * @see Externalizable#readExternal(ObjectInput) 1868 * @param in The input stream from which the Rdn will be read 1869 * @throws IOException If we can't read from the input stream 1870 * @throws ClassNotFoundException If we can't create a new Rdn 1871 */ 1872 @Override 1873 public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException 1874 { 1875 // Read the Ava number 1876 nbAvas = in.readInt(); 1877 1878 // Read the UPName 1879 upName = in.readUTF(); 1880 1881 // Read the normName 1882 normName = in.readUTF(); 1883 1884 if ( Strings.isEmpty( normName ) ) 1885 { 1886 normName = upName; 1887 } 1888 1889 switch ( nbAvas ) 1890 { 1891 case 0: 1892 ava = null; 1893 break; 1894 1895 case 1: 1896 ava = new Ava( schemaManager ); 1897 ava.readExternal( in ); 1898 avaType = ava.getNormType(); 1899 1900 break; 1901 1902 default: 1903 avas = new ArrayList<>(); 1904 1905 avaTypes = new MultiValueMap(); 1906 1907 for ( int i = 0; i < nbAvas; i++ ) 1908 { 1909 Ava newAva = new Ava( schemaManager ); 1910 newAva.readExternal( in ); 1911 avas.add( newAva ); 1912 avaTypes.put( newAva.getNormType(), newAva ); 1913 } 1914 1915 ava = null; 1916 avaType = null; 1917 1918 break; 1919 } 1920 1921 h = in.readInt(); 1922 } 1923 1924 1925 @Override 1926 public int compareTo( Rdn arg0 ) 1927 { 1928 return 0; 1929 } 1930 1931 1932 /** 1933 * @return a String representation of the Rdn. The caller will get back the user 1934 * provided Rdn 1935 */ 1936 @Override 1937 public String toString() 1938 { 1939 return upName == null ? "" : upName; 1940 } 1941}