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