001/*
002 * Copyright 2007-2014 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2014 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.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.ThreadSafety;
034import com.unboundid.util.ThreadSafetyLevel;
035
036import static com.unboundid.ldap.sdk.schema.SchemaMessages.*;
037import static com.unboundid.util.StaticUtils.*;
038import static com.unboundid.util.Validator.*;
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    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  = new LinkedHashMap<String,String[]>();
118
119    while (true)
120    {
121      // Skip over any spaces until we find the next element.
122      pos = skipSpaces(attributeSyntaxString, pos, length);
123
124      // Read until we find the next space or the end of the string.  Use that
125      // token to figure out what to do next.
126      final int tokenStartPos = pos;
127      while ((pos < length) && (attributeSyntaxString.charAt(pos) != ' '))
128      {
129        pos++;
130      }
131
132      final String token = attributeSyntaxString.substring(tokenStartPos, pos);
133      final String lowerToken = toLowerCase(token);
134      if (lowerToken.equals(")"))
135      {
136        // This indicates that we're at the end of the value.  There should not
137        // be any more closing characters.
138        if (pos < length)
139        {
140          throw new LDAPException(ResultCode.DECODING_ERROR,
141                                  ERR_ATTRSYNTAX_DECODE_CLOSE_NOT_AT_END.get(
142                                       attributeSyntaxString));
143        }
144        break;
145      }
146      else if (lowerToken.equals("desc"))
147      {
148        if (descr == null)
149        {
150          pos = skipSpaces(attributeSyntaxString, pos, length);
151
152          buffer = new StringBuilder();
153          pos = readQDString(attributeSyntaxString, pos, length, buffer);
154          descr = buffer.toString();
155        }
156        else
157        {
158          throw new LDAPException(ResultCode.DECODING_ERROR,
159                                  ERR_ATTRSYNTAX_DECODE_MULTIPLE_DESC.get(
160                                       attributeSyntaxString));
161        }
162      }
163      else if (lowerToken.startsWith("x-"))
164      {
165        pos = skipSpaces(attributeSyntaxString, pos, length);
166
167        final ArrayList<String> valueList = new ArrayList<String>();
168        pos = readQDStrings(attributeSyntaxString, pos, length, valueList);
169
170        final String[] values = new String[valueList.size()];
171        valueList.toArray(values);
172
173        if (exts.containsKey(token))
174        {
175          throw new LDAPException(ResultCode.DECODING_ERROR,
176                                  ERR_ATTRSYNTAX_DECODE_DUP_EXT.get(
177                                       attributeSyntaxString, token));
178        }
179
180        exts.put(token, values);
181      }
182      else
183      {
184        throw new LDAPException(ResultCode.DECODING_ERROR,
185                                  ERR_ATTRSYNTAX_DECODE_UNEXPECTED_TOKEN.get(
186                                       attributeSyntaxString, token));
187      }
188    }
189
190    description = descr;
191    extensions  = Collections.unmodifiableMap(exts);
192  }
193
194
195
196  /**
197   * Creates a new attribute syntax use with the provided information.
198   *
199   * @param  oid          The OID for this attribute syntax.  It must not be
200   *                      {@code null}.
201   * @param  description  The description for this attribute syntax.  It may be
202   *                      {@code null} if there is no description.
203   * @param  extensions   The set of extensions for this attribute syntax.  It
204   *                      may be {@code null} or empty if there should not be
205   *                      any extensions.
206   */
207  public AttributeSyntaxDefinition(final String oid, final String description,
208                                   final Map<String,String[]> extensions)
209  {
210    ensureNotNull(oid);
211
212    this.oid         = oid;
213    this.description = description;
214
215    if (extensions == null)
216    {
217      this.extensions = Collections.emptyMap();
218    }
219    else
220    {
221      this.extensions = Collections.unmodifiableMap(extensions);
222    }
223
224    final StringBuilder buffer = new StringBuilder();
225    createDefinitionString(buffer);
226    attributeSyntaxString = buffer.toString();
227  }
228
229
230
231  /**
232   * Constructs a string representation of this attribute syntax definition in
233   * the provided buffer.
234   *
235   * @param  buffer  The buffer in which to construct a string representation of
236   *                 this attribute syntax definition.
237   */
238  private void createDefinitionString(final StringBuilder buffer)
239  {
240    buffer.append("( ");
241    buffer.append(oid);
242
243    if (description != null)
244    {
245      buffer.append(" DESC '");
246      encodeValue(description, buffer);
247      buffer.append('\'');
248    }
249
250    for (final Map.Entry<String,String[]> e : extensions.entrySet())
251    {
252      final String   name   = e.getKey();
253      final String[] values = e.getValue();
254      if (values.length == 1)
255      {
256        buffer.append(' ');
257        buffer.append(name);
258        buffer.append(" '");
259        encodeValue(values[0], buffer);
260        buffer.append('\'');
261      }
262      else
263      {
264        buffer.append(' ');
265        buffer.append(name);
266        buffer.append(" (");
267        for (final String value : values)
268        {
269          buffer.append(" '");
270          encodeValue(value, buffer);
271          buffer.append('\'');
272        }
273        buffer.append(" )");
274      }
275    }
276
277    buffer.append(" )");
278  }
279
280
281
282  /**
283   * Retrieves the OID for this attribute syntax.
284   *
285   * @return  The OID for this attribute syntax.
286   */
287  public String getOID()
288  {
289    return oid;
290  }
291
292
293
294  /**
295   * Retrieves the description for this attribute syntax, if available.
296   *
297   * @return  The description for this attribute syntax, or {@code null} if
298   *          there is no description defined.
299   */
300  public String getDescription()
301  {
302    return description;
303  }
304
305
306
307  /**
308   * Retrieves the set of extensions for this matching rule use.  They will be
309   * mapped from the extension name (which should start with "X-") to the set
310   * of values for that extension.
311   *
312   * @return  The set of extensions for this matching rule use.
313   */
314  public Map<String,String[]> getExtensions()
315  {
316    return extensions;
317  }
318
319
320
321  /**
322   * {@inheritDoc}
323   */
324  @Override()
325  public int hashCode()
326  {
327    return oid.hashCode();
328  }
329
330
331
332  /**
333   * {@inheritDoc}
334   */
335  @Override()
336  public boolean equals(final Object o)
337  {
338    if (o == null)
339    {
340      return false;
341    }
342
343    if (o == this)
344    {
345      return true;
346    }
347
348    if (! (o instanceof AttributeSyntaxDefinition))
349    {
350      return false;
351    }
352
353    final AttributeSyntaxDefinition d = (AttributeSyntaxDefinition) o;
354    return (oid.equals(d.oid) &&
355         bothNullOrEqualIgnoreCase(description, d.description) &&
356         extensionsEqual(extensions, d.extensions));
357  }
358
359
360
361  /**
362   * Retrieves a string representation of this attribute syntax, in the format
363   * described in RFC 4512 section 4.1.5.
364   *
365   * @return  A string representation of this attribute syntax definition.
366   */
367  @Override()
368  public String toString()
369  {
370    return attributeSyntaxString;
371  }
372}