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