001    /*
002     * Copyright 2007-2016 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2016 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     */
021    package com.unboundid.ldap.sdk.schema;
022    
023    
024    
025    import java.util.ArrayList;
026    import java.util.Collections;
027    import java.util.Map;
028    import java.util.LinkedHashMap;
029    
030    import com.unboundid.ldap.sdk.LDAPException;
031    import com.unboundid.ldap.sdk.ResultCode;
032    import com.unboundid.util.NotMutable;
033    import com.unboundid.util.ThreadSafety;
034    import com.unboundid.util.ThreadSafetyLevel;
035    
036    import static com.unboundid.ldap.sdk.schema.SchemaMessages.*;
037    import static com.unboundid.util.StaticUtils.*;
038    import 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)
048    public 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    }