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