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