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