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 */
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 }