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    }