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