001/* 002 * Copyright 2007-2014 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2014 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 names The set of names for this name form. It may 329 * be {@code null} or empty if the name form 330 * should 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 isObsolete Indicates whether this name form is declared 334 * obsolete. 335 * @param structuralClass The name or OID of the structural object class 336 * with which this name form is associated. It 337 * must not be {@code null}. 338 * @param requiredAttributes The names/OIDs of the attributes which must be 339 * present the RDN for entries with the associated 340 * structural class. It must not be {@code null} 341 * or empty. 342 * @param optionalAttributes The names/OIDs of the attributes which may 343 * optionally be present in the RDN for entries 344 * with the associated structural class. It may 345 * be {@code null} or empty 346 * @param extensions The set of extensions for this name form. It 347 * may be {@code null} or empty if there should 348 * not be any extensions. 349 */ 350 public NameFormDefinition(final String oid, final String[] names, 351 final String description, 352 final boolean isObsolete, 353 final String structuralClass, 354 final String[] requiredAttributes, 355 final String[] optionalAttributes, 356 final Map<String,String[]> extensions) 357 { 358 ensureNotNull(oid, structuralClass, requiredAttributes); 359 ensureFalse(requiredAttributes.length == 0); 360 361 this.oid = oid; 362 this.isObsolete = isObsolete; 363 this.description = description; 364 this.structuralClass = structuralClass; 365 this.requiredAttributes = requiredAttributes; 366 367 if (names == null) 368 { 369 this.names = NO_STRINGS; 370 } 371 else 372 { 373 this.names = names; 374 } 375 376 if (optionalAttributes == null) 377 { 378 this.optionalAttributes = NO_STRINGS; 379 } 380 else 381 { 382 this.optionalAttributes = optionalAttributes; 383 } 384 385 if (extensions == null) 386 { 387 this.extensions = Collections.emptyMap(); 388 } 389 else 390 { 391 this.extensions = Collections.unmodifiableMap(extensions); 392 } 393 394 final StringBuilder buffer = new StringBuilder(); 395 createDefinitionString(buffer); 396 nameFormString = buffer.toString(); 397 } 398 399 400 401 /** 402 * Constructs a string representation of this name form definition in the 403 * provided buffer. 404 * 405 * @param buffer The buffer in which to construct a string representation of 406 * this name form definition. 407 */ 408 private void createDefinitionString(final StringBuilder buffer) 409 { 410 buffer.append("( "); 411 buffer.append(oid); 412 413 if (names.length == 1) 414 { 415 buffer.append(" NAME '"); 416 buffer.append(names[0]); 417 buffer.append('\''); 418 } 419 else if (names.length > 1) 420 { 421 buffer.append(" NAME ("); 422 for (final String name : names) 423 { 424 buffer.append(" '"); 425 buffer.append(name); 426 buffer.append('\''); 427 } 428 buffer.append(" )"); 429 } 430 431 if (description != null) 432 { 433 buffer.append(" DESC '"); 434 encodeValue(description, buffer); 435 buffer.append('\''); 436 } 437 438 if (isObsolete) 439 { 440 buffer.append(" OBSOLETE"); 441 } 442 443 buffer.append(" OC "); 444 buffer.append(structuralClass); 445 446 if (requiredAttributes.length == 1) 447 { 448 buffer.append(" MUST "); 449 buffer.append(requiredAttributes[0]); 450 } 451 else if (requiredAttributes.length > 1) 452 { 453 buffer.append(" MUST ("); 454 for (int i=0; i < requiredAttributes.length; i++) 455 { 456 if (i >0) 457 { 458 buffer.append(" $ "); 459 } 460 else 461 { 462 buffer.append(' '); 463 } 464 buffer.append(requiredAttributes[i]); 465 } 466 buffer.append(" )"); 467 } 468 469 if (optionalAttributes.length == 1) 470 { 471 buffer.append(" MAY "); 472 buffer.append(optionalAttributes[0]); 473 } 474 else if (optionalAttributes.length > 1) 475 { 476 buffer.append(" MAY ("); 477 for (int i=0; i < optionalAttributes.length; i++) 478 { 479 if (i > 0) 480 { 481 buffer.append(" $ "); 482 } 483 else 484 { 485 buffer.append(' '); 486 } 487 buffer.append(optionalAttributes[i]); 488 } 489 buffer.append(" )"); 490 } 491 492 for (final Map.Entry<String,String[]> e : extensions.entrySet()) 493 { 494 final String name = e.getKey(); 495 final String[] values = e.getValue(); 496 if (values.length == 1) 497 { 498 buffer.append(' '); 499 buffer.append(name); 500 buffer.append(" '"); 501 encodeValue(values[0], buffer); 502 buffer.append('\''); 503 } 504 else 505 { 506 buffer.append(' '); 507 buffer.append(name); 508 buffer.append(" ("); 509 for (final String value : values) 510 { 511 buffer.append(" '"); 512 encodeValue(value, buffer); 513 buffer.append('\''); 514 } 515 buffer.append(" )"); 516 } 517 } 518 519 buffer.append(" )"); 520 } 521 522 523 524 /** 525 * Retrieves the OID for this name form. 526 * 527 * @return The OID for this name form. 528 */ 529 public String getOID() 530 { 531 return oid; 532 } 533 534 535 536 /** 537 * Retrieves the set of names for this name form. 538 * 539 * @return The set of names for this name form, or an empty array if it does 540 * not have any names. 541 */ 542 public String[] getNames() 543 { 544 return names; 545 } 546 547 548 549 /** 550 * Retrieves the primary name that can be used to reference this name form. 551 * If one or more names are defined, then the first name will be used. 552 * Otherwise, the OID will be returned. 553 * 554 * @return The primary name that can be used to reference this name form. 555 */ 556 public String getNameOrOID() 557 { 558 if (names.length == 0) 559 { 560 return oid; 561 } 562 else 563 { 564 return names[0]; 565 } 566 } 567 568 569 570 /** 571 * Indicates whether the provided string matches the OID or any of the names 572 * for this name form. 573 * 574 * @param s The string for which to make the determination. It must not be 575 * {@code null}. 576 * 577 * @return {@code true} if the provided string matches the OID or any of the 578 * names for this name form, or {@code false} if not. 579 */ 580 public boolean hasNameOrOID(final String s) 581 { 582 for (final String name : names) 583 { 584 if (s.equalsIgnoreCase(name)) 585 { 586 return true; 587 } 588 } 589 590 return s.equalsIgnoreCase(oid); 591 } 592 593 594 595 /** 596 * Retrieves the description for this name form, if available. 597 * 598 * @return The description for this name form, or {@code null} if there is no 599 * description defined. 600 */ 601 public String getDescription() 602 { 603 return description; 604 } 605 606 607 608 /** 609 * Indicates whether this name form is declared obsolete. 610 * 611 * @return {@code true} if this name form is declared obsolete, or 612 * {@code false} if it is not. 613 */ 614 public boolean isObsolete() 615 { 616 return isObsolete; 617 } 618 619 620 621 /** 622 * Retrieves the name or OID of the structural object class associated with 623 * this name form. 624 * 625 * @return The name or OID of the structural object class associated with 626 * this name form. 627 */ 628 public String getStructuralClass() 629 { 630 return structuralClass; 631 } 632 633 634 635 /** 636 * Retrieves the names or OIDs of the attributes that are required to be 637 * present in the RDN of entries with the associated structural object class. 638 * 639 * @return The names or OIDs of the attributes that are required to be 640 * present in the RDN of entries with the associated structural 641 * object class. 642 */ 643 public String[] getRequiredAttributes() 644 { 645 return requiredAttributes; 646 } 647 648 649 650 /** 651 * Retrieves the names or OIDs of the attributes that may optionally be 652 * present in the RDN of entries with the associated structural object class. 653 * 654 * @return The names or OIDs of the attributes that may optionally be 655 * present in the RDN of entries with the associated structural 656 * object class, or an empty array if there are no optional 657 * attributes. 658 */ 659 public String[] getOptionalAttributes() 660 { 661 return optionalAttributes; 662 } 663 664 665 666 /** 667 * Retrieves the set of extensions for this name form. They will be mapped 668 * from the extension name (which should start with "X-") to the set of values 669 * for that extension. 670 * 671 * @return The set of extensions for this name form. 672 */ 673 public Map<String,String[]> getExtensions() 674 { 675 return extensions; 676 } 677 678 679 680 /** 681 * {@inheritDoc} 682 */ 683 @Override() 684 public int hashCode() 685 { 686 return oid.hashCode(); 687 } 688 689 690 691 /** 692 * {@inheritDoc} 693 */ 694 @Override() 695 public boolean equals(final Object o) 696 { 697 if (o == null) 698 { 699 return false; 700 } 701 702 if (o == this) 703 { 704 return true; 705 } 706 707 if (! (o instanceof NameFormDefinition)) 708 { 709 return false; 710 } 711 712 final NameFormDefinition d = (NameFormDefinition) o; 713 return (oid.equals(d.oid) && 714 structuralClass.equalsIgnoreCase(d.structuralClass) && 715 stringsEqualIgnoreCaseOrderIndependent(names, d.names) && 716 stringsEqualIgnoreCaseOrderIndependent(requiredAttributes, 717 d.requiredAttributes) && 718 stringsEqualIgnoreCaseOrderIndependent(optionalAttributes, 719 d.optionalAttributes) && 720 bothNullOrEqualIgnoreCase(description, d.description) && 721 (isObsolete == d.isObsolete) && 722 extensionsEqual(extensions, d.extensions)); 723 } 724 725 726 727 /** 728 * Retrieves a string representation of this name form definition, in the 729 * format described in RFC 4512 section 4.1.7.2. 730 * 731 * @return A string representation of this name form definition. 732 */ 733 @Override() 734 public String toString() 735 { 736 return nameFormString; 737 } 738}