001/* 002 * Copyright 2007-2019 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2019 Ping Identity Corporation 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.StaticUtils; 034import com.unboundid.util.ThreadSafety; 035import com.unboundid.util.ThreadSafetyLevel; 036import com.unboundid.util.Validator; 037 038import static com.unboundid.ldap.sdk.schema.SchemaMessages.*; 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 Validator.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 = 118 new LinkedHashMap<>(StaticUtils.computeMapCapacity(5)); 119 120 while (true) 121 { 122 // Skip over any spaces until we find the next element. 123 pos = skipSpaces(attributeSyntaxString, pos, length); 124 125 // Read until we find the next space or the end of the string. Use that 126 // token to figure out what to do next. 127 final int tokenStartPos = pos; 128 while ((pos < length) && (attributeSyntaxString.charAt(pos) != ' ')) 129 { 130 pos++; 131 } 132 133 final String token = attributeSyntaxString.substring(tokenStartPos, pos); 134 final String lowerToken = StaticUtils.toLowerCase(token); 135 if (lowerToken.equals(")")) 136 { 137 // This indicates that we're at the end of the value. There should not 138 // be any more closing characters. 139 if (pos < length) 140 { 141 throw new LDAPException(ResultCode.DECODING_ERROR, 142 ERR_ATTRSYNTAX_DECODE_CLOSE_NOT_AT_END.get( 143 attributeSyntaxString)); 144 } 145 break; 146 } 147 else if (lowerToken.equals("desc")) 148 { 149 if (descr == null) 150 { 151 pos = skipSpaces(attributeSyntaxString, pos, length); 152 153 buffer = new StringBuilder(); 154 pos = readQDString(attributeSyntaxString, pos, length, buffer); 155 descr = buffer.toString(); 156 } 157 else 158 { 159 throw new LDAPException(ResultCode.DECODING_ERROR, 160 ERR_ATTRSYNTAX_DECODE_MULTIPLE_DESC.get( 161 attributeSyntaxString)); 162 } 163 } 164 else if (lowerToken.startsWith("x-")) 165 { 166 pos = skipSpaces(attributeSyntaxString, pos, length); 167 168 final ArrayList<String> valueList = new ArrayList<>(5); 169 pos = readQDStrings(attributeSyntaxString, pos, length, valueList); 170 171 final String[] values = new String[valueList.size()]; 172 valueList.toArray(values); 173 174 if (exts.containsKey(token)) 175 { 176 throw new LDAPException(ResultCode.DECODING_ERROR, 177 ERR_ATTRSYNTAX_DECODE_DUP_EXT.get( 178 attributeSyntaxString, token)); 179 } 180 181 exts.put(token, values); 182 } 183 else 184 { 185 throw new LDAPException(ResultCode.DECODING_ERROR, 186 ERR_ATTRSYNTAX_DECODE_UNEXPECTED_TOKEN.get( 187 attributeSyntaxString, token)); 188 } 189 } 190 191 description = descr; 192 extensions = Collections.unmodifiableMap(exts); 193 } 194 195 196 197 /** 198 * Creates a new attribute syntax use with the provided information. 199 * 200 * @param oid The OID for this attribute syntax. It must not be 201 * {@code null}. 202 * @param description The description for this attribute syntax. It may be 203 * {@code null} if there is no description. 204 * @param extensions The set of extensions for this attribute syntax. It 205 * may be {@code null} or empty if there should not be 206 * any extensions. 207 */ 208 public AttributeSyntaxDefinition(final String oid, final String description, 209 final Map<String,String[]> extensions) 210 { 211 Validator.ensureNotNull(oid); 212 213 this.oid = oid; 214 this.description = description; 215 216 if (extensions == null) 217 { 218 this.extensions = Collections.emptyMap(); 219 } 220 else 221 { 222 this.extensions = Collections.unmodifiableMap(extensions); 223 } 224 225 final StringBuilder buffer = new StringBuilder(); 226 createDefinitionString(buffer); 227 attributeSyntaxString = buffer.toString(); 228 } 229 230 231 232 /** 233 * Constructs a string representation of this attribute syntax definition in 234 * the provided buffer. 235 * 236 * @param buffer The buffer in which to construct a string representation of 237 * this attribute syntax definition. 238 */ 239 private void createDefinitionString(final StringBuilder buffer) 240 { 241 buffer.append("( "); 242 buffer.append(oid); 243 244 if (description != null) 245 { 246 buffer.append(" DESC '"); 247 encodeValue(description, buffer); 248 buffer.append('\''); 249 } 250 251 for (final Map.Entry<String,String[]> e : extensions.entrySet()) 252 { 253 final String name = e.getKey(); 254 final String[] values = e.getValue(); 255 if (values.length == 1) 256 { 257 buffer.append(' '); 258 buffer.append(name); 259 buffer.append(" '"); 260 encodeValue(values[0], buffer); 261 buffer.append('\''); 262 } 263 else 264 { 265 buffer.append(' '); 266 buffer.append(name); 267 buffer.append(" ("); 268 for (final String value : values) 269 { 270 buffer.append(" '"); 271 encodeValue(value, buffer); 272 buffer.append('\''); 273 } 274 buffer.append(" )"); 275 } 276 } 277 278 buffer.append(" )"); 279 } 280 281 282 283 /** 284 * Retrieves the OID for this attribute syntax. 285 * 286 * @return The OID for this attribute syntax. 287 */ 288 public String getOID() 289 { 290 return oid; 291 } 292 293 294 295 /** 296 * Retrieves the description for this attribute syntax, if available. 297 * 298 * @return The description for this attribute syntax, or {@code null} if 299 * there is no description defined. 300 */ 301 public String getDescription() 302 { 303 return description; 304 } 305 306 307 308 /** 309 * Retrieves the set of extensions for this matching rule use. They will be 310 * mapped from the extension name (which should start with "X-") to the set 311 * of values for that extension. 312 * 313 * @return The set of extensions for this matching rule use. 314 */ 315 public Map<String,String[]> getExtensions() 316 { 317 return extensions; 318 } 319 320 321 322 /** 323 * {@inheritDoc} 324 */ 325 @Override() 326 public int hashCode() 327 { 328 return oid.hashCode(); 329 } 330 331 332 333 /** 334 * {@inheritDoc} 335 */ 336 @Override() 337 public boolean equals(final Object o) 338 { 339 if (o == null) 340 { 341 return false; 342 } 343 344 if (o == this) 345 { 346 return true; 347 } 348 349 if (! (o instanceof AttributeSyntaxDefinition)) 350 { 351 return false; 352 } 353 354 final AttributeSyntaxDefinition d = (AttributeSyntaxDefinition) o; 355 return (oid.equals(d.oid) && 356 StaticUtils.bothNullOrEqualIgnoreCase(description, d.description) && 357 extensionsEqual(extensions, d.extensions)); 358 } 359 360 361 362 /** 363 * Retrieves a string representation of this attribute syntax, in the format 364 * described in RFC 4512 section 4.1.5. 365 * 366 * @return A string representation of this attribute syntax definition. 367 */ 368 @Override() 369 public String toString() 370 { 371 return attributeSyntaxString; 372 } 373}