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 DIT content rule
044 * schema element.
045 */
046 @NotMutable()
047 @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
048 public final class DITContentRuleDefinition
049 extends SchemaElement
050 {
051 /**
052 * The serial version UID for this serializable class.
053 */
054 private static final long serialVersionUID = 3224440505307817586L;
055
056
057
058 // Indicates whether this DIT content rule is declared obsolete.
059 private final boolean isObsolete;
060
061 // The set of extensions for this DIT content rule.
062 private final Map<String,String[]> extensions;
063
064 // The description for this DIT content rule.
065 private final String description;
066
067 // The string representation of this DIT content rule.
068 private final String ditContentRuleString;
069
070 // The OID of the structural object class with which this DIT content rule is
071 // associated.
072 private final String oid;
073
074 // The names/OIDs of the allowed auxiliary classes.
075 private final String[] auxiliaryClasses;
076
077 // The set of names for this DIT content rule.
078 private final String[] names;
079
080 // The names/OIDs of the optional attributes.
081 private final String[] optionalAttributes;
082
083 // The names/OIDs of the prohibited attributes.
084 private final String[] prohibitedAttributes;
085
086 // The names/OIDs of the required attributes.
087 private final String[] requiredAttributes;
088
089
090
091 /**
092 * Creates a new DIT content rule from the provided string representation.
093 *
094 * @param s The string representation of the DIT content rule to create,
095 * using the syntax described in RFC 4512 section 4.1.6. It must
096 * not be {@code null}.
097 *
098 * @throws LDAPException If the provided string cannot be decoded as a DIT
099 * content rule definition.
100 */
101 public DITContentRuleDefinition(final String s)
102 throws LDAPException
103 {
104 ensureNotNull(s);
105
106 ditContentRuleString = s.trim();
107
108 // The first character must be an opening parenthesis.
109 final int length = ditContentRuleString.length();
110 if (length == 0)
111 {
112 throw new LDAPException(ResultCode.DECODING_ERROR,
113 ERR_DCR_DECODE_EMPTY.get());
114 }
115 else if (ditContentRuleString.charAt(0) != '(')
116 {
117 throw new LDAPException(ResultCode.DECODING_ERROR,
118 ERR_DCR_DECODE_NO_OPENING_PAREN.get(
119 ditContentRuleString));
120 }
121
122
123 // Skip over any spaces until we reach the start of the OID, then read the
124 // OID until we find the next space.
125 int pos = skipSpaces(ditContentRuleString, 1, length);
126
127 StringBuilder buffer = new StringBuilder();
128 pos = readOID(ditContentRuleString, pos, length, buffer);
129 oid = buffer.toString();
130
131
132 // Technically, DIT content elements are supposed to appear in a specific
133 // order, but we'll be lenient and allow remaining elements to come in any
134 // order.
135 final ArrayList<String> nameList = new ArrayList<String>(1);
136 final ArrayList<String> reqAttrs = new ArrayList<String>();
137 final ArrayList<String> optAttrs = new ArrayList<String>();
138 final ArrayList<String> notAttrs = new ArrayList<String>();
139 final ArrayList<String> auxOCs = new ArrayList<String>();
140 final Map<String,String[]> exts = new LinkedHashMap<String,String[]>();
141 Boolean obsolete = null;
142 String descr = null;
143
144 while (true)
145 {
146 // Skip over any spaces until we find the next element.
147 pos = skipSpaces(ditContentRuleString, pos, length);
148
149 // Read until we find the next space or the end of the string. Use that
150 // token to figure out what to do next.
151 final int tokenStartPos = pos;
152 while ((pos < length) && (ditContentRuleString.charAt(pos) != ' '))
153 {
154 pos++;
155 }
156
157 final String token = ditContentRuleString.substring(tokenStartPos, pos);
158 final String lowerToken = toLowerCase(token);
159 if (lowerToken.equals(")"))
160 {
161 // This indicates that we're at the end of the value. There should not
162 // be any more closing characters.
163 if (pos < length)
164 {
165 throw new LDAPException(ResultCode.DECODING_ERROR,
166 ERR_DCR_DECODE_CLOSE_NOT_AT_END.get(
167 ditContentRuleString));
168 }
169 break;
170 }
171 else if (lowerToken.equals("name"))
172 {
173 if (nameList.isEmpty())
174 {
175 pos = skipSpaces(ditContentRuleString, pos, length);
176 pos = readQDStrings(ditContentRuleString, pos, length, nameList);
177 }
178 else
179 {
180 throw new LDAPException(ResultCode.DECODING_ERROR,
181 ERR_DCR_DECODE_MULTIPLE_ELEMENTS.get(
182 ditContentRuleString, "NAME"));
183 }
184 }
185 else if (lowerToken.equals("desc"))
186 {
187 if (descr == null)
188 {
189 pos = skipSpaces(ditContentRuleString, pos, length);
190
191 buffer = new StringBuilder();
192 pos = readQDString(ditContentRuleString, pos, length, buffer);
193 descr = buffer.toString();
194 }
195 else
196 {
197 throw new LDAPException(ResultCode.DECODING_ERROR,
198 ERR_DCR_DECODE_MULTIPLE_ELEMENTS.get(
199 ditContentRuleString, "DESC"));
200 }
201 }
202 else if (lowerToken.equals("obsolete"))
203 {
204 if (obsolete == null)
205 {
206 obsolete = true;
207 }
208 else
209 {
210 throw new LDAPException(ResultCode.DECODING_ERROR,
211 ERR_DCR_DECODE_MULTIPLE_ELEMENTS.get(
212 ditContentRuleString, "OBSOLETE"));
213 }
214 }
215 else if (lowerToken.equals("aux"))
216 {
217 if (auxOCs.isEmpty())
218 {
219 pos = skipSpaces(ditContentRuleString, pos, length);
220 pos = readOIDs(ditContentRuleString, pos, length, auxOCs);
221 }
222 else
223 {
224 throw new LDAPException(ResultCode.DECODING_ERROR,
225 ERR_DCR_DECODE_MULTIPLE_ELEMENTS.get(
226 ditContentRuleString, "AUX"));
227 }
228 }
229 else if (lowerToken.equals("must"))
230 {
231 if (reqAttrs.isEmpty())
232 {
233 pos = skipSpaces(ditContentRuleString, pos, length);
234 pos = readOIDs(ditContentRuleString, pos, length, reqAttrs);
235 }
236 else
237 {
238 throw new LDAPException(ResultCode.DECODING_ERROR,
239 ERR_DCR_DECODE_MULTIPLE_ELEMENTS.get(
240 ditContentRuleString, "MUST"));
241 }
242 }
243 else if (lowerToken.equals("may"))
244 {
245 if (optAttrs.isEmpty())
246 {
247 pos = skipSpaces(ditContentRuleString, pos, length);
248 pos = readOIDs(ditContentRuleString, pos, length, optAttrs);
249 }
250 else
251 {
252 throw new LDAPException(ResultCode.DECODING_ERROR,
253 ERR_DCR_DECODE_MULTIPLE_ELEMENTS.get(
254 ditContentRuleString, "MAY"));
255 }
256 }
257 else if (lowerToken.equals("not"))
258 {
259 if (notAttrs.isEmpty())
260 {
261 pos = skipSpaces(ditContentRuleString, pos, length);
262 pos = readOIDs(ditContentRuleString, pos, length, notAttrs);
263 }
264 else
265 {
266 throw new LDAPException(ResultCode.DECODING_ERROR,
267 ERR_DCR_DECODE_MULTIPLE_ELEMENTS.get(
268 ditContentRuleString, "NOT"));
269 }
270 }
271 else if (lowerToken.startsWith("x-"))
272 {
273 pos = skipSpaces(ditContentRuleString, pos, length);
274
275 final ArrayList<String> valueList = new ArrayList<String>();
276 pos = readQDStrings(ditContentRuleString, pos, length, valueList);
277
278 final String[] values = new String[valueList.size()];
279 valueList.toArray(values);
280
281 if (exts.containsKey(token))
282 {
283 throw new LDAPException(ResultCode.DECODING_ERROR,
284 ERR_DCR_DECODE_DUP_EXT.get(
285 ditContentRuleString, token));
286 }
287
288 exts.put(token, values);
289 }
290 else
291 {
292 throw new LDAPException(ResultCode.DECODING_ERROR,
293 ERR_DCR_DECODE_DUP_EXT.get(
294 ditContentRuleString, token));
295 }
296 }
297
298 description = descr;
299
300 names = new String[nameList.size()];
301 nameList.toArray(names);
302
303 auxiliaryClasses = new String[auxOCs.size()];
304 auxOCs.toArray(auxiliaryClasses);
305
306 requiredAttributes = new String[reqAttrs.size()];
307 reqAttrs.toArray(requiredAttributes);
308
309 optionalAttributes = new String[optAttrs.size()];
310 optAttrs.toArray(optionalAttributes);
311
312 prohibitedAttributes = new String[notAttrs.size()];
313 notAttrs.toArray(prohibitedAttributes);
314
315 isObsolete = (obsolete != null);
316
317 extensions = Collections.unmodifiableMap(exts);
318 }
319
320
321
322 /**
323 * Creates a new DIT content rule with the provided information.
324 *
325 * @param oid The OID for the structural object class with
326 * which this DIT content rule is associated.
327 * It must not be {@code null}.
328 * @param names The set of names for this DIT content rule.
329 * It may be {@code null} or empty if the DIT
330 * content rule should only be referenced by
331 * OID.
332 * @param description The description for this DIT content rule.
333 * It may be {@code null} if there is no
334 * description.
335 * @param isObsolete Indicates whether this DIT content rule is
336 * declared obsolete.
337 * @param auxiliaryClasses The names/OIDs of the auxiliary object
338 * classes that may be present in entries
339 * containing this DIT content rule.
340 * @param requiredAttributes The names/OIDs of the attributes which must
341 * be present in entries containing this DIT
342 * content rule.
343 * @param optionalAttributes The names/OIDs of the attributes which may be
344 * present in entries containing this DIT
345 * content rule.
346 * @param prohibitedAttributes The names/OIDs of the attributes which may
347 * not be present in entries containing this DIT
348 * content rule.
349 * @param extensions The set of extensions for this DIT content
350 * rule. It may be {@code null} or empty if
351 * there should not be any extensions.
352 */
353 public DITContentRuleDefinition(final String oid, final String[] names,
354 final String description,
355 final boolean isObsolete,
356 final String[] auxiliaryClasses,
357 final String[] requiredAttributes,
358 final String[] optionalAttributes,
359 final String[] prohibitedAttributes,
360 final Map<String,String[]> extensions)
361 {
362 ensureNotNull(oid);
363
364 this.oid = oid;
365 this.isObsolete = isObsolete;
366 this.description = description;
367
368 if (names == null)
369 {
370 this.names = NO_STRINGS;
371 }
372 else
373 {
374 this.names = names;
375 }
376
377 if (auxiliaryClasses == null)
378 {
379 this.auxiliaryClasses = NO_STRINGS;
380 }
381 else
382 {
383 this.auxiliaryClasses = auxiliaryClasses;
384 }
385
386 if (requiredAttributes == null)
387 {
388 this.requiredAttributes = NO_STRINGS;
389 }
390 else
391 {
392 this.requiredAttributes = requiredAttributes;
393 }
394
395 if (optionalAttributes == null)
396 {
397 this.optionalAttributes = NO_STRINGS;
398 }
399 else
400 {
401 this.optionalAttributes = optionalAttributes;
402 }
403
404 if (prohibitedAttributes == null)
405 {
406 this.prohibitedAttributes = NO_STRINGS;
407 }
408 else
409 {
410 this.prohibitedAttributes = prohibitedAttributes;
411 }
412
413 if (extensions == null)
414 {
415 this.extensions = Collections.emptyMap();
416 }
417 else
418 {
419 this.extensions = Collections.unmodifiableMap(extensions);
420 }
421
422 final StringBuilder buffer = new StringBuilder();
423 createDefinitionString(buffer);
424 ditContentRuleString = buffer.toString();
425 }
426
427
428
429 /**
430 * Constructs a string representation of this DIT content rule definition in
431 * the provided buffer.
432 *
433 * @param buffer The buffer in which to construct a string representation of
434 * this DIT content rule definition.
435 */
436 private void createDefinitionString(final StringBuilder buffer)
437 {
438 buffer.append("( ");
439 buffer.append(oid);
440
441 if (names.length == 1)
442 {
443 buffer.append(" NAME '");
444 buffer.append(names[0]);
445 buffer.append('\'');
446 }
447 else if (names.length > 1)
448 {
449 buffer.append(" NAME (");
450 for (final String name : names)
451 {
452 buffer.append(" '");
453 buffer.append(name);
454 buffer.append('\'');
455 }
456 buffer.append(" )");
457 }
458
459 if (description != null)
460 {
461 buffer.append(" DESC '");
462 encodeValue(description, buffer);
463 buffer.append('\'');
464 }
465
466 if (isObsolete)
467 {
468 buffer.append(" OBSOLETE");
469 }
470
471 if (auxiliaryClasses.length == 1)
472 {
473 buffer.append(" AUX ");
474 buffer.append(auxiliaryClasses[0]);
475 }
476 else if (auxiliaryClasses.length > 1)
477 {
478 buffer.append(" AUX (");
479 for (int i=0; i < auxiliaryClasses.length; i++)
480 {
481 if (i >0)
482 {
483 buffer.append(" $ ");
484 }
485 else
486 {
487 buffer.append(' ');
488 }
489 buffer.append(auxiliaryClasses[i]);
490 }
491 buffer.append(" )");
492 }
493
494 if (requiredAttributes.length == 1)
495 {
496 buffer.append(" MUST ");
497 buffer.append(requiredAttributes[0]);
498 }
499 else if (requiredAttributes.length > 1)
500 {
501 buffer.append(" MUST (");
502 for (int i=0; i < requiredAttributes.length; i++)
503 {
504 if (i >0)
505 {
506 buffer.append(" $ ");
507 }
508 else
509 {
510 buffer.append(' ');
511 }
512 buffer.append(requiredAttributes[i]);
513 }
514 buffer.append(" )");
515 }
516
517 if (optionalAttributes.length == 1)
518 {
519 buffer.append(" MAY ");
520 buffer.append(optionalAttributes[0]);
521 }
522 else if (optionalAttributes.length > 1)
523 {
524 buffer.append(" MAY (");
525 for (int i=0; i < optionalAttributes.length; i++)
526 {
527 if (i > 0)
528 {
529 buffer.append(" $ ");
530 }
531 else
532 {
533 buffer.append(' ');
534 }
535 buffer.append(optionalAttributes[i]);
536 }
537 buffer.append(" )");
538 }
539
540 if (prohibitedAttributes.length == 1)
541 {
542 buffer.append(" NOT ");
543 buffer.append(prohibitedAttributes[0]);
544 }
545 else if (prohibitedAttributes.length > 1)
546 {
547 buffer.append(" NOT (");
548 for (int i=0; i < prohibitedAttributes.length; i++)
549 {
550 if (i > 0)
551 {
552 buffer.append(" $ ");
553 }
554 else
555 {
556 buffer.append(' ');
557 }
558 buffer.append(prohibitedAttributes[i]);
559 }
560 buffer.append(" )");
561 }
562
563 for (final Map.Entry<String,String[]> e : extensions.entrySet())
564 {
565 final String name = e.getKey();
566 final String[] values = e.getValue();
567 if (values.length == 1)
568 {
569 buffer.append(' ');
570 buffer.append(name);
571 buffer.append(" '");
572 encodeValue(values[0], buffer);
573 buffer.append('\'');
574 }
575 else
576 {
577 buffer.append(' ');
578 buffer.append(name);
579 buffer.append(" (");
580 for (final String value : values)
581 {
582 buffer.append(" '");
583 encodeValue(value, buffer);
584 buffer.append('\'');
585 }
586 buffer.append(" )");
587 }
588 }
589
590 buffer.append(" )");
591 }
592
593
594
595 /**
596 * Retrieves the OID for the structural object class associated with this
597 * DIT content rule.
598 *
599 * @return The OID for the structural object class associated with this DIT
600 * content rule.
601 */
602 public String getOID()
603 {
604 return oid;
605 }
606
607
608
609 /**
610 * Retrieves the set of names for this DIT content rule.
611 *
612 * @return The set of names for this DIT content rule, or an empty array if
613 * it does not have any names.
614 */
615 public String[] getNames()
616 {
617 return names;
618 }
619
620
621
622 /**
623 * Retrieves the primary name that can be used to reference this DIT content
624 * rule. If one or more names are defined, then the first name will be used.
625 * Otherwise, the structural object class OID will be returned.
626 *
627 * @return The primary name that can be used to reference this DIT content
628 * rule.
629 */
630 public String getNameOrOID()
631 {
632 if (names.length == 0)
633 {
634 return oid;
635 }
636 else
637 {
638 return names[0];
639 }
640 }
641
642
643
644 /**
645 * Indicates whether the provided string matches the OID or any of the names
646 * for this DIT content rule.
647 *
648 * @param s The string for which to make the determination. It must not be
649 * {@code null}.
650 *
651 * @return {@code true} if the provided string matches the OID or any of the
652 * names for this DIT content rule, or {@code false} if not.
653 */
654 public boolean hasNameOrOID(final String s)
655 {
656 for (final String name : names)
657 {
658 if (s.equalsIgnoreCase(name))
659 {
660 return true;
661 }
662 }
663
664 return s.equalsIgnoreCase(oid);
665 }
666
667
668
669 /**
670 * Retrieves the description for this DIT content rule, if available.
671 *
672 * @return The description for this DIT content rule, or {@code null} if
673 * there is no description defined.
674 */
675 public String getDescription()
676 {
677 return description;
678 }
679
680
681
682 /**
683 * Indicates whether this DIT content rule is declared obsolete.
684 *
685 * @return {@code true} if this DIT content rule is declared obsolete, or
686 * {@code false} if it is not.
687 */
688 public boolean isObsolete()
689 {
690 return isObsolete;
691 }
692
693
694
695 /**
696 * Retrieves the names or OIDs of the auxiliary object classes that may be
697 * present in entries containing the structural class for this DIT content
698 * rule.
699 *
700 * @return The names or OIDs of the auxiliary object classes that may be
701 * present in entries containing the structural class for this DIT
702 * content rule.
703 */
704 public String[] getAuxiliaryClasses()
705 {
706 return auxiliaryClasses;
707 }
708
709
710
711 /**
712 * Retrieves the names or OIDs of the attributes that are required to be
713 * present in entries containing the structural object class for this DIT
714 * content rule.
715 *
716 * @return The names or OIDs of the attributes that are required to be
717 * present in entries containing the structural object class for this
718 * DIT content rule, or an empty array if there are no required
719 * attributes.
720 */
721 public String[] getRequiredAttributes()
722 {
723 return requiredAttributes;
724 }
725
726
727
728 /**
729 * Retrieves the names or OIDs of the attributes that are optionally allowed
730 * to be present in entries containing the structural object class for this
731 * DIT content rule.
732 *
733 * @return The names or OIDs of the attributes that are optionally allowed to
734 * be present in entries containing the structural object class for
735 * this DIT content rule, or an empty array if there are no required
736 * attributes.
737 */
738 public String[] getOptionalAttributes()
739 {
740 return optionalAttributes;
741 }
742
743
744
745 /**
746 * Retrieves the names or OIDs of the attributes that are not allowed to be
747 * present in entries containing the structural object class for this DIT
748 * content rule.
749 *
750 * @return The names or OIDs of the attributes that are not allowed to be
751 * present in entries containing the structural object class for this
752 * DIT content rule, or an empty array if there are no required
753 * attributes.
754 */
755 public String[] getProhibitedAttributes()
756 {
757 return prohibitedAttributes;
758 }
759
760
761
762 /**
763 * Retrieves the set of extensions for this DIT content rule. They will be
764 * mapped from the extension name (which should start with "X-") to the set of
765 * values for that extension.
766 *
767 * @return The set of extensions for this DIT content rule.
768 */
769 public Map<String,String[]> getExtensions()
770 {
771 return extensions;
772 }
773
774
775
776 /**
777 * {@inheritDoc}
778 */
779 @Override()
780 public int hashCode()
781 {
782 return oid.hashCode();
783 }
784
785
786
787 /**
788 * {@inheritDoc}
789 */
790 @Override()
791 public boolean equals(final Object o)
792 {
793 if (o == null)
794 {
795 return false;
796 }
797
798 if (o == this)
799 {
800 return true;
801 }
802
803 if (! (o instanceof DITContentRuleDefinition))
804 {
805 return false;
806 }
807
808 final DITContentRuleDefinition d = (DITContentRuleDefinition) o;
809 return (oid.equals(d.oid) &&
810 stringsEqualIgnoreCaseOrderIndependent(names, d.names) &&
811 stringsEqualIgnoreCaseOrderIndependent(auxiliaryClasses,
812 d.auxiliaryClasses) &&
813 stringsEqualIgnoreCaseOrderIndependent(requiredAttributes,
814 d.requiredAttributes) &&
815 stringsEqualIgnoreCaseOrderIndependent(optionalAttributes,
816 d.optionalAttributes) &&
817 stringsEqualIgnoreCaseOrderIndependent(prohibitedAttributes,
818 d.prohibitedAttributes) &&
819 bothNullOrEqualIgnoreCase(description, d.description) &&
820 (isObsolete == d.isObsolete) &&
821 extensionsEqual(extensions, d.extensions));
822 }
823
824
825
826 /**
827 * Retrieves a string representation of this DIT content rule definition, in
828 * the format described in RFC 4512 section 4.1.6.
829 *
830 * @return A string representation of this DIT content rule definition.
831 */
832 @Override()
833 public String toString()
834 {
835 return ditContentRuleString;
836 }
837 }