001 /*
002 * Copyright 2007-2013 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2008-2013 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 final String token = matchingRuleUseString.substring(tokenStartPos, pos);
145 final String lowerToken = toLowerCase(token);
146 if (lowerToken.equals(")"))
147 {
148 // This indicates that we're at the end of the value. There should not
149 // be any more closing characters.
150 if (pos < length)
151 {
152 throw new LDAPException(ResultCode.DECODING_ERROR,
153 ERR_MRU_DECODE_CLOSE_NOT_AT_END.get(
154 matchingRuleUseString));
155 }
156 break;
157 }
158 else if (lowerToken.equals("name"))
159 {
160 if (nameList.isEmpty())
161 {
162 pos = skipSpaces(matchingRuleUseString, pos, length);
163 pos = readQDStrings(matchingRuleUseString, pos, length, nameList);
164 }
165 else
166 {
167 throw new LDAPException(ResultCode.DECODING_ERROR,
168 ERR_MRU_DECODE_MULTIPLE_ELEMENTS.get(
169 matchingRuleUseString, "NAME"));
170 }
171 }
172 else if (lowerToken.equals("desc"))
173 {
174 if (descr == null)
175 {
176 pos = skipSpaces(matchingRuleUseString, pos, length);
177
178 buffer = new StringBuilder();
179 pos = readQDString(matchingRuleUseString, pos, length, buffer);
180 descr = buffer.toString();
181 }
182 else
183 {
184 throw new LDAPException(ResultCode.DECODING_ERROR,
185 ERR_MRU_DECODE_MULTIPLE_ELEMENTS.get(
186 matchingRuleUseString, "DESC"));
187 }
188 }
189 else if (lowerToken.equals("obsolete"))
190 {
191 if (obsolete == null)
192 {
193 obsolete = true;
194 }
195 else
196 {
197 throw new LDAPException(ResultCode.DECODING_ERROR,
198 ERR_MRU_DECODE_MULTIPLE_ELEMENTS.get(
199 matchingRuleUseString, "OBSOLETE"));
200 }
201 }
202 else if (lowerToken.equals("applies"))
203 {
204 if (typeList.isEmpty())
205 {
206 pos = skipSpaces(matchingRuleUseString, pos, length);
207 pos = readOIDs(matchingRuleUseString, pos, length, typeList);
208 }
209 else
210 {
211 throw new LDAPException(ResultCode.DECODING_ERROR,
212 ERR_MRU_DECODE_MULTIPLE_ELEMENTS.get(
213 matchingRuleUseString, "APPLIES"));
214 }
215 }
216 else if (lowerToken.startsWith("x-"))
217 {
218 pos = skipSpaces(matchingRuleUseString, pos, length);
219
220 final ArrayList<String> valueList = new ArrayList<String>();
221 pos = readQDStrings(matchingRuleUseString, pos, length, valueList);
222
223 final String[] values = new String[valueList.size()];
224 valueList.toArray(values);
225
226 if (exts.containsKey(token))
227 {
228 throw new LDAPException(ResultCode.DECODING_ERROR,
229 ERR_MRU_DECODE_DUP_EXT.get(
230 matchingRuleUseString, token));
231 }
232
233 exts.put(token, values);
234 }
235 else
236 {
237 throw new LDAPException(ResultCode.DECODING_ERROR,
238 ERR_MRU_DECODE_UNEXPECTED_TOKEN.get(
239 matchingRuleUseString, token));
240 }
241 }
242
243 description = descr;
244
245 names = new String[nameList.size()];
246 nameList.toArray(names);
247
248 if (typeList.isEmpty())
249 {
250 throw new LDAPException(ResultCode.DECODING_ERROR,
251 ERR_MRU_DECODE_NO_APPLIES.get(
252 matchingRuleUseString));
253 }
254
255 applicableTypes = new String[typeList.size()];
256 typeList.toArray(applicableTypes);
257
258 isObsolete = (obsolete != null);
259
260 extensions = Collections.unmodifiableMap(exts);
261 }
262
263
264
265 /**
266 * Creates a new matching rule use with the provided information.
267 *
268 * @param oid The OID for this matching rule use. It must not
269 * be {@code null}.
270 * @param names The set of names for this matching rule use. It
271 * may be {@code null} or empty if the matching rule
272 * should only be referenced by OID.
273 * @param description The description for this matching rule use. It
274 * may be {@code null} if there is no description.
275 * @param isObsolete Indicates whether this matching rule use is
276 * declared obsolete.
277 * @param applicableTypes The set of attribute types to which this matching
278 * rule use applies. It must not be empty or
279 * {@code null}.
280 * @param extensions The set of extensions for this matching rule use.
281 * It may be {@code null} or empty if there should
282 * not be any extensions.
283 */
284 public MatchingRuleUseDefinition(final String oid, final String[] names,
285 final String description,
286 final boolean isObsolete,
287 final String[] applicableTypes,
288 final Map<String,String[]> extensions)
289 {
290 ensureNotNull(oid, applicableTypes);
291 ensureFalse(applicableTypes.length == 0);
292
293 this.oid = oid;
294 this.description = description;
295 this.isObsolete = isObsolete;
296 this.applicableTypes = applicableTypes;
297
298 if (names == null)
299 {
300 this.names = NO_STRINGS;
301 }
302 else
303 {
304 this.names = names;
305 }
306
307 if (extensions == null)
308 {
309 this.extensions = Collections.emptyMap();
310 }
311 else
312 {
313 this.extensions = Collections.unmodifiableMap(extensions);
314 }
315
316 final StringBuilder buffer = new StringBuilder();
317 createDefinitionString(buffer);
318 matchingRuleUseString = buffer.toString();
319 }
320
321
322
323 /**
324 * Constructs a string representation of this matching rule use definition in
325 * the provided buffer.
326 *
327 * @param buffer The buffer in which to construct a string representation of
328 * this matching rule use definition.
329 */
330 private void createDefinitionString(final StringBuilder buffer)
331 {
332 buffer.append("( ");
333 buffer.append(oid);
334
335 if (names.length == 1)
336 {
337 buffer.append(" NAME '");
338 buffer.append(names[0]);
339 buffer.append('\'');
340 }
341 else if (names.length > 1)
342 {
343 buffer.append(" NAME (");
344 for (final String name : names)
345 {
346 buffer.append(" '");
347 buffer.append(name);
348 buffer.append('\'');
349 }
350 buffer.append(" )");
351 }
352
353 if (description != null)
354 {
355 buffer.append(" DESC '");
356 encodeValue(description, buffer);
357 buffer.append('\'');
358 }
359
360 if (isObsolete)
361 {
362 buffer.append(" OBSOLETE");
363 }
364
365 if (applicableTypes.length == 1)
366 {
367 buffer.append(" APPLIES ");
368 buffer.append(applicableTypes[0]);
369 }
370 else if (applicableTypes.length > 1)
371 {
372 buffer.append(" APPLIES (");
373 for (int i=0; i < applicableTypes.length; i++)
374 {
375 if (i > 0)
376 {
377 buffer.append(" $");
378 }
379
380 buffer.append(' ');
381 buffer.append(applicableTypes[i]);
382 }
383 buffer.append(" )");
384 }
385
386 for (final Map.Entry<String,String[]> e : extensions.entrySet())
387 {
388 final String name = e.getKey();
389 final String[] values = e.getValue();
390 if (values.length == 1)
391 {
392 buffer.append(' ');
393 buffer.append(name);
394 buffer.append(" '");
395 encodeValue(values[0], buffer);
396 buffer.append('\'');
397 }
398 else
399 {
400 buffer.append(' ');
401 buffer.append(name);
402 buffer.append(" (");
403 for (final String value : values)
404 {
405 buffer.append(" '");
406 encodeValue(value, buffer);
407 buffer.append('\'');
408 }
409 buffer.append(" )");
410 }
411 }
412
413 buffer.append(" )");
414 }
415
416
417
418 /**
419 * Retrieves the OID for this matching rule use.
420 *
421 * @return The OID for this matching rule use.
422 */
423 public String getOID()
424 {
425 return oid;
426 }
427
428
429
430 /**
431 * Retrieves the set of names for this matching rule use.
432 *
433 * @return The set of names for this matching rule use, or an empty array if
434 * it does not have any names.
435 */
436 public String[] getNames()
437 {
438 return names;
439 }
440
441
442
443 /**
444 * Retrieves the primary name that can be used to reference this matching
445 * rule use. If one or more names are defined, then the first name will be
446 * used. Otherwise, the OID will be returned.
447 *
448 * @return The primary name that can be used to reference this matching rule
449 * use.
450 */
451 public String getNameOrOID()
452 {
453 if (names.length == 0)
454 {
455 return oid;
456 }
457 else
458 {
459 return names[0];
460 }
461 }
462
463
464
465 /**
466 * Indicates whether the provided string matches the OID or any of the names
467 * for this matching rule use.
468 *
469 * @param s The string for which to make the determination. It must not be
470 * {@code null}.
471 *
472 * @return {@code true} if the provided string matches the OID or any of the
473 * names for this matching rule use, or {@code false} if not.
474 */
475 public boolean hasNameOrOID(final String s)
476 {
477 for (final String name : names)
478 {
479 if (s.equalsIgnoreCase(name))
480 {
481 return true;
482 }
483 }
484
485 return s.equalsIgnoreCase(oid);
486 }
487
488
489
490 /**
491 * Retrieves the description for this matching rule use, if available.
492 *
493 * @return The description for this matching rule use, or {@code null} if
494 * there is no description defined.
495 */
496 public String getDescription()
497 {
498 return description;
499 }
500
501
502
503 /**
504 * Indicates whether this matching rule use is declared obsolete.
505 *
506 * @return {@code true} if this matching rule use is declared obsolete, or
507 * {@code false} if it is not.
508 */
509 public boolean isObsolete()
510 {
511 return isObsolete;
512 }
513
514
515
516 /**
517 * Retrieves the names or OIDs of the attribute types to which this matching
518 * rule use applies.
519 *
520 * @return The names or OIDs of the attribute types to which this matching
521 * rule use applies.
522 */
523 public String[] getApplicableAttributeTypes()
524 {
525 return applicableTypes;
526 }
527
528
529
530 /**
531 * Retrieves the set of extensions for this matching rule use. They will be
532 * mapped from the extension name (which should start with "X-") to the set
533 * of values for that extension.
534 *
535 * @return The set of extensions for this matching rule use.
536 */
537 public Map<String,String[]> getExtensions()
538 {
539 return extensions;
540 }
541
542
543
544 /**
545 * {@inheritDoc}
546 */
547 @Override()
548 public int hashCode()
549 {
550 return oid.hashCode();
551 }
552
553
554
555 /**
556 * {@inheritDoc}
557 */
558 @Override()
559 public boolean equals(final Object o)
560 {
561 if (o == null)
562 {
563 return false;
564 }
565
566 if (o == this)
567 {
568 return true;
569 }
570
571 if (! (o instanceof MatchingRuleUseDefinition))
572 {
573 return false;
574 }
575
576 final MatchingRuleUseDefinition d = (MatchingRuleUseDefinition) o;
577 return (oid.equals(d.oid) &&
578 stringsEqualIgnoreCaseOrderIndependent(names, d.names) &&
579 stringsEqualIgnoreCaseOrderIndependent(applicableTypes,
580 d.applicableTypes) &&
581 bothNullOrEqualIgnoreCase(description, d.description) &&
582 (isObsolete == d.isObsolete) &&
583 extensionsEqual(extensions, d.extensions));
584 }
585
586
587
588 /**
589 * Retrieves a string representation of this matching rule definition, in the
590 * format described in RFC 4512 section 4.1.4.
591 *
592 * @return A string representation of this matching rule use definition.
593 */
594 @Override()
595 public String toString()
596 {
597 return matchingRuleUseString;
598 }
599 }