001    /*
002     * Copyright 2007-2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2015 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 DIT content rule
044     * schema element.
045     */
046    @NotMutable()
047    @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
048    public final class DITContentRuleDefinition
049           extends SchemaElement
050    {
051      /**
052       * The serial version UID for this serializable class.
053       */
054      private static final long serialVersionUID = 3224440505307817586L;
055    
056    
057    
058      // Indicates whether this DIT content rule is declared obsolete.
059      private final boolean isObsolete;
060    
061      // The set of extensions for this DIT content rule.
062      private final Map<String,String[]> extensions;
063    
064      // The description for this DIT content rule.
065      private final String description;
066    
067      // The string representation of this DIT content rule.
068      private final String ditContentRuleString;
069    
070      // The OID of the structural object class with which this DIT content rule is
071      // associated.
072      private final String oid;
073    
074      // The names/OIDs of the allowed auxiliary classes.
075      private final String[] auxiliaryClasses;
076    
077      // The set of names for this DIT content rule.
078      private final String[] names;
079    
080      // The names/OIDs of the optional attributes.
081      private final String[] optionalAttributes;
082    
083      // The names/OIDs of the prohibited attributes.
084      private final String[] prohibitedAttributes;
085    
086      // The names/OIDs of the required attributes.
087      private final String[] requiredAttributes;
088    
089    
090    
091      /**
092       * Creates a new DIT content rule from the provided string representation.
093       *
094       * @param  s  The string representation of the DIT content rule to create,
095       *            using the syntax described in RFC 4512 section 4.1.6.  It must
096       *            not be {@code null}.
097       *
098       * @throws  LDAPException  If the provided string cannot be decoded as a DIT
099       *                         content rule definition.
100       */
101      public DITContentRuleDefinition(final String s)
102             throws LDAPException
103      {
104        ensureNotNull(s);
105    
106        ditContentRuleString = s.trim();
107    
108        // The first character must be an opening parenthesis.
109        final int length = ditContentRuleString.length();
110        if (length == 0)
111        {
112          throw new LDAPException(ResultCode.DECODING_ERROR,
113                                  ERR_DCR_DECODE_EMPTY.get());
114        }
115        else if (ditContentRuleString.charAt(0) != '(')
116        {
117          throw new LDAPException(ResultCode.DECODING_ERROR,
118                                  ERR_DCR_DECODE_NO_OPENING_PAREN.get(
119                                       ditContentRuleString));
120        }
121    
122    
123        // Skip over any spaces until we reach the start of the OID, then read the
124        // OID until we find the next space.
125        int pos = skipSpaces(ditContentRuleString, 1, length);
126    
127        StringBuilder buffer = new StringBuilder();
128        pos = readOID(ditContentRuleString, pos, length, buffer);
129        oid = buffer.toString();
130    
131    
132        // Technically, DIT content elements are supposed to appear in a specific
133        // order, but we'll be lenient and allow remaining elements to come in any
134        // order.
135        final ArrayList<String>    nameList = new ArrayList<String>(1);
136        final ArrayList<String>    reqAttrs = new ArrayList<String>();
137        final ArrayList<String>    optAttrs = new ArrayList<String>();
138        final ArrayList<String>    notAttrs = new ArrayList<String>();
139        final ArrayList<String>    auxOCs   = new ArrayList<String>();
140        final Map<String,String[]> exts     = new LinkedHashMap<String,String[]>();
141        Boolean obsolete = null;
142        String  descr    = null;
143    
144        while (true)
145        {
146          // Skip over any spaces until we find the next element.
147          pos = skipSpaces(ditContentRuleString, pos, length);
148    
149          // Read until we find the next space or the end of the string.  Use that
150          // token to figure out what to do next.
151          final int tokenStartPos = pos;
152          while ((pos < length) && (ditContentRuleString.charAt(pos) != ' '))
153          {
154            pos++;
155          }
156    
157          // It's possible that the token could be smashed right up against the
158          // closing parenthesis.  If that's the case, then extract just the token
159          // and handle the closing parenthesis the next time through.
160          String token = ditContentRuleString.substring(tokenStartPos, pos);
161          if ((token.length() > 1) && (token.endsWith(")")))
162          {
163            token = token.substring(0, token.length() - 1);
164            pos--;
165          }
166    
167          final String lowerToken = toLowerCase(token);
168          if (lowerToken.equals(")"))
169          {
170            // This indicates that we're at the end of the value.  There should not
171            // be any more closing characters.
172            if (pos < length)
173            {
174              throw new LDAPException(ResultCode.DECODING_ERROR,
175                                      ERR_DCR_DECODE_CLOSE_NOT_AT_END.get(
176                                           ditContentRuleString));
177            }
178            break;
179          }
180          else if (lowerToken.equals("name"))
181          {
182            if (nameList.isEmpty())
183            {
184              pos = skipSpaces(ditContentRuleString, pos, length);
185              pos = readQDStrings(ditContentRuleString, pos, length, nameList);
186            }
187            else
188            {
189              throw new LDAPException(ResultCode.DECODING_ERROR,
190                                      ERR_DCR_DECODE_MULTIPLE_ELEMENTS.get(
191                                           ditContentRuleString, "NAME"));
192            }
193          }
194          else if (lowerToken.equals("desc"))
195          {
196            if (descr == null)
197            {
198              pos = skipSpaces(ditContentRuleString, pos, length);
199    
200              buffer = new StringBuilder();
201              pos = readQDString(ditContentRuleString, pos, length, buffer);
202              descr = buffer.toString();
203            }
204            else
205            {
206              throw new LDAPException(ResultCode.DECODING_ERROR,
207                                      ERR_DCR_DECODE_MULTIPLE_ELEMENTS.get(
208                                           ditContentRuleString, "DESC"));
209            }
210          }
211          else if (lowerToken.equals("obsolete"))
212          {
213            if (obsolete == null)
214            {
215              obsolete = true;
216            }
217            else
218            {
219              throw new LDAPException(ResultCode.DECODING_ERROR,
220                                      ERR_DCR_DECODE_MULTIPLE_ELEMENTS.get(
221                                           ditContentRuleString, "OBSOLETE"));
222            }
223          }
224          else if (lowerToken.equals("aux"))
225          {
226            if (auxOCs.isEmpty())
227            {
228              pos = skipSpaces(ditContentRuleString, pos, length);
229              pos = readOIDs(ditContentRuleString, pos, length, auxOCs);
230            }
231            else
232            {
233              throw new LDAPException(ResultCode.DECODING_ERROR,
234                                      ERR_DCR_DECODE_MULTIPLE_ELEMENTS.get(
235                                           ditContentRuleString, "AUX"));
236            }
237          }
238          else if (lowerToken.equals("must"))
239          {
240            if (reqAttrs.isEmpty())
241            {
242              pos = skipSpaces(ditContentRuleString, pos, length);
243              pos = readOIDs(ditContentRuleString, pos, length, reqAttrs);
244            }
245            else
246            {
247              throw new LDAPException(ResultCode.DECODING_ERROR,
248                                      ERR_DCR_DECODE_MULTIPLE_ELEMENTS.get(
249                                           ditContentRuleString, "MUST"));
250            }
251          }
252          else if (lowerToken.equals("may"))
253          {
254            if (optAttrs.isEmpty())
255            {
256              pos = skipSpaces(ditContentRuleString, pos, length);
257              pos = readOIDs(ditContentRuleString, pos, length, optAttrs);
258            }
259            else
260            {
261              throw new LDAPException(ResultCode.DECODING_ERROR,
262                                      ERR_DCR_DECODE_MULTIPLE_ELEMENTS.get(
263                                           ditContentRuleString, "MAY"));
264            }
265          }
266          else if (lowerToken.equals("not"))
267          {
268            if (notAttrs.isEmpty())
269            {
270              pos = skipSpaces(ditContentRuleString, pos, length);
271              pos = readOIDs(ditContentRuleString, pos, length, notAttrs);
272            }
273            else
274            {
275              throw new LDAPException(ResultCode.DECODING_ERROR,
276                                      ERR_DCR_DECODE_MULTIPLE_ELEMENTS.get(
277                                           ditContentRuleString, "NOT"));
278            }
279          }
280          else if (lowerToken.startsWith("x-"))
281          {
282            pos = skipSpaces(ditContentRuleString, pos, length);
283    
284            final ArrayList<String> valueList = new ArrayList<String>();
285            pos = readQDStrings(ditContentRuleString, pos, length, valueList);
286    
287            final String[] values = new String[valueList.size()];
288            valueList.toArray(values);
289    
290            if (exts.containsKey(token))
291            {
292              throw new LDAPException(ResultCode.DECODING_ERROR,
293                                      ERR_DCR_DECODE_DUP_EXT.get(
294                                           ditContentRuleString, token));
295            }
296    
297            exts.put(token, values);
298          }
299          else
300          {
301            throw new LDAPException(ResultCode.DECODING_ERROR,
302                                    ERR_DCR_DECODE_DUP_EXT.get(
303                                         ditContentRuleString, token));
304          }
305        }
306    
307        description = descr;
308    
309        names = new String[nameList.size()];
310        nameList.toArray(names);
311    
312        auxiliaryClasses = new String[auxOCs.size()];
313        auxOCs.toArray(auxiliaryClasses);
314    
315        requiredAttributes = new String[reqAttrs.size()];
316        reqAttrs.toArray(requiredAttributes);
317    
318        optionalAttributes = new String[optAttrs.size()];
319        optAttrs.toArray(optionalAttributes);
320    
321        prohibitedAttributes = new String[notAttrs.size()];
322        notAttrs.toArray(prohibitedAttributes);
323    
324        isObsolete = (obsolete != null);
325    
326        extensions = Collections.unmodifiableMap(exts);
327      }
328    
329    
330    
331      /**
332       * Creates a new DIT content rule with the provided information.
333       *
334       * @param  oid                   The OID for the structural object class with
335       *                               which this DIT content rule is associated.
336       *                               It must not be {@code null}.
337       * @param  names                 The set of names for this DIT content rule.
338       *                               It may be {@code null} or empty if the DIT
339       *                               content rule should only be referenced by
340       *                               OID.
341       * @param  description           The description for this DIT content rule.
342       *                               It may be {@code null} if there is no
343       *                               description.
344       * @param  isObsolete            Indicates whether this DIT content rule is
345       *                               declared obsolete.
346       * @param  auxiliaryClasses      The names/OIDs of the auxiliary object
347       *                               classes that may be present in entries
348       *                               containing this DIT content rule.
349       * @param  requiredAttributes    The names/OIDs of the attributes which must
350       *                               be present in entries containing this DIT
351       *                               content rule.
352       * @param  optionalAttributes    The names/OIDs of the attributes which may be
353       *                               present in entries containing this DIT
354       *                               content rule.
355       * @param  prohibitedAttributes  The names/OIDs of the attributes which may
356       *                               not be present in entries containing this DIT
357       *                               content rule.
358       * @param  extensions            The set of extensions for this DIT content
359       *                               rule.  It may be {@code null} or empty if
360       *                               there should not be any extensions.
361       */
362      public DITContentRuleDefinition(final String oid, final String[] names,
363                                      final String description,
364                                      final boolean isObsolete,
365                                      final String[] auxiliaryClasses,
366                                      final String[] requiredAttributes,
367                                      final String[] optionalAttributes,
368                                      final String[] prohibitedAttributes,
369                                      final Map<String,String[]> extensions)
370      {
371        ensureNotNull(oid);
372    
373        this.oid             = oid;
374        this.isObsolete      = isObsolete;
375        this.description     = description;
376    
377        if (names == null)
378        {
379          this.names = NO_STRINGS;
380        }
381        else
382        {
383          this.names = names;
384        }
385    
386        if (auxiliaryClasses == null)
387        {
388          this.auxiliaryClasses = NO_STRINGS;
389        }
390        else
391        {
392          this.auxiliaryClasses  = auxiliaryClasses;
393        }
394    
395        if (requiredAttributes == null)
396        {
397          this.requiredAttributes = NO_STRINGS;
398        }
399        else
400        {
401          this.requiredAttributes = requiredAttributes;
402        }
403    
404        if (optionalAttributes == null)
405        {
406          this.optionalAttributes = NO_STRINGS;
407        }
408        else
409        {
410          this.optionalAttributes = optionalAttributes;
411        }
412    
413        if (prohibitedAttributes == null)
414        {
415          this.prohibitedAttributes = NO_STRINGS;
416        }
417        else
418        {
419          this.prohibitedAttributes = prohibitedAttributes;
420        }
421    
422        if (extensions == null)
423        {
424          this.extensions = Collections.emptyMap();
425        }
426        else
427        {
428          this.extensions = Collections.unmodifiableMap(extensions);
429        }
430    
431        final StringBuilder buffer = new StringBuilder();
432        createDefinitionString(buffer);
433        ditContentRuleString = buffer.toString();
434      }
435    
436    
437    
438      /**
439       * Constructs a string representation of this DIT content rule definition in
440       * the provided buffer.
441       *
442       * @param  buffer  The buffer in which to construct a string representation of
443       *                 this DIT content rule definition.
444       */
445      private void createDefinitionString(final StringBuilder buffer)
446      {
447        buffer.append("( ");
448        buffer.append(oid);
449    
450        if (names.length == 1)
451        {
452          buffer.append(" NAME '");
453          buffer.append(names[0]);
454          buffer.append('\'');
455        }
456        else if (names.length > 1)
457        {
458          buffer.append(" NAME (");
459          for (final String name : names)
460          {
461            buffer.append(" '");
462            buffer.append(name);
463            buffer.append('\'');
464          }
465          buffer.append(" )");
466        }
467    
468        if (description != null)
469        {
470          buffer.append(" DESC '");
471          encodeValue(description, buffer);
472          buffer.append('\'');
473        }
474    
475        if (isObsolete)
476        {
477          buffer.append(" OBSOLETE");
478        }
479    
480        if (auxiliaryClasses.length == 1)
481        {
482          buffer.append(" AUX ");
483          buffer.append(auxiliaryClasses[0]);
484        }
485        else if (auxiliaryClasses.length > 1)
486        {
487          buffer.append(" AUX (");
488          for (int i=0; i < auxiliaryClasses.length; i++)
489          {
490            if (i >0)
491            {
492              buffer.append(" $ ");
493            }
494            else
495            {
496              buffer.append(' ');
497            }
498            buffer.append(auxiliaryClasses[i]);
499          }
500          buffer.append(" )");
501        }
502    
503        if (requiredAttributes.length == 1)
504        {
505          buffer.append(" MUST ");
506          buffer.append(requiredAttributes[0]);
507        }
508        else if (requiredAttributes.length > 1)
509        {
510          buffer.append(" MUST (");
511          for (int i=0; i < requiredAttributes.length; i++)
512          {
513            if (i >0)
514            {
515              buffer.append(" $ ");
516            }
517            else
518            {
519              buffer.append(' ');
520            }
521            buffer.append(requiredAttributes[i]);
522          }
523          buffer.append(" )");
524        }
525    
526        if (optionalAttributes.length == 1)
527        {
528          buffer.append(" MAY ");
529          buffer.append(optionalAttributes[0]);
530        }
531        else if (optionalAttributes.length > 1)
532        {
533          buffer.append(" MAY (");
534          for (int i=0; i < optionalAttributes.length; i++)
535          {
536            if (i > 0)
537            {
538              buffer.append(" $ ");
539            }
540            else
541            {
542              buffer.append(' ');
543            }
544            buffer.append(optionalAttributes[i]);
545          }
546          buffer.append(" )");
547        }
548    
549        if (prohibitedAttributes.length == 1)
550        {
551          buffer.append(" NOT ");
552          buffer.append(prohibitedAttributes[0]);
553        }
554        else if (prohibitedAttributes.length > 1)
555        {
556          buffer.append(" NOT (");
557          for (int i=0; i < prohibitedAttributes.length; i++)
558          {
559            if (i > 0)
560            {
561              buffer.append(" $ ");
562            }
563            else
564            {
565              buffer.append(' ');
566            }
567            buffer.append(prohibitedAttributes[i]);
568          }
569          buffer.append(" )");
570        }
571    
572        for (final Map.Entry<String,String[]> e : extensions.entrySet())
573        {
574          final String   name   = e.getKey();
575          final String[] values = e.getValue();
576          if (values.length == 1)
577          {
578            buffer.append(' ');
579            buffer.append(name);
580            buffer.append(" '");
581            encodeValue(values[0], buffer);
582            buffer.append('\'');
583          }
584          else
585          {
586            buffer.append(' ');
587            buffer.append(name);
588            buffer.append(" (");
589            for (final String value : values)
590            {
591              buffer.append(" '");
592              encodeValue(value, buffer);
593              buffer.append('\'');
594            }
595            buffer.append(" )");
596          }
597        }
598    
599        buffer.append(" )");
600      }
601    
602    
603    
604      /**
605       * Retrieves the OID for the structural object class associated with this
606       * DIT content rule.
607       *
608       * @return  The OID for the structural object class associated with this DIT
609       *          content rule.
610       */
611      public String getOID()
612      {
613        return oid;
614      }
615    
616    
617    
618      /**
619       * Retrieves the set of names for this DIT content rule.
620       *
621       * @return  The set of names for this DIT content rule, or an empty array if
622       *          it does not have any names.
623       */
624      public String[] getNames()
625      {
626        return names;
627      }
628    
629    
630    
631      /**
632       * Retrieves the primary name that can be used to reference this DIT content
633       * rule.  If one or more names are defined, then the first name will be used.
634       * Otherwise, the structural object class OID will be returned.
635       *
636       * @return  The primary name that can be used to reference this DIT content
637       *          rule.
638       */
639      public String getNameOrOID()
640      {
641        if (names.length == 0)
642        {
643          return oid;
644        }
645        else
646        {
647          return names[0];
648        }
649      }
650    
651    
652    
653      /**
654       * Indicates whether the provided string matches the OID or any of the names
655       * for this DIT content rule.
656       *
657       * @param  s  The string for which to make the determination.  It must not be
658       *            {@code null}.
659       *
660       * @return  {@code true} if the provided string matches the OID or any of the
661       *          names for this DIT content rule, or {@code false} if not.
662       */
663      public boolean hasNameOrOID(final String s)
664      {
665        for (final String name : names)
666        {
667          if (s.equalsIgnoreCase(name))
668          {
669            return true;
670          }
671        }
672    
673        return s.equalsIgnoreCase(oid);
674      }
675    
676    
677    
678      /**
679       * Retrieves the description for this DIT content rule, if available.
680       *
681       * @return  The description for this DIT content rule, or {@code null} if
682       *          there is no description defined.
683       */
684      public String getDescription()
685      {
686        return description;
687      }
688    
689    
690    
691      /**
692       * Indicates whether this DIT content rule is declared obsolete.
693       *
694       * @return  {@code true} if this DIT content rule is declared obsolete, or
695       *          {@code false} if it is not.
696       */
697      public boolean isObsolete()
698      {
699        return isObsolete;
700      }
701    
702    
703    
704      /**
705       * Retrieves the names or OIDs of the auxiliary object classes that may be
706       * present in entries containing the structural class for this DIT content
707       * rule.
708       *
709       * @return  The names or OIDs of the auxiliary object classes that may be
710       *          present in entries containing the structural class for this DIT
711       *          content rule.
712       */
713      public String[] getAuxiliaryClasses()
714      {
715        return auxiliaryClasses;
716      }
717    
718    
719    
720      /**
721       * Retrieves the names or OIDs of the attributes that are required to be
722       * present in entries containing the structural object class for this DIT
723       * content rule.
724       *
725       * @return  The names or OIDs of the attributes that are required to be
726       *          present in entries containing the structural object class for this
727       *          DIT content rule, or an empty array if there are no required
728       *          attributes.
729       */
730      public String[] getRequiredAttributes()
731      {
732        return requiredAttributes;
733      }
734    
735    
736    
737      /**
738       * Retrieves the names or OIDs of the attributes that are optionally allowed
739       * to be present in entries containing the structural object class for this
740       * DIT content rule.
741       *
742       * @return  The names or OIDs of the attributes that are optionally allowed to
743       *          be present in entries containing the structural object class for
744       *          this DIT content rule, or an empty array if there are no required
745       *          attributes.
746       */
747      public String[] getOptionalAttributes()
748      {
749        return optionalAttributes;
750      }
751    
752    
753    
754      /**
755       * Retrieves the names or OIDs of the attributes that are not allowed to be
756       * present in entries containing the structural object class for this DIT
757       * content rule.
758       *
759       * @return  The names or OIDs of the attributes that are not allowed to be
760       *          present in entries containing the structural object class for this
761       *          DIT content rule, or an empty array if there are no required
762       *          attributes.
763       */
764      public String[] getProhibitedAttributes()
765      {
766        return prohibitedAttributes;
767      }
768    
769    
770    
771      /**
772       * Retrieves the set of extensions for this DIT content rule.  They will be
773       * mapped from the extension name (which should start with "X-") to the set of
774       * values for that extension.
775       *
776       * @return  The set of extensions for this DIT content rule.
777       */
778      public Map<String,String[]> getExtensions()
779      {
780        return extensions;
781      }
782    
783    
784    
785      /**
786       * {@inheritDoc}
787       */
788      @Override()
789      public int hashCode()
790      {
791        return oid.hashCode();
792      }
793    
794    
795    
796      /**
797       * {@inheritDoc}
798       */
799      @Override()
800      public boolean equals(final Object o)
801      {
802        if (o == null)
803        {
804          return false;
805        }
806    
807        if (o == this)
808        {
809          return true;
810        }
811    
812        if (! (o instanceof DITContentRuleDefinition))
813        {
814          return false;
815        }
816    
817        final DITContentRuleDefinition d = (DITContentRuleDefinition) o;
818        return (oid.equals(d.oid) &&
819             stringsEqualIgnoreCaseOrderIndependent(names, d.names) &&
820             stringsEqualIgnoreCaseOrderIndependent(auxiliaryClasses,
821                  d.auxiliaryClasses) &&
822             stringsEqualIgnoreCaseOrderIndependent(requiredAttributes,
823                  d.requiredAttributes) &&
824             stringsEqualIgnoreCaseOrderIndependent(optionalAttributes,
825                  d.optionalAttributes) &&
826             stringsEqualIgnoreCaseOrderIndependent(prohibitedAttributes,
827                  d.prohibitedAttributes) &&
828             bothNullOrEqualIgnoreCase(description, d.description) &&
829             (isObsolete == d.isObsolete) &&
830             extensionsEqual(extensions, d.extensions));
831      }
832    
833    
834    
835      /**
836       * Retrieves a string representation of this DIT content rule definition, in
837       * the format described in RFC 4512 section 4.1.6.
838       *
839       * @return  A string representation of this DIT content rule definition.
840       */
841      @Override()
842      public String toString()
843      {
844        return ditContentRuleString;
845      }
846    }