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}