001/*
002 * Copyright 2007-2017 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2017 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 */
021package com.unboundid.ldap.sdk.schema;
022
023
024
025import java.util.ArrayList;
026import java.util.Collections;
027import java.util.Map;
028import java.util.LinkedHashMap;
029
030import com.unboundid.ldap.sdk.LDAPException;
031import com.unboundid.ldap.sdk.ResultCode;
032import com.unboundid.util.NotMutable;
033import com.unboundid.util.ThreadSafety;
034import com.unboundid.util.ThreadSafetyLevel;
035
036import static com.unboundid.ldap.sdk.schema.SchemaMessages.*;
037import static com.unboundid.util.StaticUtils.*;
038import 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)
048public 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}