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