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