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.Collections;
027import java.util.Map;
028import java.util.LinkedHashMap;
029
030import com.unboundid.ldap.sdk.LDAPException;
031import com.unboundid.ldap.sdk.ResultCode;
032import com.unboundid.util.NotMutable;
033import com.unboundid.util.StaticUtils;
034import com.unboundid.util.ThreadSafety;
035import com.unboundid.util.ThreadSafetyLevel;
036import com.unboundid.util.Validator;
037
038import static com.unboundid.ldap.sdk.schema.SchemaMessages.*;
039
040
041
042/**
043 * This class provides a data structure that describes an LDAP attribute syntax
044 * schema element.
045 */
046@NotMutable()
047@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
048public final class AttributeSyntaxDefinition
049       extends SchemaElement
050{
051  /**
052   * The serial version UID for this serializable class.
053   */
054  private static final long serialVersionUID = 8593718232711987488L;
055
056
057
058  // The set of extensions for this attribute syntax.
059  private final Map<String,String[]> extensions;
060
061  // The description for this attribute syntax.
062  private final String description;
063
064  // The string representation of this attribute syntax.
065  private final String attributeSyntaxString;
066
067  // The OID for this attribute syntax.
068  private final String oid;
069
070
071
072  /**
073   * Creates a new attribute syntax from the provided string representation.
074   *
075   * @param  s  The string representation of the attribute syntax to create,
076   *            using the syntax described in RFC 4512 section 4.1.5.  It must
077   *            not be {@code null}.
078   *
079   * @throws  LDAPException  If the provided string cannot be decoded as an
080   *                         attribute syntax definition.
081   */
082  public AttributeSyntaxDefinition(final String s)
083         throws LDAPException
084  {
085    Validator.ensureNotNull(s);
086
087    attributeSyntaxString = s.trim();
088
089    // The first character must be an opening parenthesis.
090    final int length = attributeSyntaxString.length();
091    if (length == 0)
092    {
093      throw new LDAPException(ResultCode.DECODING_ERROR,
094                              ERR_ATTRSYNTAX_DECODE_EMPTY.get());
095    }
096    else if (attributeSyntaxString.charAt(0) != '(')
097    {
098      throw new LDAPException(ResultCode.DECODING_ERROR,
099                              ERR_ATTRSYNTAX_DECODE_NO_OPENING_PAREN.get(
100                                   attributeSyntaxString));
101    }
102
103
104    // Skip over any spaces until we reach the start of the OID, then read the
105    // OID until we find the next space.
106    int pos = skipSpaces(attributeSyntaxString, 1, length);
107
108    StringBuilder buffer = new StringBuilder();
109    pos = readOID(attributeSyntaxString, pos, length, buffer);
110    oid = buffer.toString();
111
112
113    // Technically, attribute syntax elements are supposed to appear in a
114    // specific order, but we'll be lenient and allow remaining elements to come
115    // in any order.
116    String descr = null;
117    final Map<String,String[]> exts  =
118         new LinkedHashMap<>(StaticUtils.computeMapCapacity(5));
119
120    while (true)
121    {
122      // Skip over any spaces until we find the next element.
123      pos = skipSpaces(attributeSyntaxString, pos, length);
124
125      // Read until we find the next space or the end of the string.  Use that
126      // token to figure out what to do next.
127      final int tokenStartPos = pos;
128      while ((pos < length) && (attributeSyntaxString.charAt(pos) != ' '))
129      {
130        pos++;
131      }
132
133      final String token = attributeSyntaxString.substring(tokenStartPos, pos);
134      final String lowerToken = StaticUtils.toLowerCase(token);
135      if (lowerToken.equals(")"))
136      {
137        // This indicates that we're at the end of the value.  There should not
138        // be any more closing characters.
139        if (pos < length)
140        {
141          throw new LDAPException(ResultCode.DECODING_ERROR,
142                                  ERR_ATTRSYNTAX_DECODE_CLOSE_NOT_AT_END.get(
143                                       attributeSyntaxString));
144        }
145        break;
146      }
147      else if (lowerToken.equals("desc"))
148      {
149        if (descr == null)
150        {
151          pos = skipSpaces(attributeSyntaxString, pos, length);
152
153          buffer = new StringBuilder();
154          pos = readQDString(attributeSyntaxString, pos, length, buffer);
155          descr = buffer.toString();
156        }
157        else
158        {
159          throw new LDAPException(ResultCode.DECODING_ERROR,
160                                  ERR_ATTRSYNTAX_DECODE_MULTIPLE_DESC.get(
161                                       attributeSyntaxString));
162        }
163      }
164      else if (lowerToken.startsWith("x-"))
165      {
166        pos = skipSpaces(attributeSyntaxString, pos, length);
167
168        final ArrayList<String> valueList = new ArrayList<>(5);
169        pos = readQDStrings(attributeSyntaxString, pos, length, valueList);
170
171        final String[] values = new String[valueList.size()];
172        valueList.toArray(values);
173
174        if (exts.containsKey(token))
175        {
176          throw new LDAPException(ResultCode.DECODING_ERROR,
177                                  ERR_ATTRSYNTAX_DECODE_DUP_EXT.get(
178                                       attributeSyntaxString, token));
179        }
180
181        exts.put(token, values);
182      }
183      else
184      {
185        throw new LDAPException(ResultCode.DECODING_ERROR,
186                                  ERR_ATTRSYNTAX_DECODE_UNEXPECTED_TOKEN.get(
187                                       attributeSyntaxString, token));
188      }
189    }
190
191    description = descr;
192    extensions  = Collections.unmodifiableMap(exts);
193  }
194
195
196
197  /**
198   * Creates a new attribute syntax use with the provided information.
199   *
200   * @param  oid          The OID for this attribute syntax.  It must not be
201   *                      {@code null}.
202   * @param  description  The description for this attribute syntax.  It may be
203   *                      {@code null} if there is no description.
204   * @param  extensions   The set of extensions for this attribute syntax.  It
205   *                      may be {@code null} or empty if there should not be
206   *                      any extensions.
207   */
208  public AttributeSyntaxDefinition(final String oid, final String description,
209                                   final Map<String,String[]> extensions)
210  {
211    Validator.ensureNotNull(oid);
212
213    this.oid         = oid;
214    this.description = description;
215
216    if (extensions == null)
217    {
218      this.extensions = Collections.emptyMap();
219    }
220    else
221    {
222      this.extensions = Collections.unmodifiableMap(extensions);
223    }
224
225    final StringBuilder buffer = new StringBuilder();
226    createDefinitionString(buffer);
227    attributeSyntaxString = buffer.toString();
228  }
229
230
231
232  /**
233   * Constructs a string representation of this attribute syntax definition in
234   * the provided buffer.
235   *
236   * @param  buffer  The buffer in which to construct a string representation of
237   *                 this attribute syntax definition.
238   */
239  private void createDefinitionString(final StringBuilder buffer)
240  {
241    buffer.append("( ");
242    buffer.append(oid);
243
244    if (description != null)
245    {
246      buffer.append(" DESC '");
247      encodeValue(description, buffer);
248      buffer.append('\'');
249    }
250
251    for (final Map.Entry<String,String[]> e : extensions.entrySet())
252    {
253      final String   name   = e.getKey();
254      final String[] values = e.getValue();
255      if (values.length == 1)
256      {
257        buffer.append(' ');
258        buffer.append(name);
259        buffer.append(" '");
260        encodeValue(values[0], buffer);
261        buffer.append('\'');
262      }
263      else
264      {
265        buffer.append(' ');
266        buffer.append(name);
267        buffer.append(" (");
268        for (final String value : values)
269        {
270          buffer.append(" '");
271          encodeValue(value, buffer);
272          buffer.append('\'');
273        }
274        buffer.append(" )");
275      }
276    }
277
278    buffer.append(" )");
279  }
280
281
282
283  /**
284   * Retrieves the OID for this attribute syntax.
285   *
286   * @return  The OID for this attribute syntax.
287   */
288  public String getOID()
289  {
290    return oid;
291  }
292
293
294
295  /**
296   * Retrieves the description for this attribute syntax, if available.
297   *
298   * @return  The description for this attribute syntax, or {@code null} if
299   *          there is no description defined.
300   */
301  public String getDescription()
302  {
303    return description;
304  }
305
306
307
308  /**
309   * Retrieves the set of extensions for this matching rule use.  They will be
310   * mapped from the extension name (which should start with "X-") to the set
311   * of values for that extension.
312   *
313   * @return  The set of extensions for this matching rule use.
314   */
315  public Map<String,String[]> getExtensions()
316  {
317    return extensions;
318  }
319
320
321
322  /**
323   * {@inheritDoc}
324   */
325  @Override()
326  public int hashCode()
327  {
328    return oid.hashCode();
329  }
330
331
332
333  /**
334   * {@inheritDoc}
335   */
336  @Override()
337  public boolean equals(final Object o)
338  {
339    if (o == null)
340    {
341      return false;
342    }
343
344    if (o == this)
345    {
346      return true;
347    }
348
349    if (! (o instanceof AttributeSyntaxDefinition))
350    {
351      return false;
352    }
353
354    final AttributeSyntaxDefinition d = (AttributeSyntaxDefinition) o;
355    return (oid.equals(d.oid) &&
356         StaticUtils.bothNullOrEqualIgnoreCase(description, d.description) &&
357         extensionsEqual(extensions, d.extensions));
358  }
359
360
361
362  /**
363   * Retrieves a string representation of this attribute syntax, in the format
364   * described in RFC 4512 section 4.1.5.
365   *
366   * @return  A string representation of this attribute syntax definition.
367   */
368  @Override()
369  public String toString()
370  {
371    return attributeSyntaxString;
372  }
373}