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