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