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 import com.unboundid.util.NotMutable;
034 import com.unboundid.util.ThreadSafety;
035 import com.unboundid.util.ThreadSafetyLevel;
036
037 import static com.unboundid.ldap.sdk.schema.SchemaMessages.*;
038 import static com.unboundid.util.StaticUtils.*;
039 import 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)
049 public 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 }