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