001    /*
002     * Copyright 2007-2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2015 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  names            The set of names for this DIT structure rule.  It
345       *                          may be {@code null} or empty if the DIT structure
346       *                          rule should only 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  isObsolete       Indicates whether this DIT structure rule is
350       *                          declared obsolete.
351       * @param  nameFormID       The name or OID of the name form with which this
352       *                          DIT structure rule is associated.  It must not be
353       *                          {@code null}.
354       * @param  superiorRuleIDs  The superior rule IDs for this DIT structure rule.
355       *                          It may be {@code null} or empty if there are no
356       *                          superior rule IDs.
357       * @param  extensions       The set of extensions for this DIT structure rule.
358       *                          It may be {@code null} or empty if there are no
359       *                          extensions.
360       */
361      public DITStructureRuleDefinition(final int ruleID, final String[] names,
362                                        final String description,
363                                        final boolean isObsolete,
364                                        final String nameFormID,
365                                        final int[] superiorRuleIDs,
366                                        final Map<String,String[]> extensions)
367      {
368        ensureNotNull(nameFormID);
369    
370        this.ruleID      = ruleID;
371        this.description = description;
372        this.isObsolete  = isObsolete;
373        this.nameFormID  = nameFormID;
374    
375        if (names == null)
376        {
377          this.names = NO_STRINGS;
378        }
379        else
380        {
381          this.names = names;
382        }
383    
384        if (superiorRuleIDs == null)
385        {
386          this.superiorRuleIDs = NO_INTS;
387        }
388        else
389        {
390          this.superiorRuleIDs = superiorRuleIDs;
391        }
392    
393        if (extensions == null)
394        {
395          this.extensions = Collections.emptyMap();
396        }
397        else
398        {
399          this.extensions = Collections.unmodifiableMap(extensions);
400        }
401    
402        final StringBuilder buffer = new StringBuilder();
403        createDefinitionString(buffer);
404        ditStructureRuleString = buffer.toString();
405      }
406    
407    
408    
409      /**
410       * Constructs a string representation of this DIT content rule definition in
411       * the provided buffer.
412       *
413       * @param  buffer  The buffer in which to construct a string representation of
414       *                 this DIT content rule definition.
415       */
416      private void createDefinitionString(final StringBuilder buffer)
417      {
418        buffer.append("( ");
419        buffer.append(ruleID);
420    
421        if (names.length == 1)
422        {
423          buffer.append(" NAME '");
424          buffer.append(names[0]);
425          buffer.append('\'');
426        }
427        else if (names.length > 1)
428        {
429          buffer.append(" NAME (");
430          for (final String name : names)
431          {
432            buffer.append(" '");
433            buffer.append(name);
434            buffer.append('\'');
435          }
436          buffer.append(" )");
437        }
438    
439        if (description != null)
440        {
441          buffer.append(" DESC '");
442          encodeValue(description, buffer);
443          buffer.append('\'');
444        }
445    
446        if (isObsolete)
447        {
448          buffer.append(" OBSOLETE");
449        }
450    
451        buffer.append(" FORM ");
452        buffer.append(nameFormID);
453    
454        if (superiorRuleIDs.length == 1)
455        {
456          buffer.append(" SUP ");
457          buffer.append(superiorRuleIDs[0]);
458        }
459        else if (superiorRuleIDs.length > 1)
460        {
461          buffer.append(" SUP (");
462          for (final int supID : superiorRuleIDs)
463          {
464            buffer.append(" $ ");
465            buffer.append(supID);
466          }
467          buffer.append(" )");
468        }
469    
470        for (final Map.Entry<String,String[]> e : extensions.entrySet())
471        {
472          final String   name   = e.getKey();
473          final String[] values = e.getValue();
474          if (values.length == 1)
475          {
476            buffer.append(' ');
477            buffer.append(name);
478            buffer.append(" '");
479            encodeValue(values[0], buffer);
480            buffer.append('\'');
481          }
482          else
483          {
484            buffer.append(' ');
485            buffer.append(name);
486            buffer.append(" (");
487            for (final String value : values)
488            {
489              buffer.append(" '");
490              encodeValue(value, buffer);
491              buffer.append('\'');
492            }
493            buffer.append(" )");
494          }
495        }
496    
497        buffer.append(" )");
498      }
499    
500    
501    
502      /**
503       * Retrieves the rule ID for this DIT structure rule.
504       *
505       * @return  The rule ID for this DIT structure rule.
506       */
507      public int getRuleID()
508      {
509        return ruleID;
510      }
511    
512    
513    
514      /**
515       * Retrieves the set of names for this DIT structure rule.
516       *
517       * @return  The set of names for this DIT structure rule, or an empty array if
518       *          it does not have any names.
519       */
520      public String[] getNames()
521      {
522        return names;
523      }
524    
525    
526    
527      /**
528       * Retrieves the primary name that can be used to reference this DIT structure
529       * rule.  If one or more names are defined, then the first name will be used.
530       * Otherwise, the string representation of the rule ID will be returned.
531       *
532       * @return  The primary name that can be used to reference this DIT structure
533       *          rule.
534       */
535      public String getNameOrRuleID()
536      {
537        if (names.length == 0)
538        {
539          return String.valueOf(ruleID);
540        }
541        else
542        {
543          return names[0];
544        }
545      }
546    
547    
548    
549      /**
550       * Indicates whether the provided string matches the rule ID or any of the
551       * names for this DIT structure rule.
552       *
553       * @param  s  The string for which to make the determination.  It must not be
554       *            {@code null}.
555       *
556       * @return  {@code true} if the provided string matches the rule ID or any of
557       *          the names for this DIT structure rule, or {@code false} if not.
558       */
559      public boolean hasNameOrRuleID(final String s)
560      {
561        for (final String name : names)
562        {
563          if (s.equalsIgnoreCase(name))
564          {
565            return true;
566          }
567        }
568    
569        return s.equalsIgnoreCase(String.valueOf(ruleID));
570      }
571    
572    
573    
574      /**
575       * Retrieves the description for this DIT structure rule, if available.
576       *
577       * @return  The description for this DIT structure rule, or {@code null} if
578       *          there is no description defined.
579       */
580      public String getDescription()
581      {
582        return description;
583      }
584    
585    
586    
587      /**
588       * Indicates whether this DIT structure rule is declared obsolete.
589       *
590       * @return  {@code true} if this DIT structure rule is declared obsolete, or
591       *          {@code false} if it is not.
592       */
593      public boolean isObsolete()
594      {
595        return isObsolete;
596      }
597    
598    
599    
600      /**
601       * Retrieves the name or OID of the name form with which this DIT structure
602       * rule is associated.
603       *
604       * @return  The name or OID of the name form with which this DIT structure
605       *          rule is associated.
606       */
607      public String getNameFormID()
608      {
609        return nameFormID;
610      }
611    
612    
613    
614      /**
615       * Retrieves the rule IDs of the superior rules for this DIT structure rule.
616       *
617       * @return  The rule IDs of the superior rules for this DIT structure rule, or
618       *          an empty array if there are no superior rule IDs.
619       */
620      public int[] getSuperiorRuleIDs()
621      {
622        return superiorRuleIDs;
623      }
624    
625    
626    
627      /**
628       * Retrieves the set of extensions for this DIT structure rule.  They will be
629       * mapped from the extension name (which should start with "X-") to the set of
630       * values for that extension.
631       *
632       * @return  The set of extensions for this DIT structure rule.
633       */
634      public Map<String,String[]> getExtensions()
635      {
636        return extensions;
637      }
638    
639    
640    
641      /**
642       * {@inheritDoc}
643       */
644      @Override()
645      public int hashCode()
646      {
647        return ruleID;
648      }
649    
650    
651    
652      /**
653       * {@inheritDoc}
654       */
655      @Override()
656      public boolean equals(final Object o)
657      {
658        if (o == null)
659        {
660          return false;
661        }
662    
663        if (o == this)
664        {
665          return true;
666        }
667    
668        if (! (o instanceof DITStructureRuleDefinition))
669        {
670          return false;
671        }
672    
673        final DITStructureRuleDefinition d = (DITStructureRuleDefinition) o;
674        if ((ruleID == d.ruleID) &&
675             nameFormID.equalsIgnoreCase(d.nameFormID) &&
676             stringsEqualIgnoreCaseOrderIndependent(names, d.names) &&
677             (isObsolete == d.isObsolete) &&
678             extensionsEqual(extensions, d.extensions))
679        {
680          if (superiorRuleIDs.length != d.superiorRuleIDs.length)
681          {
682            return false;
683          }
684    
685          final HashSet<Integer> s1 = new HashSet<Integer>(superiorRuleIDs.length);
686          final HashSet<Integer> s2 = new HashSet<Integer>(superiorRuleIDs.length);
687          for (final int i : superiorRuleIDs)
688          {
689            s1.add(i);
690          }
691    
692          for (final int i : d.superiorRuleIDs)
693          {
694            s2.add(i);
695          }
696    
697          return s1.equals(s2);
698        }
699        else
700        {
701          return false;
702        }
703      }
704    
705    
706    
707      /**
708       * Retrieves a string representation of this DIT structure rule definition, in
709       * the format described in RFC 4512 section 4.1.7.1.
710       *
711       * @return  A string representation of this DIT structure rule definition.
712       */
713      @Override()
714      public String toString()
715      {
716        return ditStructureRuleString;
717      }
718    }