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