001 /*
002 * Copyright 2007-2013 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2008-2013 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 }