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