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