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