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