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 }