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 names The set of names for this name form. It may
324 * be {@code null} or empty if the name form
325 * should 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 isObsolete Indicates whether this name form is declared
329 * obsolete.
330 * @param structuralClass The name or OID of the structural object class
331 * with which this name form is associated. It
332 * must not be {@code null}.
333 * @param requiredAttributes The names/OIDs of the attributes which must be
334 * present the RDN for entries with the associated
335 * structural class. It must not be {@code null}
336 * or empty.
337 * @param optionalAttributes The names/OIDs of the attributes which may
338 * optionally be present in the RDN for entries
339 * with the associated structural class. It may
340 * be {@code null} or empty
341 * @param extensions The set of extensions for this name form. It
342 * may be {@code null} or empty if there should
343 * not be any extensions.
344 */
345 public NameFormDefinition(final String oid, final String[] names,
346 final String description,
347 final boolean isObsolete,
348 final String structuralClass,
349 final String[] requiredAttributes,
350 final String[] optionalAttributes,
351 final Map<String,String[]> extensions)
352 {
353 ensureNotNull(oid, structuralClass, requiredAttributes);
354 ensureFalse(requiredAttributes.length == 0);
355
356 this.oid = oid;
357 this.isObsolete = isObsolete;
358 this.description = description;
359 this.structuralClass = structuralClass;
360 this.requiredAttributes = requiredAttributes;
361
362 if (names == null)
363 {
364 this.names = NO_STRINGS;
365 }
366 else
367 {
368 this.names = names;
369 }
370
371 if (optionalAttributes == null)
372 {
373 this.optionalAttributes = NO_STRINGS;
374 }
375 else
376 {
377 this.optionalAttributes = optionalAttributes;
378 }
379
380 if (extensions == null)
381 {
382 this.extensions = Collections.emptyMap();
383 }
384 else
385 {
386 this.extensions = Collections.unmodifiableMap(extensions);
387 }
388
389 final StringBuilder buffer = new StringBuilder();
390 createDefinitionString(buffer);
391 nameFormString = buffer.toString();
392 }
393
394
395
396 /**
397 * Constructs a string representation of this name form definition in the
398 * provided buffer.
399 *
400 * @param buffer The buffer in which to construct a string representation of
401 * this name form definition.
402 */
403 private void createDefinitionString(final StringBuilder buffer)
404 {
405 buffer.append("( ");
406 buffer.append(oid);
407
408 if (names.length == 1)
409 {
410 buffer.append(" NAME '");
411 buffer.append(names[0]);
412 buffer.append('\'');
413 }
414 else if (names.length > 1)
415 {
416 buffer.append(" NAME (");
417 for (final String name : names)
418 {
419 buffer.append(" '");
420 buffer.append(name);
421 buffer.append('\'');
422 }
423 buffer.append(" )");
424 }
425
426 if (description != null)
427 {
428 buffer.append(" DESC '");
429 encodeValue(description, buffer);
430 buffer.append('\'');
431 }
432
433 if (isObsolete)
434 {
435 buffer.append(" OBSOLETE");
436 }
437
438 buffer.append(" OC ");
439 buffer.append(structuralClass);
440
441 if (requiredAttributes.length == 1)
442 {
443 buffer.append(" MUST ");
444 buffer.append(requiredAttributes[0]);
445 }
446 else if (requiredAttributes.length > 1)
447 {
448 buffer.append(" MUST (");
449 for (int i=0; i < requiredAttributes.length; i++)
450 {
451 if (i >0)
452 {
453 buffer.append(" $ ");
454 }
455 else
456 {
457 buffer.append(' ');
458 }
459 buffer.append(requiredAttributes[i]);
460 }
461 buffer.append(" )");
462 }
463
464 if (optionalAttributes.length == 1)
465 {
466 buffer.append(" MAY ");
467 buffer.append(optionalAttributes[0]);
468 }
469 else if (optionalAttributes.length > 1)
470 {
471 buffer.append(" MAY (");
472 for (int i=0; i < optionalAttributes.length; i++)
473 {
474 if (i > 0)
475 {
476 buffer.append(" $ ");
477 }
478 else
479 {
480 buffer.append(' ');
481 }
482 buffer.append(optionalAttributes[i]);
483 }
484 buffer.append(" )");
485 }
486
487 for (final Map.Entry<String,String[]> e : extensions.entrySet())
488 {
489 final String name = e.getKey();
490 final String[] values = e.getValue();
491 if (values.length == 1)
492 {
493 buffer.append(' ');
494 buffer.append(name);
495 buffer.append(" '");
496 encodeValue(values[0], buffer);
497 buffer.append('\'');
498 }
499 else
500 {
501 buffer.append(' ');
502 buffer.append(name);
503 buffer.append(" (");
504 for (final String value : values)
505 {
506 buffer.append(" '");
507 encodeValue(value, buffer);
508 buffer.append('\'');
509 }
510 buffer.append(" )");
511 }
512 }
513
514 buffer.append(" )");
515 }
516
517
518
519 /**
520 * Retrieves the OID for this name form.
521 *
522 * @return The OID for this name form.
523 */
524 public String getOID()
525 {
526 return oid;
527 }
528
529
530
531 /**
532 * Retrieves the set of names for this name form.
533 *
534 * @return The set of names for this name form, or an empty array if it does
535 * not have any names.
536 */
537 public String[] getNames()
538 {
539 return names;
540 }
541
542
543
544 /**
545 * Retrieves the primary name that can be used to reference this name form.
546 * If one or more names are defined, then the first name will be used.
547 * Otherwise, the OID will be returned.
548 *
549 * @return The primary name that can be used to reference this name form.
550 */
551 public String getNameOrOID()
552 {
553 if (names.length == 0)
554 {
555 return oid;
556 }
557 else
558 {
559 return names[0];
560 }
561 }
562
563
564
565 /**
566 * Indicates whether the provided string matches the OID or any of the names
567 * for this name form.
568 *
569 * @param s The string for which to make the determination. It must not be
570 * {@code null}.
571 *
572 * @return {@code true} if the provided string matches the OID or any of the
573 * names for this name form, or {@code false} if not.
574 */
575 public boolean hasNameOrOID(final String s)
576 {
577 for (final String name : names)
578 {
579 if (s.equalsIgnoreCase(name))
580 {
581 return true;
582 }
583 }
584
585 return s.equalsIgnoreCase(oid);
586 }
587
588
589
590 /**
591 * Retrieves the description for this name form, if available.
592 *
593 * @return The description for this name form, or {@code null} if there is no
594 * description defined.
595 */
596 public String getDescription()
597 {
598 return description;
599 }
600
601
602
603 /**
604 * Indicates whether this name form is declared obsolete.
605 *
606 * @return {@code true} if this name form is declared obsolete, or
607 * {@code false} if it is not.
608 */
609 public boolean isObsolete()
610 {
611 return isObsolete;
612 }
613
614
615
616 /**
617 * Retrieves the name or OID of the structural object class associated with
618 * this name form.
619 *
620 * @return The name or OID of the structural object class associated with
621 * this name form.
622 */
623 public String getStructuralClass()
624 {
625 return structuralClass;
626 }
627
628
629
630 /**
631 * Retrieves the names or OIDs of the attributes that are required to be
632 * present in the RDN of entries with the associated structural object class.
633 *
634 * @return The names or OIDs of the attributes that are required to be
635 * present in the RDN of entries with the associated structural
636 * object class.
637 */
638 public String[] getRequiredAttributes()
639 {
640 return requiredAttributes;
641 }
642
643
644
645 /**
646 * Retrieves the names or OIDs of the attributes that may optionally be
647 * present in the RDN of entries with the associated structural object class.
648 *
649 * @return The names or OIDs of the attributes that may optionally be
650 * present in the RDN of entries with the associated structural
651 * object class, or an empty array if there are no optional
652 * attributes.
653 */
654 public String[] getOptionalAttributes()
655 {
656 return optionalAttributes;
657 }
658
659
660
661 /**
662 * Retrieves the set of extensions for this name form. They will be mapped
663 * from the extension name (which should start with "X-") to the set of values
664 * for that extension.
665 *
666 * @return The set of extensions for this name form.
667 */
668 public Map<String,String[]> getExtensions()
669 {
670 return extensions;
671 }
672
673
674
675 /**
676 * {@inheritDoc}
677 */
678 @Override()
679 public int hashCode()
680 {
681 return oid.hashCode();
682 }
683
684
685
686 /**
687 * {@inheritDoc}
688 */
689 @Override()
690 public boolean equals(final Object o)
691 {
692 if (o == null)
693 {
694 return false;
695 }
696
697 if (o == this)
698 {
699 return true;
700 }
701
702 if (! (o instanceof NameFormDefinition))
703 {
704 return false;
705 }
706
707 final NameFormDefinition d = (NameFormDefinition) o;
708 return (oid.equals(d.oid) &&
709 structuralClass.equalsIgnoreCase(d.structuralClass) &&
710 stringsEqualIgnoreCaseOrderIndependent(names, d.names) &&
711 stringsEqualIgnoreCaseOrderIndependent(requiredAttributes,
712 d.requiredAttributes) &&
713 stringsEqualIgnoreCaseOrderIndependent(optionalAttributes,
714 d.optionalAttributes) &&
715 bothNullOrEqualIgnoreCase(description, d.description) &&
716 (isObsolete == d.isObsolete) &&
717 extensionsEqual(extensions, d.extensions));
718 }
719
720
721
722 /**
723 * Retrieves a string representation of this name form definition, in the
724 * format described in RFC 4512 section 4.1.7.2.
725 *
726 * @return A string representation of this name form definition.
727 */
728 @Override()
729 public String toString()
730 {
731 return nameFormString;
732 }
733 }