001/*
002 * Copyright 2007-2017 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2017 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 */
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.ThreadSafety;
035import com.unboundid.util.ThreadSafetyLevel;
036
037import static com.unboundid.ldap.sdk.schema.SchemaMessages.*;
038import static com.unboundid.util.StaticUtils.*;
039import 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)
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    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}