001/* 002 * Copyright 2007-2017 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2017 UnboundID Corp. 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU General Public License (GPLv2 only) 010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 011 * as published by the Free Software Foundation. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Public License for more details. 017 * 018 * You should have received a copy of the GNU General Public License 019 * along with this program; if not, see <http://www.gnu.org/licenses>. 020 */ 021package com.unboundid.ldap.sdk.schema; 022 023 024 025import java.util.ArrayList; 026import java.util.Collections; 027import java.util.Map; 028import java.util.LinkedHashMap; 029 030import com.unboundid.ldap.sdk.LDAPException; 031import com.unboundid.ldap.sdk.ResultCode; 032import com.unboundid.util.NotMutable; 033import com.unboundid.util.ThreadSafety; 034import com.unboundid.util.ThreadSafetyLevel; 035 036import static com.unboundid.ldap.sdk.schema.SchemaMessages.*; 037import static com.unboundid.util.StaticUtils.*; 038import static com.unboundid.util.Validator.*; 039 040 041 042/** 043 * This class provides a data structure that describes an LDAP name form schema 044 * element. 045 */ 046@NotMutable() 047@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 048public final class NameFormDefinition 049 extends SchemaElement 050{ 051 /** 052 * The serial version UID for this serializable class. 053 */ 054 private static final long serialVersionUID = -816231530223449984L; 055 056 057 058 // Indicates whether this name form is declared obsolete. 059 private final boolean isObsolete; 060 061 // The set of extensions for this name form. 062 private final Map<String,String[]> extensions; 063 064 // The description for this name form. 065 private final String description; 066 067 // The string representation of this name form. 068 private final String nameFormString; 069 070 // The OID for this name form. 071 private final String oid; 072 073 // The set of names for this name form. 074 private final String[] names; 075 076 // The name or OID of the structural object class with which this name form 077 // is associated. 078 private final String structuralClass; 079 080 // The names/OIDs of the optional attributes. 081 private final String[] optionalAttributes; 082 083 // The names/OIDs of the required attributes. 084 private final String[] requiredAttributes; 085 086 087 088 /** 089 * Creates a new name form from the provided string representation. 090 * 091 * @param s The string representation of the name form to create, using the 092 * syntax described in RFC 4512 section 4.1.7.2. It must not be 093 * {@code null}. 094 * 095 * @throws LDAPException If the provided string cannot be decoded as a name 096 * form definition. 097 */ 098 public NameFormDefinition(final String s) 099 throws LDAPException 100 { 101 ensureNotNull(s); 102 103 nameFormString = s.trim(); 104 105 // The first character must be an opening parenthesis. 106 final int length = nameFormString.length(); 107 if (length == 0) 108 { 109 throw new LDAPException(ResultCode.DECODING_ERROR, 110 ERR_NF_DECODE_EMPTY.get()); 111 } 112 else if (nameFormString.charAt(0) != '(') 113 { 114 throw new LDAPException(ResultCode.DECODING_ERROR, 115 ERR_NF_DECODE_NO_OPENING_PAREN.get( 116 nameFormString)); 117 } 118 119 120 // Skip over any spaces until we reach the start of the OID, then read the 121 // OID until we find the next space. 122 int pos = skipSpaces(nameFormString, 1, length); 123 124 StringBuilder buffer = new StringBuilder(); 125 pos = readOID(nameFormString, pos, length, buffer); 126 oid = buffer.toString(); 127 128 129 // Technically, name form elements are supposed to appear in a specific 130 // order, but we'll be lenient and allow remaining elements to come in any 131 // order. 132 final ArrayList<String> nameList = new ArrayList<String>(1); 133 final ArrayList<String> reqAttrs = new ArrayList<String>(); 134 final ArrayList<String> optAttrs = new ArrayList<String>(); 135 final Map<String,String[]> exts = new LinkedHashMap<String,String[]>(); 136 Boolean obsolete = null; 137 String descr = null; 138 String oc = null; 139 140 while (true) 141 { 142 // Skip over any spaces until we find the next element. 143 pos = skipSpaces(nameFormString, pos, length); 144 145 // Read until we find the next space or the end of the string. Use that 146 // token to figure out what to do next. 147 final int tokenStartPos = pos; 148 while ((pos < length) && (nameFormString.charAt(pos) != ' ')) 149 { 150 pos++; 151 } 152 153 // It's possible that the token could be smashed right up against the 154 // closing parenthesis. If that's the case, then extract just the token 155 // and handle the closing parenthesis the next time through. 156 String token = nameFormString.substring(tokenStartPos, pos); 157 if ((token.length() > 1) && (token.endsWith(")"))) 158 { 159 token = token.substring(0, token.length() - 1); 160 pos--; 161 } 162 163 final String lowerToken = toLowerCase(token); 164 if (lowerToken.equals(")")) 165 { 166 // This indicates that we're at the end of the value. There should not 167 // be any more closing characters. 168 if (pos < length) 169 { 170 throw new LDAPException(ResultCode.DECODING_ERROR, 171 ERR_NF_DECODE_CLOSE_NOT_AT_END.get( 172 nameFormString)); 173 } 174 break; 175 } 176 else if (lowerToken.equals("name")) 177 { 178 if (nameList.isEmpty()) 179 { 180 pos = skipSpaces(nameFormString, pos, length); 181 pos = readQDStrings(nameFormString, pos, length, nameList); 182 } 183 else 184 { 185 throw new LDAPException(ResultCode.DECODING_ERROR, 186 ERR_NF_DECODE_MULTIPLE_ELEMENTS.get( 187 nameFormString, "NAME")); 188 } 189 } 190 else if (lowerToken.equals("desc")) 191 { 192 if (descr == null) 193 { 194 pos = skipSpaces(nameFormString, pos, length); 195 196 buffer = new StringBuilder(); 197 pos = readQDString(nameFormString, pos, length, buffer); 198 descr = buffer.toString(); 199 } 200 else 201 { 202 throw new LDAPException(ResultCode.DECODING_ERROR, 203 ERR_NF_DECODE_MULTIPLE_ELEMENTS.get( 204 nameFormString, "DESC")); 205 } 206 } 207 else if (lowerToken.equals("obsolete")) 208 { 209 if (obsolete == null) 210 { 211 obsolete = true; 212 } 213 else 214 { 215 throw new LDAPException(ResultCode.DECODING_ERROR, 216 ERR_NF_DECODE_MULTIPLE_ELEMENTS.get( 217 nameFormString, "OBSOLETE")); 218 } 219 } 220 else if (lowerToken.equals("oc")) 221 { 222 if (oc == null) 223 { 224 pos = skipSpaces(nameFormString, pos, length); 225 226 buffer = new StringBuilder(); 227 pos = readOID(nameFormString, pos, length, buffer); 228 oc = buffer.toString(); 229 } 230 else 231 { 232 throw new LDAPException(ResultCode.DECODING_ERROR, 233 ERR_NF_DECODE_MULTIPLE_ELEMENTS.get( 234 nameFormString, "OC")); 235 } 236 } 237 else if (lowerToken.equals("must")) 238 { 239 if (reqAttrs.isEmpty()) 240 { 241 pos = skipSpaces(nameFormString, pos, length); 242 pos = readOIDs(nameFormString, pos, length, reqAttrs); 243 } 244 else 245 { 246 throw new LDAPException(ResultCode.DECODING_ERROR, 247 ERR_NF_DECODE_MULTIPLE_ELEMENTS.get( 248 nameFormString, "MUST")); 249 } 250 } 251 else if (lowerToken.equals("may")) 252 { 253 if (optAttrs.isEmpty()) 254 { 255 pos = skipSpaces(nameFormString, pos, length); 256 pos = readOIDs(nameFormString, pos, length, optAttrs); 257 } 258 else 259 { 260 throw new LDAPException(ResultCode.DECODING_ERROR, 261 ERR_NF_DECODE_MULTIPLE_ELEMENTS.get( 262 nameFormString, "MAY")); 263 } 264 } 265 else if (lowerToken.startsWith("x-")) 266 { 267 pos = skipSpaces(nameFormString, pos, length); 268 269 final ArrayList<String> valueList = new ArrayList<String>(); 270 pos = readQDStrings(nameFormString, pos, length, valueList); 271 272 final String[] values = new String[valueList.size()]; 273 valueList.toArray(values); 274 275 if (exts.containsKey(token)) 276 { 277 throw new LDAPException(ResultCode.DECODING_ERROR, 278 ERR_NF_DECODE_DUP_EXT.get(nameFormString, 279 token)); 280 } 281 282 exts.put(token, values); 283 } 284 else 285 { 286 throw new LDAPException(ResultCode.DECODING_ERROR, 287 ERR_NF_DECODE_UNEXPECTED_TOKEN.get( 288 nameFormString, token)); 289 } 290 } 291 292 description = descr; 293 structuralClass = oc; 294 295 if (structuralClass == null) 296 { 297 throw new LDAPException(ResultCode.DECODING_ERROR, 298 ERR_NF_DECODE_NO_OC.get(nameFormString)); 299 } 300 301 names = new String[nameList.size()]; 302 nameList.toArray(names); 303 304 requiredAttributes = new String[reqAttrs.size()]; 305 reqAttrs.toArray(requiredAttributes); 306 307 if (reqAttrs.isEmpty()) 308 { 309 throw new LDAPException(ResultCode.DECODING_ERROR, 310 ERR_NF_DECODE_NO_MUST.get(nameFormString)); 311 } 312 313 optionalAttributes = new String[optAttrs.size()]; 314 optAttrs.toArray(optionalAttributes); 315 316 isObsolete = (obsolete != null); 317 318 extensions = Collections.unmodifiableMap(exts); 319 } 320 321 322 323 /** 324 * Creates a new name form with the provided information. 325 * 326 * @param oid The OID for this name form. It must not be 327 * {@code null}. 328 * @param name The name for this name form. It may be 329 * {@code null} or empty if the name form should 330 * only be referenced by OID. 331 * @param description The description for this name form. It may be 332 * {@code null} if there is no description. 333 * @param structuralClass The name or OID of the structural object class 334 * with which this name form is associated. It 335 * must not be {@code null}. 336 * @param requiredAttribute he name or OID of the attribute which must be 337 * present the RDN for entries with the associated 338 * structural class. It must not be {@code null}. 339 * @param extensions The set of extensions for this name form. It 340 * may be {@code null} or empty if there should 341 * not be any extensions. 342 */ 343 public NameFormDefinition(final String oid, final String name, 344 final String description, 345 final String structuralClass, 346 final String requiredAttribute, 347 final Map<String,String[]> extensions) 348 { 349 this(oid, ((name == null) ? null : new String[] { name }), description, 350 false, structuralClass, new String[] { requiredAttribute }, null, 351 extensions); 352 } 353 354 355 356 /** 357 * Creates a new name form with the provided information. 358 * 359 * @param oid The OID for this name form. It must not be 360 * {@code null}. 361 * @param names The set of names for this name form. It may 362 * be {@code null} or empty if the name form 363 * should only be referenced by OID. 364 * @param description The description for this name form. It may be 365 * {@code null} if there is no description. 366 * @param isObsolete Indicates whether this name form is declared 367 * obsolete. 368 * @param structuralClass The name or OID of the structural object class 369 * with which this name form is associated. It 370 * must not be {@code null}. 371 * @param requiredAttributes The names/OIDs of the attributes which must be 372 * present the RDN for entries with the associated 373 * structural class. It must not be {@code null} 374 * or empty. 375 * @param optionalAttributes The names/OIDs of the attributes which may 376 * optionally be present in the RDN for entries 377 * with the associated structural class. It may 378 * be {@code null} or empty 379 * @param extensions The set of extensions for this name form. It 380 * may be {@code null} or empty if there should 381 * not be any extensions. 382 */ 383 public NameFormDefinition(final String oid, final String[] names, 384 final String description, 385 final boolean isObsolete, 386 final String structuralClass, 387 final String[] requiredAttributes, 388 final String[] optionalAttributes, 389 final Map<String,String[]> extensions) 390 { 391 ensureNotNull(oid, structuralClass, requiredAttributes); 392 ensureFalse(requiredAttributes.length == 0); 393 394 this.oid = oid; 395 this.isObsolete = isObsolete; 396 this.description = description; 397 this.structuralClass = structuralClass; 398 this.requiredAttributes = requiredAttributes; 399 400 if (names == null) 401 { 402 this.names = NO_STRINGS; 403 } 404 else 405 { 406 this.names = names; 407 } 408 409 if (optionalAttributes == null) 410 { 411 this.optionalAttributes = NO_STRINGS; 412 } 413 else 414 { 415 this.optionalAttributes = optionalAttributes; 416 } 417 418 if (extensions == null) 419 { 420 this.extensions = Collections.emptyMap(); 421 } 422 else 423 { 424 this.extensions = Collections.unmodifiableMap(extensions); 425 } 426 427 final StringBuilder buffer = new StringBuilder(); 428 createDefinitionString(buffer); 429 nameFormString = buffer.toString(); 430 } 431 432 433 434 /** 435 * Constructs a string representation of this name form definition in the 436 * provided buffer. 437 * 438 * @param buffer The buffer in which to construct a string representation of 439 * this name form definition. 440 */ 441 private void createDefinitionString(final StringBuilder buffer) 442 { 443 buffer.append("( "); 444 buffer.append(oid); 445 446 if (names.length == 1) 447 { 448 buffer.append(" NAME '"); 449 buffer.append(names[0]); 450 buffer.append('\''); 451 } 452 else if (names.length > 1) 453 { 454 buffer.append(" NAME ("); 455 for (final String name : names) 456 { 457 buffer.append(" '"); 458 buffer.append(name); 459 buffer.append('\''); 460 } 461 buffer.append(" )"); 462 } 463 464 if (description != null) 465 { 466 buffer.append(" DESC '"); 467 encodeValue(description, buffer); 468 buffer.append('\''); 469 } 470 471 if (isObsolete) 472 { 473 buffer.append(" OBSOLETE"); 474 } 475 476 buffer.append(" OC "); 477 buffer.append(structuralClass); 478 479 if (requiredAttributes.length == 1) 480 { 481 buffer.append(" MUST "); 482 buffer.append(requiredAttributes[0]); 483 } 484 else if (requiredAttributes.length > 1) 485 { 486 buffer.append(" MUST ("); 487 for (int i=0; i < requiredAttributes.length; i++) 488 { 489 if (i >0) 490 { 491 buffer.append(" $ "); 492 } 493 else 494 { 495 buffer.append(' '); 496 } 497 buffer.append(requiredAttributes[i]); 498 } 499 buffer.append(" )"); 500 } 501 502 if (optionalAttributes.length == 1) 503 { 504 buffer.append(" MAY "); 505 buffer.append(optionalAttributes[0]); 506 } 507 else if (optionalAttributes.length > 1) 508 { 509 buffer.append(" MAY ("); 510 for (int i=0; i < optionalAttributes.length; i++) 511 { 512 if (i > 0) 513 { 514 buffer.append(" $ "); 515 } 516 else 517 { 518 buffer.append(' '); 519 } 520 buffer.append(optionalAttributes[i]); 521 } 522 buffer.append(" )"); 523 } 524 525 for (final Map.Entry<String,String[]> e : extensions.entrySet()) 526 { 527 final String name = e.getKey(); 528 final String[] values = e.getValue(); 529 if (values.length == 1) 530 { 531 buffer.append(' '); 532 buffer.append(name); 533 buffer.append(" '"); 534 encodeValue(values[0], buffer); 535 buffer.append('\''); 536 } 537 else 538 { 539 buffer.append(' '); 540 buffer.append(name); 541 buffer.append(" ("); 542 for (final String value : values) 543 { 544 buffer.append(" '"); 545 encodeValue(value, buffer); 546 buffer.append('\''); 547 } 548 buffer.append(" )"); 549 } 550 } 551 552 buffer.append(" )"); 553 } 554 555 556 557 /** 558 * Retrieves the OID for this name form. 559 * 560 * @return The OID for this name form. 561 */ 562 public String getOID() 563 { 564 return oid; 565 } 566 567 568 569 /** 570 * Retrieves the set of names for this name form. 571 * 572 * @return The set of names for this name form, or an empty array if it does 573 * not have any names. 574 */ 575 public String[] getNames() 576 { 577 return names; 578 } 579 580 581 582 /** 583 * Retrieves the primary name that can be used to reference this name form. 584 * If one or more names are defined, then the first name will be used. 585 * Otherwise, the OID will be returned. 586 * 587 * @return The primary name that can be used to reference this name form. 588 */ 589 public String getNameOrOID() 590 { 591 if (names.length == 0) 592 { 593 return oid; 594 } 595 else 596 { 597 return names[0]; 598 } 599 } 600 601 602 603 /** 604 * Indicates whether the provided string matches the OID or any of the names 605 * for this name form. 606 * 607 * @param s The string for which to make the determination. It must not be 608 * {@code null}. 609 * 610 * @return {@code true} if the provided string matches the OID or any of the 611 * names for this name form, or {@code false} if not. 612 */ 613 public boolean hasNameOrOID(final String s) 614 { 615 for (final String name : names) 616 { 617 if (s.equalsIgnoreCase(name)) 618 { 619 return true; 620 } 621 } 622 623 return s.equalsIgnoreCase(oid); 624 } 625 626 627 628 /** 629 * Retrieves the description for this name form, if available. 630 * 631 * @return The description for this name form, or {@code null} if there is no 632 * description defined. 633 */ 634 public String getDescription() 635 { 636 return description; 637 } 638 639 640 641 /** 642 * Indicates whether this name form is declared obsolete. 643 * 644 * @return {@code true} if this name form is declared obsolete, or 645 * {@code false} if it is not. 646 */ 647 public boolean isObsolete() 648 { 649 return isObsolete; 650 } 651 652 653 654 /** 655 * Retrieves the name or OID of the structural object class associated with 656 * this name form. 657 * 658 * @return The name or OID of the structural object class associated with 659 * this name form. 660 */ 661 public String getStructuralClass() 662 { 663 return structuralClass; 664 } 665 666 667 668 /** 669 * Retrieves the names or OIDs of the attributes that are required to be 670 * present in the RDN of entries with the associated structural object class. 671 * 672 * @return The names or OIDs of the attributes that are required to be 673 * present in the RDN of entries with the associated structural 674 * object class. 675 */ 676 public String[] getRequiredAttributes() 677 { 678 return requiredAttributes; 679 } 680 681 682 683 /** 684 * Retrieves the names or OIDs of the attributes that may optionally be 685 * present in the RDN of entries with the associated structural object class. 686 * 687 * @return The names or OIDs of the attributes that may optionally be 688 * present in the RDN of entries with the associated structural 689 * object class, or an empty array if there are no optional 690 * attributes. 691 */ 692 public String[] getOptionalAttributes() 693 { 694 return optionalAttributes; 695 } 696 697 698 699 /** 700 * Retrieves the set of extensions for this name form. They will be mapped 701 * from the extension name (which should start with "X-") to the set of values 702 * for that extension. 703 * 704 * @return The set of extensions for this name form. 705 */ 706 public Map<String,String[]> getExtensions() 707 { 708 return extensions; 709 } 710 711 712 713 /** 714 * {@inheritDoc} 715 */ 716 @Override() 717 public int hashCode() 718 { 719 return oid.hashCode(); 720 } 721 722 723 724 /** 725 * {@inheritDoc} 726 */ 727 @Override() 728 public boolean equals(final Object o) 729 { 730 if (o == null) 731 { 732 return false; 733 } 734 735 if (o == this) 736 { 737 return true; 738 } 739 740 if (! (o instanceof NameFormDefinition)) 741 { 742 return false; 743 } 744 745 final NameFormDefinition d = (NameFormDefinition) o; 746 return (oid.equals(d.oid) && 747 structuralClass.equalsIgnoreCase(d.structuralClass) && 748 stringsEqualIgnoreCaseOrderIndependent(names, d.names) && 749 stringsEqualIgnoreCaseOrderIndependent(requiredAttributes, 750 d.requiredAttributes) && 751 stringsEqualIgnoreCaseOrderIndependent(optionalAttributes, 752 d.optionalAttributes) && 753 bothNullOrEqualIgnoreCase(description, d.description) && 754 (isObsolete == d.isObsolete) && 755 extensionsEqual(extensions, d.extensions)); 756 } 757 758 759 760 /** 761 * Retrieves a string representation of this name form definition, in the 762 * format described in RFC 4512 section 4.1.7.2. 763 * 764 * @return A string representation of this name form definition. 765 */ 766 @Override() 767 public String toString() 768 { 769 return nameFormString; 770 } 771}