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 }