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