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.HashSet;
028    import java.util.Map;
029    import java.util.LinkedHashMap;
030    
031    import com.unboundid.ldap.sdk.LDAPException;
032    import com.unboundid.ldap.sdk.ResultCode;
033    import com.unboundid.util.NotMutable;
034    import com.unboundid.util.ThreadSafety;
035    import com.unboundid.util.ThreadSafetyLevel;
036    
037    import static com.unboundid.ldap.sdk.schema.SchemaMessages.*;
038    import static com.unboundid.util.Debug.*;
039    import static com.unboundid.util.StaticUtils.*;
040    import static com.unboundid.util.Validator.*;
041    
042    
043    
044    /**
045     * This class provides a data structure that describes an LDAP DIT structure
046     * rule schema element.
047     */
048    @NotMutable()
049    @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
050    public final class DITStructureRuleDefinition
051           extends SchemaElement
052    {
053      /**
054       * A pre-allocated zero-element integer array.
055       */
056      private static final int[] NO_INTS = new int[0];
057    
058    
059    
060      /**
061       * The serial version UID for this serializable class.
062       */
063      private static final long serialVersionUID = -3233223742542121140L;
064    
065    
066    
067      // Indicates whether this DIT structure rule is declared obsolete.
068      private final boolean isObsolete;
069    
070      // The rule ID for this DIT structure rule.
071      private final int ruleID;
072    
073      // The set of superior rule IDs for this DIT structure rule.
074      private final int[] superiorRuleIDs;
075    
076      // The set of extensions for this DIT content rule.
077      private final Map<String,String[]> extensions;
078    
079      // The description for this DIT content rule.
080      private final String description;
081    
082      // The string representation of this DIT structure rule.
083      private final String ditStructureRuleString;
084    
085      // The name/OID of the name form with which this DIT structure rule is
086      // associated.
087      private final String nameFormID;
088    
089      // The set of names for this DIT structure rule.
090      private final String[] names;
091    
092    
093    
094      /**
095       * Creates a new DIT structure rule from the provided string representation.
096       *
097       * @param  s  The string representation of the DIT structure rule to create,
098       *            using the syntax described in RFC 4512 section 4.1.7.1.  It must
099       *            not be {@code null}.
100       *
101       * @throws  LDAPException  If the provided string cannot be decoded as a DIT
102       *                         structure rule definition.
103       */
104      public DITStructureRuleDefinition(final String s)
105             throws LDAPException
106      {
107        ensureNotNull(s);
108    
109        ditStructureRuleString = s.trim();
110    
111        // The first character must be an opening parenthesis.
112        final int length = ditStructureRuleString.length();
113        if (length == 0)
114        {
115          throw new LDAPException(ResultCode.DECODING_ERROR,
116                                  ERR_DSR_DECODE_EMPTY.get());
117        }
118        else if (ditStructureRuleString.charAt(0) != '(')
119        {
120          throw new LDAPException(ResultCode.DECODING_ERROR,
121                                  ERR_DSR_DECODE_NO_OPENING_PAREN.get(
122                                       ditStructureRuleString));
123        }
124    
125    
126        // Skip over any spaces until we reach the start of the OID, then read the
127        // rule ID until we find the next space.
128        int pos = skipSpaces(ditStructureRuleString, 1, length);
129    
130        StringBuilder buffer = new StringBuilder();
131        pos = readOID(ditStructureRuleString, pos, length, buffer);
132        final String ruleIDStr = buffer.toString();
133        try
134        {
135          ruleID = Integer.parseInt(ruleIDStr);
136        }
137        catch (NumberFormatException nfe)
138        {
139          debugException(nfe);
140          throw new LDAPException(ResultCode.DECODING_ERROR,
141                                  ERR_DSR_DECODE_RULE_ID_NOT_INT.get(
142                                       ditStructureRuleString),
143                                  nfe);
144        }
145    
146    
147        // Technically, DIT structure elements are supposed to appear in a specific
148        // order, but we'll be lenient and allow remaining elements to come in any
149        // order.
150        final ArrayList<Integer>   supList  = new ArrayList<Integer>(1);
151        final ArrayList<String>    nameList = new ArrayList<String>(1);
152        final Map<String,String[]> exts     = new LinkedHashMap<String,String[]>();
153        Boolean                    obsolete = null;
154        String                     descr    = null;
155        String                     nfID     = null;
156    
157        while (true)
158        {
159          // Skip over any spaces until we find the next element.
160          pos = skipSpaces(ditStructureRuleString, pos, length);
161    
162          // Read until we find the next space or the end of the string.  Use that
163          // token to figure out what to do next.
164          final int tokenStartPos = pos;
165          while ((pos < length) && (ditStructureRuleString.charAt(pos) != ' '))
166          {
167            pos++;
168          }
169    
170          final String token = ditStructureRuleString.substring(tokenStartPos, pos);
171          final String lowerToken = toLowerCase(token);
172          if (lowerToken.equals(")"))
173          {
174            // This indicates that we're at the end of the value.  There should not
175            // be any more closing characters.
176            if (pos < length)
177            {
178              throw new LDAPException(ResultCode.DECODING_ERROR,
179                                      ERR_DSR_DECODE_CLOSE_NOT_AT_END.get(
180                                           ditStructureRuleString));
181            }
182            break;
183          }
184          else if (lowerToken.equals("name"))
185          {
186            if (nameList.isEmpty())
187            {
188              pos = skipSpaces(ditStructureRuleString, pos, length);
189              pos = readQDStrings(ditStructureRuleString, pos, length, nameList);
190            }
191            else
192            {
193              throw new LDAPException(ResultCode.DECODING_ERROR,
194                                      ERR_DSR_DECODE_MULTIPLE_ELEMENTS.get(
195                                           ditStructureRuleString, "NAME"));
196            }
197          }
198          else if (lowerToken.equals("desc"))
199          {
200            if (descr == null)
201            {
202              pos = skipSpaces(ditStructureRuleString, pos, length);
203    
204              buffer = new StringBuilder();
205              pos = readQDString(ditStructureRuleString, pos, length, buffer);
206              descr = buffer.toString();
207            }
208            else
209            {
210              throw new LDAPException(ResultCode.DECODING_ERROR,
211                                      ERR_DSR_DECODE_MULTIPLE_ELEMENTS.get(
212                                           ditStructureRuleString, "DESC"));
213            }
214          }
215          else if (lowerToken.equals("obsolete"))
216          {
217            if (obsolete == null)
218            {
219              obsolete = true;
220            }
221            else
222            {
223              throw new LDAPException(ResultCode.DECODING_ERROR,
224                                      ERR_DSR_DECODE_MULTIPLE_ELEMENTS.get(
225                                           ditStructureRuleString, "OBSOLETE"));
226            }
227          }
228          else if (lowerToken.equals("form"))
229          {
230            if (nfID == null)
231            {
232              pos = skipSpaces(ditStructureRuleString, pos, length);
233    
234              buffer = new StringBuilder();
235              pos = readOID(ditStructureRuleString, pos, length, buffer);
236              nfID = buffer.toString();
237            }
238            else
239            {
240              throw new LDAPException(ResultCode.DECODING_ERROR,
241                                      ERR_DSR_DECODE_MULTIPLE_ELEMENTS.get(
242                                           ditStructureRuleString, "FORM"));
243            }
244          }
245          else if (lowerToken.equals("sup"))
246          {
247            if (supList.isEmpty())
248            {
249              final ArrayList<String> supStrs = new ArrayList<String>(1);
250    
251              pos = skipSpaces(ditStructureRuleString, pos, length);
252              pos = readOIDs(ditStructureRuleString, pos, length, supStrs);
253    
254              supList.ensureCapacity(supStrs.size());
255              for (final String supStr : supStrs)
256              {
257                try
258                {
259                  supList.add(Integer.parseInt(supStr));
260                }
261                catch (NumberFormatException nfe)
262                {
263                  debugException(nfe);
264                  throw new LDAPException(ResultCode.DECODING_ERROR,
265                                          ERR_DSR_DECODE_SUP_ID_NOT_INT.get(
266                                               ditStructureRuleString),
267                                          nfe);
268                }
269              }
270            }
271            else
272            {
273              throw new LDAPException(ResultCode.DECODING_ERROR,
274                                      ERR_DSR_DECODE_MULTIPLE_ELEMENTS.get(
275                                           ditStructureRuleString, "SUP"));
276            }
277          }
278          else if (lowerToken.startsWith("x-"))
279          {
280            pos = skipSpaces(ditStructureRuleString, pos, length);
281    
282            final ArrayList<String> valueList = new ArrayList<String>();
283            pos = readQDStrings(ditStructureRuleString, pos, length, valueList);
284    
285            final String[] values = new String[valueList.size()];
286            valueList.toArray(values);
287    
288            if (exts.containsKey(token))
289            {
290              throw new LDAPException(ResultCode.DECODING_ERROR,
291                                      ERR_DSR_DECODE_DUP_EXT.get(
292                                           ditStructureRuleString, token));
293            }
294    
295            exts.put(token, values);
296          }
297          else
298          {
299            throw new LDAPException(ResultCode.DECODING_ERROR,
300                                    ERR_DSR_DECODE_UNEXPECTED_TOKEN.get(
301                                         ditStructureRuleString, token));
302          }
303        }
304    
305        description = descr;
306        nameFormID  = nfID;
307    
308        if (nameFormID == null)
309        {
310          throw new LDAPException(ResultCode.DECODING_ERROR,
311                                  ERR_DSR_DECODE_NO_FORM.get(
312                                       ditStructureRuleString));
313        }
314    
315        names = new String[nameList.size()];
316        nameList.toArray(names);
317    
318        superiorRuleIDs = new int[supList.size()];
319        for (int i=0; i < superiorRuleIDs.length; i++)
320        {
321          superiorRuleIDs[i] = supList.get(i);
322        }
323    
324        isObsolete = (obsolete != null);
325    
326        extensions = Collections.unmodifiableMap(exts);
327      }
328    
329    
330    
331      /**
332       * Creates a new DIT structure rule with the provided information.
333       *
334       * @param  ruleID           The rule ID for this DIT structure rule.
335       * @param  names            The set of names for this DIT structure rule.  It
336       *                          may be {@code null} or empty if the DIT structure
337       *                          rule should only be referenced by rule ID.
338       * @param  description      The description for this DIT structure rule.  It
339       *                          may be {@code null} if there is no description.
340       * @param  isObsolete       Indicates whether this DIT structure rule is
341       *                          declared obsolete.
342       * @param  nameFormID       The name or OID of the name form with which this
343       *                          DIT structure rule is associated.  It must not be
344       *                          {@code null}.
345       * @param  superiorRuleIDs  The superior rule IDs for this DIT structure rule.
346       *                          It may be {@code null} or empty if there are no
347       *                          superior rule IDs.
348       * @param  extensions       The set of extensions for this DIT structure rule.
349       *                          It may be {@code null} or empty if there are no
350       *                          extensions.
351       */
352      public DITStructureRuleDefinition(final int ruleID, final String[] names,
353                                        final String description,
354                                        final boolean isObsolete,
355                                        final String nameFormID,
356                                        final int[] superiorRuleIDs,
357                                        final Map<String,String[]> extensions)
358      {
359        ensureNotNull(nameFormID);
360    
361        this.ruleID      = ruleID;
362        this.description = description;
363        this.isObsolete  = isObsolete;
364        this.nameFormID  = nameFormID;
365    
366        if (names == null)
367        {
368          this.names = NO_STRINGS;
369        }
370        else
371        {
372          this.names = names;
373        }
374    
375        if (superiorRuleIDs == null)
376        {
377          this.superiorRuleIDs = NO_INTS;
378        }
379        else
380        {
381          this.superiorRuleIDs = superiorRuleIDs;
382        }
383    
384        if (extensions == null)
385        {
386          this.extensions = Collections.emptyMap();
387        }
388        else
389        {
390          this.extensions = Collections.unmodifiableMap(extensions);
391        }
392    
393        final StringBuilder buffer = new StringBuilder();
394        createDefinitionString(buffer);
395        ditStructureRuleString = buffer.toString();
396      }
397    
398    
399    
400      /**
401       * Constructs a string representation of this DIT content rule definition in
402       * the provided buffer.
403       *
404       * @param  buffer  The buffer in which to construct a string representation of
405       *                 this DIT content rule definition.
406       */
407      private void createDefinitionString(final StringBuilder buffer)
408      {
409        buffer.append("( ");
410        buffer.append(ruleID);
411    
412        if (names.length == 1)
413        {
414          buffer.append(" NAME '");
415          buffer.append(names[0]);
416          buffer.append('\'');
417        }
418        else if (names.length > 1)
419        {
420          buffer.append(" NAME (");
421          for (final String name : names)
422          {
423            buffer.append(" '");
424            buffer.append(name);
425            buffer.append('\'');
426          }
427          buffer.append(" )");
428        }
429    
430        if (description != null)
431        {
432          buffer.append(" DESC '");
433          encodeValue(description, buffer);
434          buffer.append('\'');
435        }
436    
437        if (isObsolete)
438        {
439          buffer.append(" OBSOLETE");
440        }
441    
442        buffer.append(" FORM ");
443        buffer.append(nameFormID);
444    
445        if (superiorRuleIDs.length == 1)
446        {
447          buffer.append(" SUP ");
448          buffer.append(superiorRuleIDs[0]);
449        }
450        else if (superiorRuleIDs.length > 1)
451        {
452          buffer.append(" SUP (");
453          for (final int supID : superiorRuleIDs)
454          {
455            buffer.append(" $ ");
456            buffer.append(supID);
457          }
458          buffer.append(" )");
459        }
460    
461        for (final Map.Entry<String,String[]> e : extensions.entrySet())
462        {
463          final String   name   = e.getKey();
464          final String[] values = e.getValue();
465          if (values.length == 1)
466          {
467            buffer.append(' ');
468            buffer.append(name);
469            buffer.append(" '");
470            encodeValue(values[0], buffer);
471            buffer.append('\'');
472          }
473          else
474          {
475            buffer.append(' ');
476            buffer.append(name);
477            buffer.append(" (");
478            for (final String value : values)
479            {
480              buffer.append(" '");
481              encodeValue(value, buffer);
482              buffer.append('\'');
483            }
484            buffer.append(" )");
485          }
486        }
487    
488        buffer.append(" )");
489      }
490    
491    
492    
493      /**
494       * Retrieves the rule ID for this DIT structure rule.
495       *
496       * @return  The rule ID for this DIT structure rule.
497       */
498      public int getRuleID()
499      {
500        return ruleID;
501      }
502    
503    
504    
505      /**
506       * Retrieves the set of names for this DIT structure rule.
507       *
508       * @return  The set of names for this DIT structure rule, or an empty array if
509       *          it does not have any names.
510       */
511      public String[] getNames()
512      {
513        return names;
514      }
515    
516    
517    
518      /**
519       * Retrieves the primary name that can be used to reference this DIT structure
520       * rule.  If one or more names are defined, then the first name will be used.
521       * Otherwise, the string representation of the rule ID will be returned.
522       *
523       * @return  The primary name that can be used to reference this DIT structure
524       *          rule.
525       */
526      public String getNameOrRuleID()
527      {
528        if (names.length == 0)
529        {
530          return String.valueOf(ruleID);
531        }
532        else
533        {
534          return names[0];
535        }
536      }
537    
538    
539    
540      /**
541       * Indicates whether the provided string matches the rule ID or any of the
542       * names for this DIT structure rule.
543       *
544       * @param  s  The string for which to make the determination.  It must not be
545       *            {@code null}.
546       *
547       * @return  {@code true} if the provided string matches the rule ID or any of
548       *          the names for this DIT structure rule, or {@code false} if not.
549       */
550      public boolean hasNameOrRuleID(final String s)
551      {
552        for (final String name : names)
553        {
554          if (s.equalsIgnoreCase(name))
555          {
556            return true;
557          }
558        }
559    
560        return s.equalsIgnoreCase(String.valueOf(ruleID));
561      }
562    
563    
564    
565      /**
566       * Retrieves the description for this DIT structure rule, if available.
567       *
568       * @return  The description for this DIT structure rule, or {@code null} if
569       *          there is no description defined.
570       */
571      public String getDescription()
572      {
573        return description;
574      }
575    
576    
577    
578      /**
579       * Indicates whether this DIT structure rule is declared obsolete.
580       *
581       * @return  {@code true} if this DIT structure rule is declared obsolete, or
582       *          {@code false} if it is not.
583       */
584      public boolean isObsolete()
585      {
586        return isObsolete;
587      }
588    
589    
590    
591      /**
592       * Retrieves the name or OID of the name form with which this DIT structure
593       * rule is associated.
594       *
595       * @return  The name or OID of the name form with which this DIT structure
596       *          rule is associated.
597       */
598      public String getNameFormID()
599      {
600        return nameFormID;
601      }
602    
603    
604    
605      /**
606       * Retrieves the rule IDs of the superior rules for this DIT structure rule.
607       *
608       * @return  The rule IDs of the superior rules for this DIT structure rule, or
609       *          an empty array if there are no superior rule IDs.
610       */
611      public int[] getSuperiorRuleIDs()
612      {
613        return superiorRuleIDs;
614      }
615    
616    
617    
618      /**
619       * Retrieves the set of extensions for this DIT structure rule.  They will be
620       * mapped from the extension name (which should start with "X-") to the set of
621       * values for that extension.
622       *
623       * @return  The set of extensions for this DIT structure rule.
624       */
625      public Map<String,String[]> getExtensions()
626      {
627        return extensions;
628      }
629    
630    
631    
632      /**
633       * {@inheritDoc}
634       */
635      @Override()
636      public int hashCode()
637      {
638        return ruleID;
639      }
640    
641    
642    
643      /**
644       * {@inheritDoc}
645       */
646      @Override()
647      public boolean equals(final Object o)
648      {
649        if (o == null)
650        {
651          return false;
652        }
653    
654        if (o == this)
655        {
656          return true;
657        }
658    
659        if (! (o instanceof DITStructureRuleDefinition))
660        {
661          return false;
662        }
663    
664        final DITStructureRuleDefinition d = (DITStructureRuleDefinition) o;
665        if ((ruleID == d.ruleID) &&
666             nameFormID.equalsIgnoreCase(d.nameFormID) &&
667             stringsEqualIgnoreCaseOrderIndependent(names, d.names) &&
668             (isObsolete == d.isObsolete) &&
669             extensionsEqual(extensions, d.extensions))
670        {
671          if (superiorRuleIDs.length != d.superiorRuleIDs.length)
672          {
673            return false;
674          }
675    
676          final HashSet<Integer> s1 = new HashSet<Integer>(superiorRuleIDs.length);
677          final HashSet<Integer> s2 = new HashSet<Integer>(superiorRuleIDs.length);
678          for (final int i : superiorRuleIDs)
679          {
680            s1.add(i);
681          }
682    
683          for (final int i : d.superiorRuleIDs)
684          {
685            s2.add(i);
686          }
687    
688          return s1.equals(s2);
689        }
690        else
691        {
692          return false;
693        }
694      }
695    
696    
697    
698      /**
699       * Retrieves a string representation of this DIT structure rule definition, in
700       * the format described in RFC 4512 section 4.1.7.1.
701       *
702       * @return  A string representation of this DIT structure rule definition.
703       */
704      @Override()
705      public String toString()
706      {
707        return ditStructureRuleString;
708      }
709    }