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.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 final String token = ditStructureRuleString.substring(tokenStartPos, pos);
171 final String lowerToken = toLowerCase(token);
172 if (lowerToken.equals(")"))
173 {
174 // This indicates that we're at the end of the value. There should not
175 // be any more closing characters.
176 if (pos < length)
177 {
178 throw new LDAPException(ResultCode.DECODING_ERROR,
179 ERR_DSR_DECODE_CLOSE_NOT_AT_END.get(
180 ditStructureRuleString));
181 }
182 break;
183 }
184 else if (lowerToken.equals("name"))
185 {
186 if (nameList.isEmpty())
187 {
188 pos = skipSpaces(ditStructureRuleString, pos, length);
189 pos = readQDStrings(ditStructureRuleString, pos, length, nameList);
190 }
191 else
192 {
193 throw new LDAPException(ResultCode.DECODING_ERROR,
194 ERR_DSR_DECODE_MULTIPLE_ELEMENTS.get(
195 ditStructureRuleString, "NAME"));
196 }
197 }
198 else if (lowerToken.equals("desc"))
199 {
200 if (descr == null)
201 {
202 pos = skipSpaces(ditStructureRuleString, pos, length);
203
204 buffer = new StringBuilder();
205 pos = readQDString(ditStructureRuleString, pos, length, buffer);
206 descr = buffer.toString();
207 }
208 else
209 {
210 throw new LDAPException(ResultCode.DECODING_ERROR,
211 ERR_DSR_DECODE_MULTIPLE_ELEMENTS.get(
212 ditStructureRuleString, "DESC"));
213 }
214 }
215 else if (lowerToken.equals("obsolete"))
216 {
217 if (obsolete == null)
218 {
219 obsolete = true;
220 }
221 else
222 {
223 throw new LDAPException(ResultCode.DECODING_ERROR,
224 ERR_DSR_DECODE_MULTIPLE_ELEMENTS.get(
225 ditStructureRuleString, "OBSOLETE"));
226 }
227 }
228 else if (lowerToken.equals("form"))
229 {
230 if (nfID == null)
231 {
232 pos = skipSpaces(ditStructureRuleString, pos, length);
233
234 buffer = new StringBuilder();
235 pos = readOID(ditStructureRuleString, pos, length, buffer);
236 nfID = buffer.toString();
237 }
238 else
239 {
240 throw new LDAPException(ResultCode.DECODING_ERROR,
241 ERR_DSR_DECODE_MULTIPLE_ELEMENTS.get(
242 ditStructureRuleString, "FORM"));
243 }
244 }
245 else if (lowerToken.equals("sup"))
246 {
247 if (supList.isEmpty())
248 {
249 final ArrayList<String> supStrs = new ArrayList<String>(1);
250
251 pos = skipSpaces(ditStructureRuleString, pos, length);
252 pos = readOIDs(ditStructureRuleString, pos, length, supStrs);
253
254 supList.ensureCapacity(supStrs.size());
255 for (final String supStr : supStrs)
256 {
257 try
258 {
259 supList.add(Integer.parseInt(supStr));
260 }
261 catch (NumberFormatException nfe)
262 {
263 debugException(nfe);
264 throw new LDAPException(ResultCode.DECODING_ERROR,
265 ERR_DSR_DECODE_SUP_ID_NOT_INT.get(
266 ditStructureRuleString),
267 nfe);
268 }
269 }
270 }
271 else
272 {
273 throw new LDAPException(ResultCode.DECODING_ERROR,
274 ERR_DSR_DECODE_MULTIPLE_ELEMENTS.get(
275 ditStructureRuleString, "SUP"));
276 }
277 }
278 else if (lowerToken.startsWith("x-"))
279 {
280 pos = skipSpaces(ditStructureRuleString, pos, length);
281
282 final ArrayList<String> valueList = new ArrayList<String>();
283 pos = readQDStrings(ditStructureRuleString, pos, length, valueList);
284
285 final String[] values = new String[valueList.size()];
286 valueList.toArray(values);
287
288 if (exts.containsKey(token))
289 {
290 throw new LDAPException(ResultCode.DECODING_ERROR,
291 ERR_DSR_DECODE_DUP_EXT.get(
292 ditStructureRuleString, token));
293 }
294
295 exts.put(token, values);
296 }
297 else
298 {
299 throw new LDAPException(ResultCode.DECODING_ERROR,
300 ERR_DSR_DECODE_UNEXPECTED_TOKEN.get(
301 ditStructureRuleString, token));
302 }
303 }
304
305 description = descr;
306 nameFormID = nfID;
307
308 if (nameFormID == null)
309 {
310 throw new LDAPException(ResultCode.DECODING_ERROR,
311 ERR_DSR_DECODE_NO_FORM.get(
312 ditStructureRuleString));
313 }
314
315 names = new String[nameList.size()];
316 nameList.toArray(names);
317
318 superiorRuleIDs = new int[supList.size()];
319 for (int i=0; i < superiorRuleIDs.length; i++)
320 {
321 superiorRuleIDs[i] = supList.get(i);
322 }
323
324 isObsolete = (obsolete != null);
325
326 extensions = Collections.unmodifiableMap(exts);
327 }
328
329
330
331 /**
332 * Creates a new DIT structure rule with the provided information.
333 *
334 * @param ruleID The rule ID for this DIT structure rule.
335 * @param names The set of names for this DIT structure rule. It
336 * may be {@code null} or empty if the DIT structure
337 * rule should only be referenced by rule ID.
338 * @param description The description for this DIT structure rule. It
339 * may be {@code null} if there is no description.
340 * @param isObsolete Indicates whether this DIT structure rule is
341 * declared obsolete.
342 * @param nameFormID The name or OID of the name form with which this
343 * DIT structure rule is associated. It must not be
344 * {@code null}.
345 * @param superiorRuleIDs The superior rule IDs for this DIT structure rule.
346 * It may be {@code null} or empty if there are no
347 * superior rule IDs.
348 * @param extensions The set of extensions for this DIT structure rule.
349 * It may be {@code null} or empty if there are no
350 * extensions.
351 */
352 public DITStructureRuleDefinition(final int ruleID, final String[] names,
353 final String description,
354 final boolean isObsolete,
355 final String nameFormID,
356 final int[] superiorRuleIDs,
357 final Map<String,String[]> extensions)
358 {
359 ensureNotNull(nameFormID);
360
361 this.ruleID = ruleID;
362 this.description = description;
363 this.isObsolete = isObsolete;
364 this.nameFormID = nameFormID;
365
366 if (names == null)
367 {
368 this.names = NO_STRINGS;
369 }
370 else
371 {
372 this.names = names;
373 }
374
375 if (superiorRuleIDs == null)
376 {
377 this.superiorRuleIDs = NO_INTS;
378 }
379 else
380 {
381 this.superiorRuleIDs = superiorRuleIDs;
382 }
383
384 if (extensions == null)
385 {
386 this.extensions = Collections.emptyMap();
387 }
388 else
389 {
390 this.extensions = Collections.unmodifiableMap(extensions);
391 }
392
393 final StringBuilder buffer = new StringBuilder();
394 createDefinitionString(buffer);
395 ditStructureRuleString = buffer.toString();
396 }
397
398
399
400 /**
401 * Constructs a string representation of this DIT content rule definition in
402 * the provided buffer.
403 *
404 * @param buffer The buffer in which to construct a string representation of
405 * this DIT content rule definition.
406 */
407 private void createDefinitionString(final StringBuilder buffer)
408 {
409 buffer.append("( ");
410 buffer.append(ruleID);
411
412 if (names.length == 1)
413 {
414 buffer.append(" NAME '");
415 buffer.append(names[0]);
416 buffer.append('\'');
417 }
418 else if (names.length > 1)
419 {
420 buffer.append(" NAME (");
421 for (final String name : names)
422 {
423 buffer.append(" '");
424 buffer.append(name);
425 buffer.append('\'');
426 }
427 buffer.append(" )");
428 }
429
430 if (description != null)
431 {
432 buffer.append(" DESC '");
433 encodeValue(description, buffer);
434 buffer.append('\'');
435 }
436
437 if (isObsolete)
438 {
439 buffer.append(" OBSOLETE");
440 }
441
442 buffer.append(" FORM ");
443 buffer.append(nameFormID);
444
445 if (superiorRuleIDs.length == 1)
446 {
447 buffer.append(" SUP ");
448 buffer.append(superiorRuleIDs[0]);
449 }
450 else if (superiorRuleIDs.length > 1)
451 {
452 buffer.append(" SUP (");
453 for (final int supID : superiorRuleIDs)
454 {
455 buffer.append(" $ ");
456 buffer.append(supID);
457 }
458 buffer.append(" )");
459 }
460
461 for (final Map.Entry<String,String[]> e : extensions.entrySet())
462 {
463 final String name = e.getKey();
464 final String[] values = e.getValue();
465 if (values.length == 1)
466 {
467 buffer.append(' ');
468 buffer.append(name);
469 buffer.append(" '");
470 encodeValue(values[0], buffer);
471 buffer.append('\'');
472 }
473 else
474 {
475 buffer.append(' ');
476 buffer.append(name);
477 buffer.append(" (");
478 for (final String value : values)
479 {
480 buffer.append(" '");
481 encodeValue(value, buffer);
482 buffer.append('\'');
483 }
484 buffer.append(" )");
485 }
486 }
487
488 buffer.append(" )");
489 }
490
491
492
493 /**
494 * Retrieves the rule ID for this DIT structure rule.
495 *
496 * @return The rule ID for this DIT structure rule.
497 */
498 public int getRuleID()
499 {
500 return ruleID;
501 }
502
503
504
505 /**
506 * Retrieves the set of names for this DIT structure rule.
507 *
508 * @return The set of names for this DIT structure rule, or an empty array if
509 * it does not have any names.
510 */
511 public String[] getNames()
512 {
513 return names;
514 }
515
516
517
518 /**
519 * Retrieves the primary name that can be used to reference this DIT structure
520 * rule. If one or more names are defined, then the first name will be used.
521 * Otherwise, the string representation of the rule ID will be returned.
522 *
523 * @return The primary name that can be used to reference this DIT structure
524 * rule.
525 */
526 public String getNameOrRuleID()
527 {
528 if (names.length == 0)
529 {
530 return String.valueOf(ruleID);
531 }
532 else
533 {
534 return names[0];
535 }
536 }
537
538
539
540 /**
541 * Indicates whether the provided string matches the rule ID or any of the
542 * names for this DIT structure rule.
543 *
544 * @param s The string for which to make the determination. It must not be
545 * {@code null}.
546 *
547 * @return {@code true} if the provided string matches the rule ID or any of
548 * the names for this DIT structure rule, or {@code false} if not.
549 */
550 public boolean hasNameOrRuleID(final String s)
551 {
552 for (final String name : names)
553 {
554 if (s.equalsIgnoreCase(name))
555 {
556 return true;
557 }
558 }
559
560 return s.equalsIgnoreCase(String.valueOf(ruleID));
561 }
562
563
564
565 /**
566 * Retrieves the description for this DIT structure rule, if available.
567 *
568 * @return The description for this DIT structure rule, or {@code null} if
569 * there is no description defined.
570 */
571 public String getDescription()
572 {
573 return description;
574 }
575
576
577
578 /**
579 * Indicates whether this DIT structure rule is declared obsolete.
580 *
581 * @return {@code true} if this DIT structure rule is declared obsolete, or
582 * {@code false} if it is not.
583 */
584 public boolean isObsolete()
585 {
586 return isObsolete;
587 }
588
589
590
591 /**
592 * Retrieves the name or OID of the name form with which this DIT structure
593 * rule is associated.
594 *
595 * @return The name or OID of the name form with which this DIT structure
596 * rule is associated.
597 */
598 public String getNameFormID()
599 {
600 return nameFormID;
601 }
602
603
604
605 /**
606 * Retrieves the rule IDs of the superior rules for this DIT structure rule.
607 *
608 * @return The rule IDs of the superior rules for this DIT structure rule, or
609 * an empty array if there are no superior rule IDs.
610 */
611 public int[] getSuperiorRuleIDs()
612 {
613 return superiorRuleIDs;
614 }
615
616
617
618 /**
619 * Retrieves the set of extensions for this DIT structure rule. They will be
620 * mapped from the extension name (which should start with "X-") to the set of
621 * values for that extension.
622 *
623 * @return The set of extensions for this DIT structure rule.
624 */
625 public Map<String,String[]> getExtensions()
626 {
627 return extensions;
628 }
629
630
631
632 /**
633 * {@inheritDoc}
634 */
635 @Override()
636 public int hashCode()
637 {
638 return ruleID;
639 }
640
641
642
643 /**
644 * {@inheritDoc}
645 */
646 @Override()
647 public boolean equals(final Object o)
648 {
649 if (o == null)
650 {
651 return false;
652 }
653
654 if (o == this)
655 {
656 return true;
657 }
658
659 if (! (o instanceof DITStructureRuleDefinition))
660 {
661 return false;
662 }
663
664 final DITStructureRuleDefinition d = (DITStructureRuleDefinition) o;
665 if ((ruleID == d.ruleID) &&
666 nameFormID.equalsIgnoreCase(d.nameFormID) &&
667 stringsEqualIgnoreCaseOrderIndependent(names, d.names) &&
668 (isObsolete == d.isObsolete) &&
669 extensionsEqual(extensions, d.extensions))
670 {
671 if (superiorRuleIDs.length != d.superiorRuleIDs.length)
672 {
673 return false;
674 }
675
676 final HashSet<Integer> s1 = new HashSet<Integer>(superiorRuleIDs.length);
677 final HashSet<Integer> s2 = new HashSet<Integer>(superiorRuleIDs.length);
678 for (final int i : superiorRuleIDs)
679 {
680 s1.add(i);
681 }
682
683 for (final int i : d.superiorRuleIDs)
684 {
685 s2.add(i);
686 }
687
688 return s1.equals(s2);
689 }
690 else
691 {
692 return false;
693 }
694 }
695
696
697
698 /**
699 * Retrieves a string representation of this DIT structure rule definition, in
700 * the format described in RFC 4512 section 4.1.7.1.
701 *
702 * @return A string representation of this DIT structure rule definition.
703 */
704 @Override()
705 public String toString()
706 {
707 return ditStructureRuleString;
708 }
709 }