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