001/*
002 * Copyright 2007-2014 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2014 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 DIT content rule
044 * schema element.
045 */
046@NotMutable()
047@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
048public 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}