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