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    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          // It's possible that the token could be smashed right up against the
171          // closing parenthesis.  If that's the case, then extract just the token
172          // and handle the closing parenthesis the next time through.
173          String token = ditStructureRuleString.substring(tokenStartPos, pos);
174          if ((token.length() > 1) && (token.endsWith(")")))
175          {
176            token = token.substring(0, token.length() - 1);
177            pos--;
178          }
179    
180          final String lowerToken = toLowerCase(token);
181          if (lowerToken.equals(")"))
182          {
183            // This indicates that we're at the end of the value.  There should not
184            // be any more closing characters.
185            if (pos < length)
186            {
187              throw new LDAPException(ResultCode.DECODING_ERROR,
188                                      ERR_DSR_DECODE_CLOSE_NOT_AT_END.get(
189                                           ditStructureRuleString));
190            }
191            break;
192          }
193          else if (lowerToken.equals("name"))
194          {
195            if (nameList.isEmpty())
196            {
197              pos = skipSpaces(ditStructureRuleString, pos, length);
198              pos = readQDStrings(ditStructureRuleString, pos, length, nameList);
199            }
200            else
201            {
202              throw new LDAPException(ResultCode.DECODING_ERROR,
203                                      ERR_DSR_DECODE_MULTIPLE_ELEMENTS.get(
204                                           ditStructureRuleString, "NAME"));
205            }
206          }
207          else if (lowerToken.equals("desc"))
208          {
209            if (descr == null)
210            {
211              pos = skipSpaces(ditStructureRuleString, pos, length);
212    
213              buffer = new StringBuilder();
214              pos = readQDString(ditStructureRuleString, pos, length, buffer);
215              descr = buffer.toString();
216            }
217            else
218            {
219              throw new LDAPException(ResultCode.DECODING_ERROR,
220                                      ERR_DSR_DECODE_MULTIPLE_ELEMENTS.get(
221                                           ditStructureRuleString, "DESC"));
222            }
223          }
224          else if (lowerToken.equals("obsolete"))
225          {
226            if (obsolete == null)
227            {
228              obsolete = true;
229            }
230            else
231            {
232              throw new LDAPException(ResultCode.DECODING_ERROR,
233                                      ERR_DSR_DECODE_MULTIPLE_ELEMENTS.get(
234                                           ditStructureRuleString, "OBSOLETE"));
235            }
236          }
237          else if (lowerToken.equals("form"))
238          {
239            if (nfID == null)
240            {
241              pos = skipSpaces(ditStructureRuleString, pos, length);
242    
243              buffer = new StringBuilder();
244              pos = readOID(ditStructureRuleString, pos, length, buffer);
245              nfID = buffer.toString();
246            }
247            else
248            {
249              throw new LDAPException(ResultCode.DECODING_ERROR,
250                                      ERR_DSR_DECODE_MULTIPLE_ELEMENTS.get(
251                                           ditStructureRuleString, "FORM"));
252            }
253          }
254          else if (lowerToken.equals("sup"))
255          {
256            if (supList.isEmpty())
257            {
258              final ArrayList<String> supStrs = new ArrayList<String>(1);
259    
260              pos = skipSpaces(ditStructureRuleString, pos, length);
261              pos = readOIDs(ditStructureRuleString, pos, length, supStrs);
262    
263              supList.ensureCapacity(supStrs.size());
264              for (final String supStr : supStrs)
265              {
266                try
267                {
268                  supList.add(Integer.parseInt(supStr));
269                }
270                catch (NumberFormatException nfe)
271                {
272                  debugException(nfe);
273                  throw new LDAPException(ResultCode.DECODING_ERROR,
274                                          ERR_DSR_DECODE_SUP_ID_NOT_INT.get(
275                                               ditStructureRuleString),
276                                          nfe);
277                }
278              }
279            }
280            else
281            {
282              throw new LDAPException(ResultCode.DECODING_ERROR,
283                                      ERR_DSR_DECODE_MULTIPLE_ELEMENTS.get(
284                                           ditStructureRuleString, "SUP"));
285            }
286          }
287          else if (lowerToken.startsWith("x-"))
288          {
289            pos = skipSpaces(ditStructureRuleString, pos, length);
290    
291            final ArrayList<String> valueList = new ArrayList<String>();
292            pos = readQDStrings(ditStructureRuleString, pos, length, valueList);
293    
294            final String[] values = new String[valueList.size()];
295            valueList.toArray(values);
296    
297            if (exts.containsKey(token))
298            {
299              throw new LDAPException(ResultCode.DECODING_ERROR,
300                                      ERR_DSR_DECODE_DUP_EXT.get(
301                                           ditStructureRuleString, token));
302            }
303    
304            exts.put(token, values);
305          }
306          else
307          {
308            throw new LDAPException(ResultCode.DECODING_ERROR,
309                                    ERR_DSR_DECODE_UNEXPECTED_TOKEN.get(
310                                         ditStructureRuleString, token));
311          }
312        }
313    
314        description = descr;
315        nameFormID  = nfID;
316    
317        if (nameFormID == null)
318        {
319          throw new LDAPException(ResultCode.DECODING_ERROR,
320                                  ERR_DSR_DECODE_NO_FORM.get(
321                                       ditStructureRuleString));
322        }
323    
324        names = new String[nameList.size()];
325        nameList.toArray(names);
326    
327        superiorRuleIDs = new int[supList.size()];
328        for (int i=0; i < superiorRuleIDs.length; i++)
329        {
330          superiorRuleIDs[i] = supList.get(i);
331        }
332    
333        isObsolete = (obsolete != null);
334    
335        extensions = Collections.unmodifiableMap(exts);
336      }
337    
338    
339    
340      /**
341       * Creates a new DIT structure rule with the provided information.
342       *
343       * @param  ruleID          The rule ID for this DIT structure rule.
344       * @param  name            The name for this DIT structure rule.  It may be
345       *                         {@code null} if the DIT structure rule should only
346       *                         be referenced by rule ID.
347       * @param  description     The description for this DIT structure rule.  It
348       *                         may be {@code null} if there is no description.
349       * @param  nameFormID      The name or OID of the name form with which this
350       *                         DIT structure rule is associated.  It must not be
351       *                         {@code null}.
352       * @param  superiorRuleID  The superior rule ID for this DIT structure rule.
353       *                         It may be {@code null} if there are no superior
354       *                         rule IDs.
355       * @param  extensions      The set of extensions for this DIT structure rule.
356       *                         It may be {@code null} or empty if there are no
357       *                         extensions.
358       */
359      public DITStructureRuleDefinition(final int ruleID, final String name,
360                                        final String description,
361                                        final String nameFormID,
362                                        final Integer superiorRuleID,
363                                        final Map<String,String[]> extensions)
364      {
365        this(ruleID, ((name == null) ? null : new String[] { name }), description,
366             false, nameFormID,
367             ((superiorRuleID == null) ? null : new int[] { superiorRuleID }),
368             extensions);
369      }
370    
371    
372    
373      /**
374       * Creates a new DIT structure rule with the provided information.
375       *
376       * @param  ruleID           The rule ID for this DIT structure rule.
377       * @param  names            The set of names for this DIT structure rule.  It
378       *                          may be {@code null} or empty if the DIT structure
379       *                          rule should only be referenced by rule ID.
380       * @param  description      The description for this DIT structure rule.  It
381       *                          may be {@code null} if there is no description.
382       * @param  isObsolete       Indicates whether this DIT structure rule is
383       *                          declared obsolete.
384       * @param  nameFormID       The name or OID of the name form with which this
385       *                          DIT structure rule is associated.  It must not be
386       *                          {@code null}.
387       * @param  superiorRuleIDs  The superior rule IDs for this DIT structure rule.
388       *                          It may be {@code null} or empty if there are no
389       *                          superior rule IDs.
390       * @param  extensions       The set of extensions for this DIT structure rule.
391       *                          It may be {@code null} or empty if there are no
392       *                          extensions.
393       */
394      public DITStructureRuleDefinition(final int ruleID, final String[] names,
395                                        final String description,
396                                        final boolean isObsolete,
397                                        final String nameFormID,
398                                        final int[] superiorRuleIDs,
399                                        final Map<String,String[]> extensions)
400      {
401        ensureNotNull(nameFormID);
402    
403        this.ruleID      = ruleID;
404        this.description = description;
405        this.isObsolete  = isObsolete;
406        this.nameFormID  = nameFormID;
407    
408        if (names == null)
409        {
410          this.names = NO_STRINGS;
411        }
412        else
413        {
414          this.names = names;
415        }
416    
417        if (superiorRuleIDs == null)
418        {
419          this.superiorRuleIDs = NO_INTS;
420        }
421        else
422        {
423          this.superiorRuleIDs = superiorRuleIDs;
424        }
425    
426        if (extensions == null)
427        {
428          this.extensions = Collections.emptyMap();
429        }
430        else
431        {
432          this.extensions = Collections.unmodifiableMap(extensions);
433        }
434    
435        final StringBuilder buffer = new StringBuilder();
436        createDefinitionString(buffer);
437        ditStructureRuleString = buffer.toString();
438      }
439    
440    
441    
442      /**
443       * Constructs a string representation of this DIT content rule definition in
444       * the provided buffer.
445       *
446       * @param  buffer  The buffer in which to construct a string representation of
447       *                 this DIT content rule definition.
448       */
449      private void createDefinitionString(final StringBuilder buffer)
450      {
451        buffer.append("( ");
452        buffer.append(ruleID);
453    
454        if (names.length == 1)
455        {
456          buffer.append(" NAME '");
457          buffer.append(names[0]);
458          buffer.append('\'');
459        }
460        else if (names.length > 1)
461        {
462          buffer.append(" NAME (");
463          for (final String name : names)
464          {
465            buffer.append(" '");
466            buffer.append(name);
467            buffer.append('\'');
468          }
469          buffer.append(" )");
470        }
471    
472        if (description != null)
473        {
474          buffer.append(" DESC '");
475          encodeValue(description, buffer);
476          buffer.append('\'');
477        }
478    
479        if (isObsolete)
480        {
481          buffer.append(" OBSOLETE");
482        }
483    
484        buffer.append(" FORM ");
485        buffer.append(nameFormID);
486    
487        if (superiorRuleIDs.length == 1)
488        {
489          buffer.append(" SUP ");
490          buffer.append(superiorRuleIDs[0]);
491        }
492        else if (superiorRuleIDs.length > 1)
493        {
494          buffer.append(" SUP (");
495          for (final int supID : superiorRuleIDs)
496          {
497            buffer.append(" $ ");
498            buffer.append(supID);
499          }
500          buffer.append(" )");
501        }
502    
503        for (final Map.Entry<String,String[]> e : extensions.entrySet())
504        {
505          final String   name   = e.getKey();
506          final String[] values = e.getValue();
507          if (values.length == 1)
508          {
509            buffer.append(' ');
510            buffer.append(name);
511            buffer.append(" '");
512            encodeValue(values[0], buffer);
513            buffer.append('\'');
514          }
515          else
516          {
517            buffer.append(' ');
518            buffer.append(name);
519            buffer.append(" (");
520            for (final String value : values)
521            {
522              buffer.append(" '");
523              encodeValue(value, buffer);
524              buffer.append('\'');
525            }
526            buffer.append(" )");
527          }
528        }
529    
530        buffer.append(" )");
531      }
532    
533    
534    
535      /**
536       * Retrieves the rule ID for this DIT structure rule.
537       *
538       * @return  The rule ID for this DIT structure rule.
539       */
540      public int getRuleID()
541      {
542        return ruleID;
543      }
544    
545    
546    
547      /**
548       * Retrieves the set of names for this DIT structure rule.
549       *
550       * @return  The set of names for this DIT structure rule, or an empty array if
551       *          it does not have any names.
552       */
553      public String[] getNames()
554      {
555        return names;
556      }
557    
558    
559    
560      /**
561       * Retrieves the primary name that can be used to reference this DIT structure
562       * rule.  If one or more names are defined, then the first name will be used.
563       * Otherwise, the string representation of the rule ID will be returned.
564       *
565       * @return  The primary name that can be used to reference this DIT structure
566       *          rule.
567       */
568      public String getNameOrRuleID()
569      {
570        if (names.length == 0)
571        {
572          return String.valueOf(ruleID);
573        }
574        else
575        {
576          return names[0];
577        }
578      }
579    
580    
581    
582      /**
583       * Indicates whether the provided string matches the rule ID or any of the
584       * names for this DIT structure rule.
585       *
586       * @param  s  The string for which to make the determination.  It must not be
587       *            {@code null}.
588       *
589       * @return  {@code true} if the provided string matches the rule ID or any of
590       *          the names for this DIT structure rule, or {@code false} if not.
591       */
592      public boolean hasNameOrRuleID(final String s)
593      {
594        for (final String name : names)
595        {
596          if (s.equalsIgnoreCase(name))
597          {
598            return true;
599          }
600        }
601    
602        return s.equalsIgnoreCase(String.valueOf(ruleID));
603      }
604    
605    
606    
607      /**
608       * Retrieves the description for this DIT structure rule, if available.
609       *
610       * @return  The description for this DIT structure rule, or {@code null} if
611       *          there is no description defined.
612       */
613      public String getDescription()
614      {
615        return description;
616      }
617    
618    
619    
620      /**
621       * Indicates whether this DIT structure rule is declared obsolete.
622       *
623       * @return  {@code true} if this DIT structure rule is declared obsolete, or
624       *          {@code false} if it is not.
625       */
626      public boolean isObsolete()
627      {
628        return isObsolete;
629      }
630    
631    
632    
633      /**
634       * Retrieves the name or OID of the name form with which this DIT structure
635       * rule is associated.
636       *
637       * @return  The name or OID of the name form with which this DIT structure
638       *          rule is associated.
639       */
640      public String getNameFormID()
641      {
642        return nameFormID;
643      }
644    
645    
646    
647      /**
648       * Retrieves the rule IDs of the superior rules for this DIT structure rule.
649       *
650       * @return  The rule IDs of the superior rules for this DIT structure rule, or
651       *          an empty array if there are no superior rule IDs.
652       */
653      public int[] getSuperiorRuleIDs()
654      {
655        return superiorRuleIDs;
656      }
657    
658    
659    
660      /**
661       * Retrieves the set of extensions for this DIT structure rule.  They will be
662       * mapped from the extension name (which should start with "X-") to the set of
663       * values for that extension.
664       *
665       * @return  The set of extensions for this DIT structure rule.
666       */
667      public Map<String,String[]> getExtensions()
668      {
669        return extensions;
670      }
671    
672    
673    
674      /**
675       * {@inheritDoc}
676       */
677      @Override()
678      public int hashCode()
679      {
680        return ruleID;
681      }
682    
683    
684    
685      /**
686       * {@inheritDoc}
687       */
688      @Override()
689      public boolean equals(final Object o)
690      {
691        if (o == null)
692        {
693          return false;
694        }
695    
696        if (o == this)
697        {
698          return true;
699        }
700    
701        if (! (o instanceof DITStructureRuleDefinition))
702        {
703          return false;
704        }
705    
706        final DITStructureRuleDefinition d = (DITStructureRuleDefinition) o;
707        if ((ruleID == d.ruleID) &&
708             nameFormID.equalsIgnoreCase(d.nameFormID) &&
709             stringsEqualIgnoreCaseOrderIndependent(names, d.names) &&
710             (isObsolete == d.isObsolete) &&
711             extensionsEqual(extensions, d.extensions))
712        {
713          if (superiorRuleIDs.length != d.superiorRuleIDs.length)
714          {
715            return false;
716          }
717    
718          final HashSet<Integer> s1 = new HashSet<Integer>(superiorRuleIDs.length);
719          final HashSet<Integer> s2 = new HashSet<Integer>(superiorRuleIDs.length);
720          for (final int i : superiorRuleIDs)
721          {
722            s1.add(i);
723          }
724    
725          for (final int i : d.superiorRuleIDs)
726          {
727            s2.add(i);
728          }
729    
730          return s1.equals(s2);
731        }
732        else
733        {
734          return false;
735        }
736      }
737    
738    
739    
740      /**
741       * Retrieves a string representation of this DIT structure rule definition, in
742       * the format described in RFC 4512 section 4.1.7.1.
743       *
744       * @return  A string representation of this DIT structure rule definition.
745       */
746      @Override()
747      public String toString()
748      {
749        return ditStructureRuleString;
750      }
751    }