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