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