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