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