001/* 002 * Copyright 2007-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2007-2024 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2007-2024 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.ldap.sdk.schema; 037 038 039 040import java.util.ArrayList; 041import java.util.Collections; 042import java.util.Map; 043import java.util.LinkedHashMap; 044 045import com.unboundid.ldap.sdk.LDAPException; 046import com.unboundid.ldap.sdk.ResultCode; 047import com.unboundid.util.NotMutable; 048import com.unboundid.util.NotNull; 049import com.unboundid.util.Nullable; 050import com.unboundid.util.StaticUtils; 051import com.unboundid.util.ThreadSafety; 052import com.unboundid.util.ThreadSafetyLevel; 053import com.unboundid.util.Validator; 054 055import static com.unboundid.ldap.sdk.schema.SchemaMessages.*; 056 057 058 059/** 060 * This class provides a data structure that describes an LDAP attribute syntax 061 * schema element. 062 */ 063@NotMutable() 064@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 065public final class AttributeSyntaxDefinition 066 extends SchemaElement 067{ 068 /** 069 * The serial version UID for this serializable class. 070 */ 071 private static final long serialVersionUID = 8593718232711987488L; 072 073 074 075 // The set of extensions for this attribute syntax. 076 @NotNull private final Map<String,String[]> extensions; 077 078 // The description for this attribute syntax. 079 @Nullable private final String description; 080 081 // The string representation of this attribute syntax. 082 @NotNull private final String attributeSyntaxString; 083 084 // The OID for this attribute syntax. 085 @NotNull private final String oid; 086 087 088 089 /** 090 * Creates a new attribute syntax from the provided string representation. 091 * 092 * @param s The string representation of the attribute syntax to create, 093 * using the syntax described in RFC 4512 section 4.1.5. It must 094 * not be {@code null}. 095 * 096 * @throws LDAPException If the provided string cannot be decoded as an 097 * attribute syntax definition. 098 */ 099 public AttributeSyntaxDefinition(@NotNull final String s) 100 throws LDAPException 101 { 102 Validator.ensureNotNull(s); 103 104 attributeSyntaxString = s.trim(); 105 106 // The first character must be an opening parenthesis. 107 final int length = attributeSyntaxString.length(); 108 if (length == 0) 109 { 110 throw new LDAPException(ResultCode.DECODING_ERROR, 111 ERR_ATTRSYNTAX_DECODE_EMPTY.get()); 112 } 113 else if (attributeSyntaxString.charAt(0) != '(') 114 { 115 throw new LDAPException(ResultCode.DECODING_ERROR, 116 ERR_ATTRSYNTAX_DECODE_NO_OPENING_PAREN.get( 117 attributeSyntaxString)); 118 } 119 120 121 // Skip over any spaces until we reach the start of the OID, then read the 122 // OID until we find the next space. 123 int pos = skipSpaces(attributeSyntaxString, 1, length); 124 125 StringBuilder buffer = new StringBuilder(); 126 pos = readOID(attributeSyntaxString, pos, length, buffer); 127 oid = buffer.toString(); 128 129 130 // Technically, attribute syntax elements are supposed to appear in a 131 // specific order, but we'll be lenient and allow remaining elements to come 132 // in any order. 133 String descr = null; 134 final Map<String,String[]> exts = 135 new LinkedHashMap<>(StaticUtils.computeMapCapacity(5)); 136 137 while (true) 138 { 139 // Skip over any spaces until we find the next element. 140 pos = skipSpaces(attributeSyntaxString, pos, length); 141 142 // Read until we find the next space or the end of the string. Use that 143 // token to figure out what to do next. 144 final int tokenStartPos = pos; 145 while ((pos < length) && (attributeSyntaxString.charAt(pos) != ' ')) 146 { 147 pos++; 148 } 149 150 final String token = attributeSyntaxString.substring(tokenStartPos, pos); 151 final String lowerToken = StaticUtils.toLowerCase(token); 152 if (lowerToken.equals(")")) 153 { 154 // This indicates that we're at the end of the value. There should not 155 // be any more closing characters. 156 if (pos < length) 157 { 158 throw new LDAPException(ResultCode.DECODING_ERROR, 159 ERR_ATTRSYNTAX_DECODE_CLOSE_NOT_AT_END.get( 160 attributeSyntaxString)); 161 } 162 break; 163 } 164 else if (lowerToken.equals("desc")) 165 { 166 if (descr == null) 167 { 168 pos = skipSpaces(attributeSyntaxString, pos, length); 169 170 buffer = new StringBuilder(); 171 pos = readQDString(attributeSyntaxString, pos, length, token, buffer); 172 descr = buffer.toString(); 173 } 174 else 175 { 176 throw new LDAPException(ResultCode.DECODING_ERROR, 177 ERR_ATTRSYNTAX_DECODE_MULTIPLE_DESC.get( 178 attributeSyntaxString)); 179 } 180 } 181 else if (lowerToken.startsWith("x-")) 182 { 183 pos = skipSpaces(attributeSyntaxString, pos, length); 184 185 final ArrayList<String> valueList = new ArrayList<>(5); 186 pos = readQDStrings(attributeSyntaxString, pos, length, token, 187 valueList); 188 189 final String[] values = new String[valueList.size()]; 190 valueList.toArray(values); 191 192 if (exts.containsKey(token)) 193 { 194 throw new LDAPException(ResultCode.DECODING_ERROR, 195 ERR_ATTRSYNTAX_DECODE_DUP_EXT.get( 196 attributeSyntaxString, token)); 197 } 198 199 exts.put(token, values); 200 } 201 else 202 { 203 throw new LDAPException(ResultCode.DECODING_ERROR, 204 ERR_ATTRSYNTAX_DECODE_UNEXPECTED_TOKEN.get( 205 attributeSyntaxString, token)); 206 } 207 } 208 209 description = descr; 210 extensions = Collections.unmodifiableMap(exts); 211 } 212 213 214 215 /** 216 * Creates a new attribute syntax use with the provided information. 217 * 218 * @param oid The OID for this attribute syntax. It must not be 219 * {@code null}. 220 * @param description The description for this attribute syntax. It may be 221 * {@code null} if there is no description. 222 * @param extensions The set of extensions for this attribute syntax. It 223 * may be {@code null} or empty if there should not be 224 * any extensions. 225 */ 226 public AttributeSyntaxDefinition(@NotNull final String oid, 227 @Nullable final String description, 228 @Nullable final Map<String,String[]> extensions) 229 { 230 Validator.ensureNotNull(oid); 231 232 this.oid = oid; 233 this.description = description; 234 235 if (extensions == null) 236 { 237 this.extensions = Collections.emptyMap(); 238 } 239 else 240 { 241 this.extensions = Collections.unmodifiableMap(extensions); 242 } 243 244 final StringBuilder buffer = new StringBuilder(); 245 createDefinitionString(buffer); 246 attributeSyntaxString = buffer.toString(); 247 } 248 249 250 251 /** 252 * Constructs a string representation of this attribute syntax definition in 253 * the provided buffer. 254 * 255 * @param buffer The buffer in which to construct a string representation of 256 * this attribute syntax definition. 257 */ 258 private void createDefinitionString(@NotNull final StringBuilder buffer) 259 { 260 buffer.append("( "); 261 buffer.append(oid); 262 263 if (description != null) 264 { 265 buffer.append(" DESC '"); 266 encodeValue(description, buffer); 267 buffer.append('\''); 268 } 269 270 for (final Map.Entry<String,String[]> e : extensions.entrySet()) 271 { 272 final String name = e.getKey(); 273 final String[] values = e.getValue(); 274 if (values.length == 1) 275 { 276 buffer.append(' '); 277 buffer.append(name); 278 buffer.append(" '"); 279 encodeValue(values[0], buffer); 280 buffer.append('\''); 281 } 282 else 283 { 284 buffer.append(' '); 285 buffer.append(name); 286 buffer.append(" ("); 287 for (final String value : values) 288 { 289 buffer.append(" '"); 290 encodeValue(value, buffer); 291 buffer.append('\''); 292 } 293 buffer.append(" )"); 294 } 295 } 296 297 buffer.append(" )"); 298 } 299 300 301 302 /** 303 * Retrieves the OID for this attribute syntax. 304 * 305 * @return The OID for this attribute syntax. 306 */ 307 @NotNull() 308 public String getOID() 309 { 310 return oid; 311 } 312 313 314 315 /** 316 * Retrieves the description for this attribute syntax, if available. 317 * 318 * @return The description for this attribute syntax, or {@code null} if 319 * there is no description defined. 320 */ 321 @Nullable() 322 public String getDescription() 323 { 324 return description; 325 } 326 327 328 329 /** 330 * Retrieves the set of extensions for this matching rule use. They will be 331 * mapped from the extension name (which should start with "X-") to the set 332 * of values for that extension. 333 * 334 * @return The set of extensions for this matching rule use. 335 */ 336 @NotNull() 337 public Map<String,String[]> getExtensions() 338 { 339 return extensions; 340 } 341 342 343 344 /** 345 * {@inheritDoc} 346 */ 347 @Override() 348 @NotNull() 349 public SchemaElementType getSchemaElementType() 350 { 351 return SchemaElementType.ATTRIBUTE_SYNTAX; 352 } 353 354 355 356 /** 357 * {@inheritDoc} 358 */ 359 @Override() 360 public int hashCode() 361 { 362 return oid.hashCode(); 363 } 364 365 366 367 /** 368 * {@inheritDoc} 369 */ 370 @Override() 371 public boolean equals(@Nullable final Object o) 372 { 373 if (o == null) 374 { 375 return false; 376 } 377 378 if (o == this) 379 { 380 return true; 381 } 382 383 if (! (o instanceof AttributeSyntaxDefinition)) 384 { 385 return false; 386 } 387 388 final AttributeSyntaxDefinition d = (AttributeSyntaxDefinition) o; 389 return (oid.equals(d.oid) && 390 StaticUtils.bothNullOrEqualIgnoreCase(description, d.description) && 391 extensionsEqual(extensions, d.extensions)); 392 } 393 394 395 396 /** 397 * Retrieves a string representation of this attribute syntax, in the format 398 * described in RFC 4512 section 4.1.5. 399 * 400 * @return A string representation of this attribute syntax definition. 401 */ 402 @Override() 403 @NotNull() 404 public String toString() 405 { 406 return attributeSyntaxString; 407 } 408}