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 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          final String token = ditContentRuleString.substring(tokenStartPos, pos);
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_DCR_DECODE_CLOSE_NOT_AT_END.get(
167                                           ditContentRuleString));
168            }
169            break;
170          }
171          else if (lowerToken.equals("name"))
172          {
173            if (nameList.isEmpty())
174            {
175              pos = skipSpaces(ditContentRuleString, pos, length);
176              pos = readQDStrings(ditContentRuleString, pos, length, nameList);
177            }
178            else
179            {
180              throw new LDAPException(ResultCode.DECODING_ERROR,
181                                      ERR_DCR_DECODE_MULTIPLE_ELEMENTS.get(
182                                           ditContentRuleString, "NAME"));
183            }
184          }
185          else if (lowerToken.equals("desc"))
186          {
187            if (descr == null)
188            {
189              pos = skipSpaces(ditContentRuleString, pos, length);
190    
191              buffer = new StringBuilder();
192              pos = readQDString(ditContentRuleString, pos, length, buffer);
193              descr = buffer.toString();
194            }
195            else
196            {
197              throw new LDAPException(ResultCode.DECODING_ERROR,
198                                      ERR_DCR_DECODE_MULTIPLE_ELEMENTS.get(
199                                           ditContentRuleString, "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_DCR_DECODE_MULTIPLE_ELEMENTS.get(
212                                           ditContentRuleString, "OBSOLETE"));
213            }
214          }
215          else if (lowerToken.equals("aux"))
216          {
217            if (auxOCs.isEmpty())
218            {
219              pos = skipSpaces(ditContentRuleString, pos, length);
220              pos = readOIDs(ditContentRuleString, pos, length, auxOCs);
221            }
222            else
223            {
224              throw new LDAPException(ResultCode.DECODING_ERROR,
225                                      ERR_DCR_DECODE_MULTIPLE_ELEMENTS.get(
226                                           ditContentRuleString, "AUX"));
227            }
228          }
229          else if (lowerToken.equals("must"))
230          {
231            if (reqAttrs.isEmpty())
232            {
233              pos = skipSpaces(ditContentRuleString, pos, length);
234              pos = readOIDs(ditContentRuleString, pos, length, reqAttrs);
235            }
236            else
237            {
238              throw new LDAPException(ResultCode.DECODING_ERROR,
239                                      ERR_DCR_DECODE_MULTIPLE_ELEMENTS.get(
240                                           ditContentRuleString, "MUST"));
241            }
242          }
243          else if (lowerToken.equals("may"))
244          {
245            if (optAttrs.isEmpty())
246            {
247              pos = skipSpaces(ditContentRuleString, pos, length);
248              pos = readOIDs(ditContentRuleString, pos, length, optAttrs);
249            }
250            else
251            {
252              throw new LDAPException(ResultCode.DECODING_ERROR,
253                                      ERR_DCR_DECODE_MULTIPLE_ELEMENTS.get(
254                                           ditContentRuleString, "MAY"));
255            }
256          }
257          else if (lowerToken.equals("not"))
258          {
259            if (notAttrs.isEmpty())
260            {
261              pos = skipSpaces(ditContentRuleString, pos, length);
262              pos = readOIDs(ditContentRuleString, pos, length, notAttrs);
263            }
264            else
265            {
266              throw new LDAPException(ResultCode.DECODING_ERROR,
267                                      ERR_DCR_DECODE_MULTIPLE_ELEMENTS.get(
268                                           ditContentRuleString, "NOT"));
269            }
270          }
271          else if (lowerToken.startsWith("x-"))
272          {
273            pos = skipSpaces(ditContentRuleString, pos, length);
274    
275            final ArrayList<String> valueList = new ArrayList<String>();
276            pos = readQDStrings(ditContentRuleString, pos, length, valueList);
277    
278            final String[] values = new String[valueList.size()];
279            valueList.toArray(values);
280    
281            if (exts.containsKey(token))
282            {
283              throw new LDAPException(ResultCode.DECODING_ERROR,
284                                      ERR_DCR_DECODE_DUP_EXT.get(
285                                           ditContentRuleString, token));
286            }
287    
288            exts.put(token, values);
289          }
290          else
291          {
292            throw new LDAPException(ResultCode.DECODING_ERROR,
293                                    ERR_DCR_DECODE_DUP_EXT.get(
294                                         ditContentRuleString, token));
295          }
296        }
297    
298        description = descr;
299    
300        names = new String[nameList.size()];
301        nameList.toArray(names);
302    
303        auxiliaryClasses = new String[auxOCs.size()];
304        auxOCs.toArray(auxiliaryClasses);
305    
306        requiredAttributes = new String[reqAttrs.size()];
307        reqAttrs.toArray(requiredAttributes);
308    
309        optionalAttributes = new String[optAttrs.size()];
310        optAttrs.toArray(optionalAttributes);
311    
312        prohibitedAttributes = new String[notAttrs.size()];
313        notAttrs.toArray(prohibitedAttributes);
314    
315        isObsolete = (obsolete != null);
316    
317        extensions = Collections.unmodifiableMap(exts);
318      }
319    
320    
321    
322      /**
323       * Creates a new DIT content rule with the provided information.
324       *
325       * @param  oid                   The OID for the structural object class with
326       *                               which this DIT content rule is associated.
327       *                               It must not be {@code null}.
328       * @param  names                 The set of names for this DIT content rule.
329       *                               It may be {@code null} or empty if the DIT
330       *                               content rule should only be referenced by
331       *                               OID.
332       * @param  description           The description for this DIT content rule.
333       *                               It may be {@code null} if there is no
334       *                               description.
335       * @param  isObsolete            Indicates whether this DIT content rule is
336       *                               declared obsolete.
337       * @param  auxiliaryClasses      The names/OIDs of the auxiliary object
338       *                               classes that may be present in entries
339       *                               containing this DIT content rule.
340       * @param  requiredAttributes    The names/OIDs of the attributes which must
341       *                               be present in entries containing this DIT
342       *                               content rule.
343       * @param  optionalAttributes    The names/OIDs of the attributes which may be
344       *                               present in entries containing this DIT
345       *                               content rule.
346       * @param  prohibitedAttributes  The names/OIDs of the attributes which may
347       *                               not be present in entries containing this DIT
348       *                               content rule.
349       * @param  extensions            The set of extensions for this DIT content
350       *                               rule.  It may be {@code null} or empty if
351       *                               there should not be any extensions.
352       */
353      public DITContentRuleDefinition(final String oid, final String[] names,
354                                      final String description,
355                                      final boolean isObsolete,
356                                      final String[] auxiliaryClasses,
357                                      final String[] requiredAttributes,
358                                      final String[] optionalAttributes,
359                                      final String[] prohibitedAttributes,
360                                      final Map<String,String[]> extensions)
361      {
362        ensureNotNull(oid);
363    
364        this.oid             = oid;
365        this.isObsolete      = isObsolete;
366        this.description     = description;
367    
368        if (names == null)
369        {
370          this.names = NO_STRINGS;
371        }
372        else
373        {
374          this.names = names;
375        }
376    
377        if (auxiliaryClasses == null)
378        {
379          this.auxiliaryClasses = NO_STRINGS;
380        }
381        else
382        {
383          this.auxiliaryClasses  = auxiliaryClasses;
384        }
385    
386        if (requiredAttributes == null)
387        {
388          this.requiredAttributes = NO_STRINGS;
389        }
390        else
391        {
392          this.requiredAttributes = requiredAttributes;
393        }
394    
395        if (optionalAttributes == null)
396        {
397          this.optionalAttributes = NO_STRINGS;
398        }
399        else
400        {
401          this.optionalAttributes = optionalAttributes;
402        }
403    
404        if (prohibitedAttributes == null)
405        {
406          this.prohibitedAttributes = NO_STRINGS;
407        }
408        else
409        {
410          this.prohibitedAttributes = prohibitedAttributes;
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        ditContentRuleString = buffer.toString();
425      }
426    
427    
428    
429      /**
430       * Constructs a string representation of this DIT content rule definition in
431       * the provided buffer.
432       *
433       * @param  buffer  The buffer in which to construct a string representation of
434       *                 this DIT content rule 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        if (auxiliaryClasses.length == 1)
472        {
473          buffer.append(" AUX ");
474          buffer.append(auxiliaryClasses[0]);
475        }
476        else if (auxiliaryClasses.length > 1)
477        {
478          buffer.append(" AUX (");
479          for (int i=0; i < auxiliaryClasses.length; i++)
480          {
481            if (i >0)
482            {
483              buffer.append(" $ ");
484            }
485            else
486            {
487              buffer.append(' ');
488            }
489            buffer.append(auxiliaryClasses[i]);
490          }
491          buffer.append(" )");
492        }
493    
494        if (requiredAttributes.length == 1)
495        {
496          buffer.append(" MUST ");
497          buffer.append(requiredAttributes[0]);
498        }
499        else if (requiredAttributes.length > 1)
500        {
501          buffer.append(" MUST (");
502          for (int i=0; i < requiredAttributes.length; i++)
503          {
504            if (i >0)
505            {
506              buffer.append(" $ ");
507            }
508            else
509            {
510              buffer.append(' ');
511            }
512            buffer.append(requiredAttributes[i]);
513          }
514          buffer.append(" )");
515        }
516    
517        if (optionalAttributes.length == 1)
518        {
519          buffer.append(" MAY ");
520          buffer.append(optionalAttributes[0]);
521        }
522        else if (optionalAttributes.length > 1)
523        {
524          buffer.append(" MAY (");
525          for (int i=0; i < optionalAttributes.length; i++)
526          {
527            if (i > 0)
528            {
529              buffer.append(" $ ");
530            }
531            else
532            {
533              buffer.append(' ');
534            }
535            buffer.append(optionalAttributes[i]);
536          }
537          buffer.append(" )");
538        }
539    
540        if (prohibitedAttributes.length == 1)
541        {
542          buffer.append(" NOT ");
543          buffer.append(prohibitedAttributes[0]);
544        }
545        else if (prohibitedAttributes.length > 1)
546        {
547          buffer.append(" NOT (");
548          for (int i=0; i < prohibitedAttributes.length; i++)
549          {
550            if (i > 0)
551            {
552              buffer.append(" $ ");
553            }
554            else
555            {
556              buffer.append(' ');
557            }
558            buffer.append(prohibitedAttributes[i]);
559          }
560          buffer.append(" )");
561        }
562    
563        for (final Map.Entry<String,String[]> e : extensions.entrySet())
564        {
565          final String   name   = e.getKey();
566          final String[] values = e.getValue();
567          if (values.length == 1)
568          {
569            buffer.append(' ');
570            buffer.append(name);
571            buffer.append(" '");
572            encodeValue(values[0], buffer);
573            buffer.append('\'');
574          }
575          else
576          {
577            buffer.append(' ');
578            buffer.append(name);
579            buffer.append(" (");
580            for (final String value : values)
581            {
582              buffer.append(" '");
583              encodeValue(value, buffer);
584              buffer.append('\'');
585            }
586            buffer.append(" )");
587          }
588        }
589    
590        buffer.append(" )");
591      }
592    
593    
594    
595      /**
596       * Retrieves the OID for the structural object class associated with this
597       * DIT content rule.
598       *
599       * @return  The OID for the structural object class associated with this DIT
600       *          content rule.
601       */
602      public String getOID()
603      {
604        return oid;
605      }
606    
607    
608    
609      /**
610       * Retrieves the set of names for this DIT content rule.
611       *
612       * @return  The set of names for this DIT content rule, or an empty array if
613       *          it does not have any names.
614       */
615      public String[] getNames()
616      {
617        return names;
618      }
619    
620    
621    
622      /**
623       * Retrieves the primary name that can be used to reference this DIT content
624       * rule.  If one or more names are defined, then the first name will be used.
625       * Otherwise, the structural object class OID will be returned.
626       *
627       * @return  The primary name that can be used to reference this DIT content
628       *          rule.
629       */
630      public String getNameOrOID()
631      {
632        if (names.length == 0)
633        {
634          return oid;
635        }
636        else
637        {
638          return names[0];
639        }
640      }
641    
642    
643    
644      /**
645       * Indicates whether the provided string matches the OID or any of the names
646       * for this DIT content rule.
647       *
648       * @param  s  The string for which to make the determination.  It must not be
649       *            {@code null}.
650       *
651       * @return  {@code true} if the provided string matches the OID or any of the
652       *          names for this DIT content rule, or {@code false} if not.
653       */
654      public boolean hasNameOrOID(final String s)
655      {
656        for (final String name : names)
657        {
658          if (s.equalsIgnoreCase(name))
659          {
660            return true;
661          }
662        }
663    
664        return s.equalsIgnoreCase(oid);
665      }
666    
667    
668    
669      /**
670       * Retrieves the description for this DIT content rule, if available.
671       *
672       * @return  The description for this DIT content rule, or {@code null} if
673       *          there is no description defined.
674       */
675      public String getDescription()
676      {
677        return description;
678      }
679    
680    
681    
682      /**
683       * Indicates whether this DIT content rule is declared obsolete.
684       *
685       * @return  {@code true} if this DIT content rule is declared obsolete, or
686       *          {@code false} if it is not.
687       */
688      public boolean isObsolete()
689      {
690        return isObsolete;
691      }
692    
693    
694    
695      /**
696       * Retrieves the names or OIDs of the auxiliary object classes that may be
697       * present in entries containing the structural class for this DIT content
698       * rule.
699       *
700       * @return  The names or OIDs of the auxiliary object classes that may be
701       *          present in entries containing the structural class for this DIT
702       *          content rule.
703       */
704      public String[] getAuxiliaryClasses()
705      {
706        return auxiliaryClasses;
707      }
708    
709    
710    
711      /**
712       * Retrieves the names or OIDs of the attributes that are required to be
713       * present in entries containing the structural object class for this DIT
714       * content rule.
715       *
716       * @return  The names or OIDs of the attributes that are required to be
717       *          present in entries containing the structural object class for this
718       *          DIT content rule, or an empty array if there are no required
719       *          attributes.
720       */
721      public String[] getRequiredAttributes()
722      {
723        return requiredAttributes;
724      }
725    
726    
727    
728      /**
729       * Retrieves the names or OIDs of the attributes that are optionally allowed
730       * to be present in entries containing the structural object class for this
731       * DIT content rule.
732       *
733       * @return  The names or OIDs of the attributes that are optionally allowed to
734       *          be present in entries containing the structural object class for
735       *          this DIT content rule, or an empty array if there are no required
736       *          attributes.
737       */
738      public String[] getOptionalAttributes()
739      {
740        return optionalAttributes;
741      }
742    
743    
744    
745      /**
746       * Retrieves the names or OIDs of the attributes that are not allowed to be
747       * present in entries containing the structural object class for this DIT
748       * content rule.
749       *
750       * @return  The names or OIDs of the attributes that are not allowed to be
751       *          present in entries containing the structural object class for this
752       *          DIT content rule, or an empty array if there are no required
753       *          attributes.
754       */
755      public String[] getProhibitedAttributes()
756      {
757        return prohibitedAttributes;
758      }
759    
760    
761    
762      /**
763       * Retrieves the set of extensions for this DIT content rule.  They will be
764       * mapped from the extension name (which should start with "X-") to the set of
765       * values for that extension.
766       *
767       * @return  The set of extensions for this DIT content rule.
768       */
769      public Map<String,String[]> getExtensions()
770      {
771        return extensions;
772      }
773    
774    
775    
776      /**
777       * {@inheritDoc}
778       */
779      @Override()
780      public int hashCode()
781      {
782        return oid.hashCode();
783      }
784    
785    
786    
787      /**
788       * {@inheritDoc}
789       */
790      @Override()
791      public boolean equals(final Object o)
792      {
793        if (o == null)
794        {
795          return false;
796        }
797    
798        if (o == this)
799        {
800          return true;
801        }
802    
803        if (! (o instanceof DITContentRuleDefinition))
804        {
805          return false;
806        }
807    
808        final DITContentRuleDefinition d = (DITContentRuleDefinition) o;
809        return (oid.equals(d.oid) &&
810             stringsEqualIgnoreCaseOrderIndependent(names, d.names) &&
811             stringsEqualIgnoreCaseOrderIndependent(auxiliaryClasses,
812                  d.auxiliaryClasses) &&
813             stringsEqualIgnoreCaseOrderIndependent(requiredAttributes,
814                  d.requiredAttributes) &&
815             stringsEqualIgnoreCaseOrderIndependent(optionalAttributes,
816                  d.optionalAttributes) &&
817             stringsEqualIgnoreCaseOrderIndependent(prohibitedAttributes,
818                  d.prohibitedAttributes) &&
819             bothNullOrEqualIgnoreCase(description, d.description) &&
820             (isObsolete == d.isObsolete) &&
821             extensionsEqual(extensions, d.extensions));
822      }
823    
824    
825    
826      /**
827       * Retrieves a string representation of this DIT content rule definition, in
828       * the format described in RFC 4512 section 4.1.6.
829       *
830       * @return  A string representation of this DIT content rule definition.
831       */
832      @Override()
833      public String toString()
834      {
835        return ditContentRuleString;
836      }
837    }