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