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