001/* 002 * Copyright 2007-2020 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2007-2020 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) 2008-2020 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.StaticUtils; 049import com.unboundid.util.ThreadSafety; 050import com.unboundid.util.ThreadSafetyLevel; 051import com.unboundid.util.Validator; 052 053import static com.unboundid.ldap.sdk.schema.SchemaMessages.*; 054 055 056 057/** 058 * This class provides a data structure that describes an LDAP attribute syntax 059 * schema element. 060 */ 061@NotMutable() 062@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 063public final class AttributeSyntaxDefinition 064 extends SchemaElement 065{ 066 /** 067 * The serial version UID for this serializable class. 068 */ 069 private static final long serialVersionUID = 8593718232711987488L; 070 071 072 073 // The set of extensions for this attribute syntax. 074 private final Map<String,String[]> extensions; 075 076 // The description for this attribute syntax. 077 private final String description; 078 079 // The string representation of this attribute syntax. 080 private final String attributeSyntaxString; 081 082 // The OID for this attribute syntax. 083 private final String oid; 084 085 086 087 /** 088 * Creates a new attribute syntax from the provided string representation. 089 * 090 * @param s The string representation of the attribute syntax to create, 091 * using the syntax described in RFC 4512 section 4.1.5. It must 092 * not be {@code null}. 093 * 094 * @throws LDAPException If the provided string cannot be decoded as an 095 * attribute syntax definition. 096 */ 097 public AttributeSyntaxDefinition(final String s) 098 throws LDAPException 099 { 100 Validator.ensureNotNull(s); 101 102 attributeSyntaxString = s.trim(); 103 104 // The first character must be an opening parenthesis. 105 final int length = attributeSyntaxString.length(); 106 if (length == 0) 107 { 108 throw new LDAPException(ResultCode.DECODING_ERROR, 109 ERR_ATTRSYNTAX_DECODE_EMPTY.get()); 110 } 111 else if (attributeSyntaxString.charAt(0) != '(') 112 { 113 throw new LDAPException(ResultCode.DECODING_ERROR, 114 ERR_ATTRSYNTAX_DECODE_NO_OPENING_PAREN.get( 115 attributeSyntaxString)); 116 } 117 118 119 // Skip over any spaces until we reach the start of the OID, then read the 120 // OID until we find the next space. 121 int pos = skipSpaces(attributeSyntaxString, 1, length); 122 123 StringBuilder buffer = new StringBuilder(); 124 pos = readOID(attributeSyntaxString, pos, length, buffer); 125 oid = buffer.toString(); 126 127 128 // Technically, attribute syntax elements are supposed to appear in a 129 // specific order, but we'll be lenient and allow remaining elements to come 130 // in any order. 131 String descr = null; 132 final Map<String,String[]> exts = 133 new LinkedHashMap<>(StaticUtils.computeMapCapacity(5)); 134 135 while (true) 136 { 137 // Skip over any spaces until we find the next element. 138 pos = skipSpaces(attributeSyntaxString, pos, length); 139 140 // Read until we find the next space or the end of the string. Use that 141 // token to figure out what to do next. 142 final int tokenStartPos = pos; 143 while ((pos < length) && (attributeSyntaxString.charAt(pos) != ' ')) 144 { 145 pos++; 146 } 147 148 final String token = attributeSyntaxString.substring(tokenStartPos, pos); 149 final String lowerToken = StaticUtils.toLowerCase(token); 150 if (lowerToken.equals(")")) 151 { 152 // This indicates that we're at the end of the value. There should not 153 // be any more closing characters. 154 if (pos < length) 155 { 156 throw new LDAPException(ResultCode.DECODING_ERROR, 157 ERR_ATTRSYNTAX_DECODE_CLOSE_NOT_AT_END.get( 158 attributeSyntaxString)); 159 } 160 break; 161 } 162 else if (lowerToken.equals("desc")) 163 { 164 if (descr == null) 165 { 166 pos = skipSpaces(attributeSyntaxString, pos, length); 167 168 buffer = new StringBuilder(); 169 pos = readQDString(attributeSyntaxString, pos, length, buffer); 170 descr = buffer.toString(); 171 } 172 else 173 { 174 throw new LDAPException(ResultCode.DECODING_ERROR, 175 ERR_ATTRSYNTAX_DECODE_MULTIPLE_DESC.get( 176 attributeSyntaxString)); 177 } 178 } 179 else if (lowerToken.startsWith("x-")) 180 { 181 pos = skipSpaces(attributeSyntaxString, pos, length); 182 183 final ArrayList<String> valueList = new ArrayList<>(5); 184 pos = readQDStrings(attributeSyntaxString, pos, length, valueList); 185 186 final String[] values = new String[valueList.size()]; 187 valueList.toArray(values); 188 189 if (exts.containsKey(token)) 190 { 191 throw new LDAPException(ResultCode.DECODING_ERROR, 192 ERR_ATTRSYNTAX_DECODE_DUP_EXT.get( 193 attributeSyntaxString, token)); 194 } 195 196 exts.put(token, values); 197 } 198 else 199 { 200 throw new LDAPException(ResultCode.DECODING_ERROR, 201 ERR_ATTRSYNTAX_DECODE_UNEXPECTED_TOKEN.get( 202 attributeSyntaxString, token)); 203 } 204 } 205 206 description = descr; 207 extensions = Collections.unmodifiableMap(exts); 208 } 209 210 211 212 /** 213 * Creates a new attribute syntax use with the provided information. 214 * 215 * @param oid The OID for this attribute syntax. It must not be 216 * {@code null}. 217 * @param description The description for this attribute syntax. It may be 218 * {@code null} if there is no description. 219 * @param extensions The set of extensions for this attribute syntax. It 220 * may be {@code null} or empty if there should not be 221 * any extensions. 222 */ 223 public AttributeSyntaxDefinition(final String oid, final String description, 224 final Map<String,String[]> extensions) 225 { 226 Validator.ensureNotNull(oid); 227 228 this.oid = oid; 229 this.description = description; 230 231 if (extensions == null) 232 { 233 this.extensions = Collections.emptyMap(); 234 } 235 else 236 { 237 this.extensions = Collections.unmodifiableMap(extensions); 238 } 239 240 final StringBuilder buffer = new StringBuilder(); 241 createDefinitionString(buffer); 242 attributeSyntaxString = buffer.toString(); 243 } 244 245 246 247 /** 248 * Constructs a string representation of this attribute syntax definition in 249 * the provided buffer. 250 * 251 * @param buffer The buffer in which to construct a string representation of 252 * this attribute syntax definition. 253 */ 254 private void createDefinitionString(final StringBuilder buffer) 255 { 256 buffer.append("( "); 257 buffer.append(oid); 258 259 if (description != null) 260 { 261 buffer.append(" DESC '"); 262 encodeValue(description, buffer); 263 buffer.append('\''); 264 } 265 266 for (final Map.Entry<String,String[]> e : extensions.entrySet()) 267 { 268 final String name = e.getKey(); 269 final String[] values = e.getValue(); 270 if (values.length == 1) 271 { 272 buffer.append(' '); 273 buffer.append(name); 274 buffer.append(" '"); 275 encodeValue(values[0], buffer); 276 buffer.append('\''); 277 } 278 else 279 { 280 buffer.append(' '); 281 buffer.append(name); 282 buffer.append(" ("); 283 for (final String value : values) 284 { 285 buffer.append(" '"); 286 encodeValue(value, buffer); 287 buffer.append('\''); 288 } 289 buffer.append(" )"); 290 } 291 } 292 293 buffer.append(" )"); 294 } 295 296 297 298 /** 299 * Retrieves the OID for this attribute syntax. 300 * 301 * @return The OID for this attribute syntax. 302 */ 303 public String getOID() 304 { 305 return oid; 306 } 307 308 309 310 /** 311 * Retrieves the description for this attribute syntax, if available. 312 * 313 * @return The description for this attribute syntax, or {@code null} if 314 * there is no description defined. 315 */ 316 public String getDescription() 317 { 318 return description; 319 } 320 321 322 323 /** 324 * Retrieves the set of extensions for this matching rule use. They will be 325 * mapped from the extension name (which should start with "X-") to the set 326 * of values for that extension. 327 * 328 * @return The set of extensions for this matching rule use. 329 */ 330 public Map<String,String[]> getExtensions() 331 { 332 return extensions; 333 } 334 335 336 337 /** 338 * {@inheritDoc} 339 */ 340 @Override() 341 public int hashCode() 342 { 343 return oid.hashCode(); 344 } 345 346 347 348 /** 349 * {@inheritDoc} 350 */ 351 @Override() 352 public boolean equals(final Object o) 353 { 354 if (o == null) 355 { 356 return false; 357 } 358 359 if (o == this) 360 { 361 return true; 362 } 363 364 if (! (o instanceof AttributeSyntaxDefinition)) 365 { 366 return false; 367 } 368 369 final AttributeSyntaxDefinition d = (AttributeSyntaxDefinition) o; 370 return (oid.equals(d.oid) && 371 StaticUtils.bothNullOrEqualIgnoreCase(description, d.description) && 372 extensionsEqual(extensions, d.extensions)); 373 } 374 375 376 377 /** 378 * Retrieves a string representation of this attribute syntax, in the format 379 * described in RFC 4512 section 4.1.5. 380 * 381 * @return A string representation of this attribute syntax definition. 382 */ 383 @Override() 384 public String toString() 385 { 386 return attributeSyntaxString; 387 } 388}