001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2006-2009 Sun Microsystems, Inc.
025 *      Portions Copyright 2011-2015 ForgeRock AS
026 */
027package org.opends.server.schema;
028
029import static org.opends.messages.SchemaMessages.*;
030import static org.opends.server.config.ConfigConstants.*;
031import static org.opends.server.schema.SchemaConstants.*;
032import static org.opends.server.util.ServerConstants.*;
033import static org.opends.server.util.StaticUtils.*;
034
035import java.util.ArrayList;
036import java.util.HashMap;
037import java.util.LinkedHashMap;
038import java.util.LinkedList;
039import java.util.List;
040
041import org.forgerock.i18n.LocalizableMessage;
042import org.forgerock.opendj.config.server.ConfigChangeResult;
043import org.forgerock.opendj.config.server.ConfigException;
044import org.forgerock.opendj.ldap.ByteSequence;
045import org.forgerock.opendj.ldap.Option;
046import org.forgerock.opendj.ldap.ResultCode;
047import org.forgerock.opendj.ldap.schema.AttributeUsage;
048import org.forgerock.opendj.ldap.schema.MatchingRule;
049import org.forgerock.opendj.ldap.schema.SchemaOptions;
050import org.forgerock.opendj.ldap.schema.Syntax;
051import org.opends.server.admin.server.ConfigurationChangeListener;
052import org.opends.server.admin.std.server.AttributeTypeDescriptionAttributeSyntaxCfg;
053import org.opends.server.api.AttributeSyntax;
054import org.opends.server.core.DirectoryServer;
055import org.opends.server.core.ServerContext;
056import org.opends.server.types.AttributeType;
057import org.opends.server.types.CommonSchemaElements;
058import org.opends.server.types.DirectoryException;
059import org.opends.server.types.InitializationException;
060import org.opends.server.types.Schema;
061
062/**
063 * This class defines the attribute type description syntax, which is used to
064 * hold attribute type definitions in the server schema.  The format of this
065 * syntax is defined in RFC 2252.
066 */
067public class AttributeTypeSyntax
068       extends AttributeSyntax<AttributeTypeDescriptionAttributeSyntaxCfg>
069       implements
070       ConfigurationChangeListener<AttributeTypeDescriptionAttributeSyntaxCfg> {
071
072  /**
073   * The reference to the configuration for this attribute type description
074   * syntax.
075   */
076  private AttributeTypeDescriptionAttributeSyntaxCfg currentConfig;
077
078
079
080  /** If true strip the suggested minimum upper bound from the syntax OID. */
081  private static boolean stripMinimumUpperBound;
082
083  private ServerContext serverContext;
084
085
086  /**
087   * Creates a new instance of this syntax.  Note that the only thing that
088   * should be done here is to invoke the default constructor for the
089   * superclass.  All initialization should be performed in the
090   * <CODE>initializeSyntax</CODE> method.
091   */
092  public AttributeTypeSyntax()
093  {
094    super();
095  }
096
097
098
099  /** {@inheritDoc} */
100  @Override
101  public void
102  initializeSyntax(AttributeTypeDescriptionAttributeSyntaxCfg configuration, ServerContext serverContext)
103         throws ConfigException, InitializationException
104  {
105    this.serverContext = serverContext;
106
107    // This syntax is one of the Directory Server's core syntaxes and therefore
108    // it may be instantiated at times without a configuration entry.  If that
109    // is the case, then we'll exit now before doing anything that could require
110    // access to that entry.
111    if (configuration == null)
112    {
113      return;
114    }
115
116    currentConfig = configuration;
117    currentConfig.addAttributeTypeDescriptionChangeListener(this);
118    stripMinimumUpperBound=configuration.isStripSyntaxMinUpperBound();
119    updateNewSchema();
120  }
121
122  /** Update the option in new schema if it changes from current value. */
123  private void updateNewSchema()
124  {
125    Option<Boolean> option = SchemaOptions.STRIP_UPPER_BOUND_FOR_ATTRIBUTE_TYPE;
126    if (isStripSyntaxMinimumUpperBound() != serverContext.getSchemaNG().getOption(option))
127    {
128      SchemaUpdater schemaUpdater = serverContext.getSchemaUpdater();
129      schemaUpdater.updateSchema(
130          schemaUpdater.getSchemaBuilder().setOption(option, stripMinimumUpperBound).toSchema());
131    }
132  }
133
134  /** {@inheritDoc} */
135  @Override
136  public Syntax getSDKSyntax(org.forgerock.opendj.ldap.schema.Schema schema)
137  {
138    return schema.getSyntax(SchemaConstants.SYNTAX_ATTRIBUTE_TYPE_OID);
139  }
140
141  /** {@inheritDoc} */
142  @Override
143  public String getName()
144  {
145    return SYNTAX_ATTRIBUTE_TYPE_NAME;
146  }
147
148
149
150  /** {@inheritDoc} */
151  @Override
152  public String getOID()
153  {
154    return SYNTAX_ATTRIBUTE_TYPE_OID;
155  }
156
157
158
159  /** {@inheritDoc} */
160  @Override
161  public String getDescription()
162  {
163    return SYNTAX_ATTRIBUTE_TYPE_DESCRIPTION;
164  }
165
166
167
168  /**
169   * Decodes the contents of the provided ASN.1 octet string as an attribute
170   * type definition according to the rules of this syntax.  Note that the
171   * provided octet string value does not need to be normalized (and in fact, it
172   * should not be in order to allow the desired capitalization to be
173   * preserved).
174   *
175   * @param  value                 The ASN.1 octet string containing the value
176   *                               to decode (it does not need to be
177   *                               normalized).
178   * @param  schema                The schema to use to resolve references to
179   *                               other schema elements.
180   * @param  allowUnknownElements  Indicates whether to allow values that
181   *                               reference a superior attribute type which are
182   *                               not defined in the server schema. This should
183   *                               only be true when called by
184   *                               {@code valueIsAcceptable}.
185   *
186   * @return  The decoded attribute type definition.
187   *
188   * @throws  DirectoryException  If the provided value cannot be decoded as an
189   *                              attribute type definition.
190   */
191  public static AttributeType decodeAttributeType(ByteSequence value,
192                                                  Schema schema,
193                                                  boolean allowUnknownElements)
194         throws DirectoryException
195  {
196    // Get string representations of the provided value using the provided form
197    // and with all lowercase characters.
198    String valueStr = value.toString();
199    String lowerStr = toLowerCase(valueStr);
200
201
202    // We'll do this a character at a time.  First, skip over any leading
203    // whitespace.
204    int pos    = 0;
205    int length = valueStr.length();
206    while (pos < length && valueStr.charAt(pos) == ' ')
207    {
208      pos++;
209    }
210
211    if (pos >= length)
212    {
213      // This means that the value was empty or contained only whitespace.  That
214      // is illegal.
215      LocalizableMessage message = ERR_ATTR_SYNTAX_ATTRTYPE_EMPTY_VALUE.get();
216      throw new DirectoryException(
217              ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
218    }
219
220
221    // The next character must be an open parenthesis.  If it is not, then that
222    // is an error.
223    char c = valueStr.charAt(pos++);
224    if (c != '(')
225    {
226      LocalizableMessage message = ERR_ATTR_SYNTAX_ATTRTYPE_EXPECTED_OPEN_PARENTHESIS.get(valueStr, pos - 1, c);
227      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
228    }
229
230
231    // Skip over any spaces immediately following the opening parenthesis.
232    while (pos < length && ((c = valueStr.charAt(pos)) == ' '))
233    {
234      pos++;
235    }
236
237    if (pos >= length)
238    {
239      // This means that the end of the value was reached before we could find
240      // the OID.  Ths is illegal.
241      LocalizableMessage message = ERR_ATTR_SYNTAX_ATTRTYPE_TRUNCATED_VALUE.get(valueStr);
242      throw new DirectoryException(
243              ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
244    }
245
246
247    // The next set of characters must be the OID.  Strictly speaking, this
248    // should only be a numeric OID, but we'll also allow for the
249    // "attrname-oid" case as well.  Look at the first character to figure out
250    // which we will be using.
251    int oidStartPos = pos;
252    if (isDigit(c))
253    {
254      // This must be a numeric OID.  In that case, we will accept only digits
255      // and periods, but not consecutive periods.
256      boolean lastWasPeriod = false;
257      while (pos < length
258              && ((c = valueStr.charAt(pos)) != ' ')
259              && ((c = valueStr.charAt(pos)) != ')'))
260      {
261        if (c == '.')
262        {
263          if (lastWasPeriod)
264          {
265            LocalizableMessage message =
266              ERR_ATTR_SYNTAX_ATTRTYPE_DOUBLE_PERIOD_IN_NUMERIC_OID.
267                  get(valueStr, pos - 1);
268            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
269                                         message);
270          }
271          else
272          {
273            lastWasPeriod = true;
274          }
275        }
276        else if (! isDigit(c))
277        {
278          // This must have been an illegal character.
279          LocalizableMessage message =
280            ERR_ATTR_SYNTAX_ATTRTYPE_ILLEGAL_CHAR_IN_NUMERIC_OID.get(valueStr, c, pos - 1);
281          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
282        }
283        else
284        {
285          lastWasPeriod = false;
286        }
287        pos++;
288      }
289    }
290    else
291    {
292      // This must be a "fake" OID.  In this case, we will only accept
293      // alphabetic characters, numeric digits, and the hyphen.
294      while (pos < length
295          && ((c = valueStr.charAt(pos)) != ' ')
296          && ((c = valueStr.charAt(pos)) != ')'))
297      {
298        if (isAlpha(c)
299            || isDigit(c)
300            || c == '-'
301            || (c == '_' && DirectoryServer.allowAttributeNameExceptions()))
302        {
303          // This is fine.  It is an acceptable character.
304          pos++;
305        }
306        else
307        {
308          // This must have been an illegal character.
309          LocalizableMessage message = ERR_ATTR_SYNTAX_ATTRTYPE_ILLEGAL_CHAR_IN_STRING_OID.get(valueStr, c, pos - 1);
310          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
311        }
312      }
313    }
314
315
316    // If we're at the end of the value, then it isn't a valid attribute type
317    // description.  Otherwise, parse out the OID.
318    String oid;
319    if (pos >= length)
320    {
321      LocalizableMessage message = ERR_ATTR_SYNTAX_ATTRTYPE_TRUNCATED_VALUE.get(valueStr);
322      throw new DirectoryException(
323              ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
324    }
325    else
326    {
327      oid = lowerStr.substring(oidStartPos, pos);
328    }
329
330
331    // Skip over the space(s) after the OID.
332    while (pos < length && ((c = valueStr.charAt(pos)) == ' '))
333    {
334      pos++;
335    }
336
337    if (pos >= length)
338    {
339      // This means that the end of the value was reached before we could find
340      // the OID.  Ths is illegal.
341      LocalizableMessage message = ERR_ATTR_SYNTAX_ATTRTYPE_TRUNCATED_VALUE.get(valueStr);
342      throw new DirectoryException(
343              ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
344    }
345
346
347    // At this point, we should have a pretty specific syntax that describes
348    // what may come next, but some of the components are optional and it would
349    // be pretty easy to put something in the wrong order, so we will be very
350    // flexible about what we can accept.  Just look at the next token, figure
351    // out what it is and how to treat what comes after it, then repeat until
352    // we get to the end of the value.  But before we start, set default values
353    // for everything else we might need to know.
354    String  primaryName = oid;
355    List<String> typeNames = new LinkedList<>();
356    String description = null;
357    AttributeType superiorType = null;
358    Syntax syntax = DirectoryServer.getDefaultAttributeSyntax();
359    MatchingRule approximateMatchingRule = null;
360    MatchingRule equalityMatchingRule = null;
361    MatchingRule orderingMatchingRule = null;
362    MatchingRule substringMatchingRule = null;
363    AttributeUsage attributeUsage = AttributeUsage.USER_APPLICATIONS;
364    boolean isCollective = false;
365    boolean isNoUserModification = false;
366    boolean isObsolete = false;
367    boolean isSingleValue = false;
368    HashMap<String,List<String>> extraProperties = new LinkedHashMap<>();
369
370
371    while (true)
372    {
373      StringBuilder tokenNameBuffer = new StringBuilder();
374      pos = readTokenName(valueStr, tokenNameBuffer, pos);
375      String tokenName = tokenNameBuffer.toString();
376      String lowerTokenName = toLowerCase(tokenName);
377      if (")".equals(tokenName))
378      {
379        // We must be at the end of the value.  If not, then that's a problem.
380        if (pos < length)
381        {
382          LocalizableMessage message =
383            ERR_ATTR_SYNTAX_ATTRTYPE_UNEXPECTED_CLOSE_PARENTHESIS.
384                get(valueStr, pos - 1);
385          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
386                                       message);
387        }
388
389        break;
390      }
391      else if ("name".equals(lowerTokenName))
392      {
393        // This specifies the set of names for the attribute type.  It may be a
394        // single name in single quotes, or it may be an open parenthesis
395        // followed by one or more names in single quotes separated by spaces.
396        c = valueStr.charAt(pos++);
397        if (c == '\'')
398        {
399          StringBuilder userBuffer  = new StringBuilder();
400          StringBuilder lowerBuffer = new StringBuilder();
401          pos = readQuotedString(valueStr, lowerStr, userBuffer, lowerBuffer, pos - 1);
402          primaryName = userBuffer.toString();
403          typeNames.add(primaryName);
404        }
405        else if (c == '(')
406        {
407          StringBuilder userBuffer  = new StringBuilder();
408          StringBuilder lowerBuffer = new StringBuilder();
409          pos = readQuotedString(valueStr, lowerStr, userBuffer, lowerBuffer,
410                                 pos);
411          primaryName = userBuffer.toString();
412          typeNames.add(primaryName);
413
414
415          while (true)
416          {
417            if (valueStr.charAt(pos) == ')')
418            {
419              // Skip over any spaces after the parenthesis.
420              pos++;
421              while (pos < length && ((c = valueStr.charAt(pos)) == ' '))
422              {
423                pos++;
424              }
425
426              break;
427            }
428            userBuffer  = new StringBuilder();
429            lowerBuffer = new StringBuilder();
430
431            pos = readQuotedString(valueStr, lowerStr, userBuffer, lowerBuffer, pos);
432            typeNames.add(userBuffer.toString());
433          }
434        }
435        else
436        {
437          // This is an illegal character.
438          LocalizableMessage message = ERR_ATTR_SYNTAX_ATTRTYPE_ILLEGAL_CHAR.get(valueStr, c, pos - 1);
439          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
440        }
441        //RFC 2251: A specification may also assign one or more textual names
442        //for an attribute type.  These names MUST begin with a letter, and
443        //only contain ASCII letters, digit characters and hyphens.
444
445        //The global config hasn't been read so far. Allow the name exceptions
446        //during startup.
447        boolean allowExceptions = DirectoryServer.isRunning()?
448                           DirectoryServer.allowAttributeNameExceptions():true;
449        //Iterate over all the names and throw an exception if it is invalid.
450        for(String name : typeNames)
451        {
452          for(int index=0; index < name.length(); index++)
453          {
454            char ch = name.charAt(index);
455            switch(ch)
456            {
457              case '-':
458              //hyphen is allowed but not as the first byte.
459                if (index==0)
460                {
461                  throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
462                      ERR_ATTR_SYNTAX_ATTR_ILLEGAL_INITIAL_DASH.get(value));
463                }
464                break;
465              case '_':
466              // This will never be allowed as the first character.  It
467              // may be allowed for subsequent characters if the attribute
468              // name exceptions option is enabled.
469                if (index==0)
470                {
471                  throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
472                      ERR_ATTR_SYNTAX_ATTR_ILLEGAL_INITIAL_UNDERSCORE.get(
473                          value, ATTR_ALLOW_ATTRIBUTE_NAME_EXCEPTIONS));
474                }
475                else if (!allowExceptions)
476                {
477                  throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
478                      ERR_ATTR_SYNTAX_ATTR_ILLEGAL_UNDERSCORE_CHAR.get(
479                          value, ATTR_ALLOW_ATTRIBUTE_NAME_EXCEPTIONS));
480                }
481                break;
482
483              default:
484              //Only digits and ascii letters are allowed but the first byte
485              //can not be a digit.
486                if(index ==0 && isDigit(ch) && !allowExceptions)
487                {
488                  throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
489                      ERR_ATTR_SYNTAX_ATTR_ILLEGAL_INITIAL_DIGIT.get(
490                          value, ch, ATTR_ALLOW_ATTRIBUTE_NAME_EXCEPTIONS));
491                }
492                else if (!(('0'<=ch && ch<='9')
493                    || ('A'<=ch && ch<='Z')
494                    || ('a'<=ch && ch<='z')))
495                {
496                  throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
497                      ERR_ATTR_SYNTAX_ATTR_ILLEGAL_CHAR.get(value, ch, index));
498                }
499                break;
500            }
501          }
502
503        }
504
505      }
506      else if ("desc".equals(lowerTokenName))
507      {
508        // This specifies the description for the attribute type.  It is an
509        // arbitrary string of characters enclosed in single quotes.
510        StringBuilder descriptionBuffer = new StringBuilder();
511        pos = readQuotedString(valueStr, descriptionBuffer, pos);
512        description = descriptionBuffer.toString();
513      }
514      else if ("obsolete".equals(lowerTokenName))
515      {
516        // This indicates whether the attribute type should be considered
517        // obsolete.  We do not need to do any more parsing for this token.
518        isObsolete = true;
519      }
520      else if ("sup".equals(lowerTokenName))
521      {
522        // This specifies the name or OID of the superior attribute type from
523        // which this attribute type should inherit its properties.
524        StringBuilder woidBuffer = new StringBuilder();
525        pos = readWOID(lowerStr, woidBuffer, pos);
526        String woidString = woidBuffer.toString();
527        superiorType = schema.getAttributeType(woidString);
528        if (superiorType == null)
529        {
530          if (allowUnknownElements)
531          {
532            superiorType = DirectoryServer.getDefaultAttributeType(woidString);
533          }
534          else
535          {
536            // This is bad because we don't know what the superior attribute
537            // type is so we can't base this attribute type on it.
538            LocalizableMessage message = WARN_ATTR_SYNTAX_ATTRTYPE_UNKNOWN_SUPERIOR_TYPE.get(oid, woidString);
539            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
540          }
541        }
542
543
544        // Use the information in the superior type to provide defaults for the
545        // rest of the components in this attribute type description.
546        // Technically, the definition of the superior type should be provided
547        // before the matching rule, syntax, single-value, collective,
548        // no-user-modification, and usage components, and in that case we won't
549        // undo something else that has already been set by an earlier
550        // definition.  However, if the information is provided out-of-order,
551        // then it is possible that this could overwrite some desired setting
552        // that is different from that of the supertype.
553        approximateMatchingRule = superiorType.getApproximateMatchingRule();
554        equalityMatchingRule    = superiorType.getEqualityMatchingRule();
555        orderingMatchingRule    = superiorType.getOrderingMatchingRule();
556        substringMatchingRule   = superiorType.getSubstringMatchingRule();
557        syntax                  = superiorType.getSyntax();
558        isSingleValue           = superiorType.isSingleValue();
559        isCollective            = superiorType.isCollective();
560        isNoUserModification    = superiorType.isNoUserModification();
561        attributeUsage          = superiorType.getUsage();
562      }
563      else if ("equality".equals(lowerTokenName))
564      {
565        // This specifies the name or OID of the equality matching rule to use
566        // for this attribute type.
567        StringBuilder woidBuffer = new StringBuilder();
568        pos = readWOID(lowerStr, woidBuffer, pos);
569        MatchingRule emr =
570             schema.getMatchingRule(woidBuffer.toString());
571        if (emr == null)
572        {
573          // This is bad because we have no idea what the equality matching
574          // rule should be.
575          LocalizableMessage message = WARN_ATTR_SYNTAX_ATTRTYPE_UNKNOWN_EQUALITY_MR.get(oid, woidBuffer);
576          throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
577        }
578        else
579        {
580          equalityMatchingRule = emr;
581        }
582      }
583      else if ("ordering".equals(lowerTokenName))
584      {
585        // This specifies the name or OID of the ordering matching rule to use
586        // for this attribute type.
587        StringBuilder woidBuffer = new StringBuilder();
588        pos = readWOID(lowerStr, woidBuffer, pos);
589        MatchingRule omr = schema.getMatchingRule(woidBuffer.toString());
590        if (omr == null)
591        {
592          // This is bad because we have no idea what the ordering matching
593          // rule should be.
594          LocalizableMessage message = WARN_ATTR_SYNTAX_ATTRTYPE_UNKNOWN_ORDERING_MR.get(oid, woidBuffer);
595          throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
596        }
597        else
598        {
599          orderingMatchingRule = omr;
600        }
601      }
602      else if ("substr".equals(lowerTokenName))
603      {
604        // This specifies the name or OID of the substring matching rule to use
605        // for this attribute type.
606        StringBuilder woidBuffer = new StringBuilder();
607        pos = readWOID(lowerStr, woidBuffer, pos);
608        MatchingRule smr = schema.getMatchingRule(woidBuffer.toString());
609        if (smr == null)
610        {
611          // This is bad because we have no idea what the substring matching
612          // rule should be.
613          LocalizableMessage message = WARN_ATTR_SYNTAX_ATTRTYPE_UNKNOWN_SUBSTRING_MR.get(oid, woidBuffer);
614          throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
615        }
616        else
617        {
618          substringMatchingRule = smr;
619        }
620      }
621      else if ("syntax".equals(lowerTokenName))
622      {
623        // This specifies the numeric OID of the syntax for this matching rule.
624        // It may optionally be immediately followed by an open curly brace, an
625        // integer value, and a close curly brace to suggest the minimum number
626        // of characters that should be allowed in values of that type.  This
627        // implementation will ignore any such length because it does not
628        // impose any practical limit on the length of attribute values.
629        boolean inBrace         = false;
630        boolean lastWasPeriod   = false;
631        StringBuilder oidBuffer = new StringBuilder();
632        while (pos < length)
633        {
634          c = lowerStr.charAt(pos++);
635          if (inBrace)
636          {
637            // The only thing we'll allow here will be numeric digits and the
638            // closing curly brace.
639            if (c == '}')
640            {
641              // The next character must be a space or a closing parenthesis.
642              c = lowerStr.charAt(pos);
643              if (c != ' ' && c != ')')
644              {
645                LocalizableMessage message =
646                  ERR_ATTR_SYNTAX_ATTRTYPE_ILLEGAL_CHAR_IN_NUMERIC_OID.get(valueStr, c, pos - 1);
647                throw new DirectoryException(
648                               ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
649              }
650
651              break;
652            }
653            else if (! isDigit(c))
654            {
655              LocalizableMessage message =
656                ERR_ATTR_SYNTAX_ATTRTYPE_ILLEGAL_CHAR_IN_NUMERIC_OID.get(valueStr, c, pos - 1);
657              throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
658                                           message);
659            }
660          }
661          else
662          {
663            if (isDigit(c))
664            {
665              oidBuffer.append(c);
666              lastWasPeriod = false;
667            }
668            else if (c == '.')
669            {
670              if (lastWasPeriod)
671              {
672                LocalizableMessage message =
673                    ERR_ATTR_SYNTAX_ATTRTYPE_DOUBLE_PERIOD_IN_NUMERIC_OID.
674                      get(valueStr, pos - 1);
675                throw new DirectoryException(
676                               ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
677              }
678              else
679              {
680                oidBuffer.append(c);
681                lastWasPeriod = true;
682              }
683            }
684            else if (c == '{')
685            {
686              // It's the start of the length specification.
687              inBrace = true;
688            }
689            else if (c == ' ')
690            {
691              // It's the end of the value.
692              break;
693            }
694            else if(c == ')')
695            {
696              // As per RFC 4512 (4.1.2) it is end of the value.
697              --pos;
698              break;
699            }
700            else
701            {
702              LocalizableMessage message =
703                ERR_ATTR_SYNTAX_ATTRTYPE_ILLEGAL_CHAR_IN_NUMERIC_OID.get(valueStr, c, pos - 1);
704              throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
705                                           message);
706            }
707          }
708        }
709
710        syntax = schema.getSyntax(oidBuffer.toString());
711        if (syntax == null)
712        {
713          LocalizableMessage message = WARN_ATTR_SYNTAX_ATTRTYPE_UNKNOWN_SYNTAX.get(oid, oidBuffer);
714          throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
715                                       message);
716        }
717
718        if (approximateMatchingRule == null)
719        {
720          approximateMatchingRule = syntax.getApproximateMatchingRule();
721        }
722
723        if (equalityMatchingRule == null)
724        {
725          equalityMatchingRule = syntax.getEqualityMatchingRule();
726        }
727
728        if (orderingMatchingRule == null)
729        {
730          orderingMatchingRule = syntax.getOrderingMatchingRule();
731        }
732
733        if (substringMatchingRule == null)
734        {
735          substringMatchingRule = syntax.getSubstringMatchingRule();
736        }
737      }
738      else if ("single-value".equals(lowerTokenName))
739      {
740        // This indicates that attributes of this type are allowed to have at
741        // most one value.  We do not need any more parsing for this token.
742        isSingleValue = true;
743      }
744      else if ("collective".equals(lowerTokenName))
745      {
746        // This indicates that attributes of this type are collective (i.e.,
747        // have their values generated dynamically in some way).  We do not need
748        // any more parsing for this token.
749        isCollective = true;
750      }
751      else if ("no-user-modification".equals(lowerTokenName))
752      {
753        // This indicates that the values of attributes of this type are not to
754        // be modified by end users.  We do not need any more parsing for this
755        // token.
756        isNoUserModification = true;
757      }
758      else if ("usage".equals(lowerTokenName))
759      {
760        // This specifies the usage string for this attribute type.  It should
761        // be followed by one of the strings "userApplications",
762        // "directoryOperation", "distributedOperation", or "dSAOperation".
763        StringBuilder usageBuffer = new StringBuilder();
764        while (pos < length)
765        {
766          c = lowerStr.charAt(pos++);
767          if (c == ' ')
768          {
769            break;
770          }
771          else if(c == ')')
772          {
773            pos--;
774            break;
775          }
776          else
777          {
778            usageBuffer.append(c);
779          }
780        }
781
782        String usageStr = usageBuffer.toString();
783        if ("userapplications".equals(usageStr))
784        {
785          attributeUsage = AttributeUsage.USER_APPLICATIONS;
786        }
787        else if ("directoryoperation".equals(usageStr))
788        {
789          attributeUsage = AttributeUsage.DIRECTORY_OPERATION;
790        }
791        else if ("distributedoperation".equals(usageStr))
792        {
793          attributeUsage = AttributeUsage.DISTRIBUTED_OPERATION;
794        }
795        else if ("dsaoperation".equals(usageStr))
796        {
797          attributeUsage = AttributeUsage.DSA_OPERATION;
798        }
799        else
800        {
801          // This must be an illegal usage.
802          attributeUsage = AttributeUsage.USER_APPLICATIONS;
803
804          LocalizableMessage message = WARN_ATTR_SYNTAX_ATTRTYPE_INVALID_ATTRIBUTE_USAGE.get(oid, usageStr);
805          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
806        }
807      }
808      else
809      {
810        // This must be a non-standard property and it must be followed by
811        // either a single value in single quotes or an open parenthesis
812        // followed by one or more values in single quotes separated by spaces
813        // followed by a close parenthesis.
814        List<String> valueList = new ArrayList<>();
815        pos = readExtraParameterValues(valueStr, valueList, pos);
816        extraProperties.put(tokenName, valueList);
817      }
818    }
819
820    List<String> approxRules = extraProperties.get(SCHEMA_PROPERTY_APPROX_RULE);
821    if (approxRules != null && !approxRules.isEmpty())
822    {
823      String ruleName  = approxRules.get(0);
824      String lowerName = toLowerCase(ruleName);
825      MatchingRule amr = schema.getMatchingRule(lowerName);
826      if (amr == null)
827      {
828        // This is bad because we have no idea what the approximate matching
829        // rule should be.
830        LocalizableMessage message = WARN_ATTR_SYNTAX_ATTRTYPE_UNKNOWN_APPROXIMATE_MR.get(oid, ruleName);
831        throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
832      }
833      else
834      {
835        approximateMatchingRule = amr;
836      }
837    }
838
839
840    // If there is a superior type, then it must have the same usage as the
841    // subordinate type.  Also, if the superior type is collective, then so must
842    // the subordinate type be collective.
843    if (superiorType != null)
844    {
845      if (superiorType.getUsage() != attributeUsage)
846      {
847        LocalizableMessage message = WARN_ATTR_SYNTAX_ATTRTYPE_INVALID_SUPERIOR_USAGE.get(
848            oid, attributeUsage, superiorType.getNameOrOID());
849        throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
850      }
851
852      if (superiorType.isCollective() && !isCollective)
853      {
854        throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
855            WARN_ATTR_SYNTAX_ATTRTYPE_NONCOLLECTIVE_FROM_COLLECTIVE
856                .get(oid, superiorType.getNameOrOID()));
857      }
858    }
859
860
861    // If the attribute type is NO-USER-MODIFICATION, then it must not have a
862    // usage of userApplications.
863    if (isNoUserModification
864        && attributeUsage == AttributeUsage.USER_APPLICATIONS)
865    {
866      LocalizableMessage message =
867          WARN_ATTR_SYNTAX_ATTRTYPE_NO_USER_MOD_NOT_OPERATIONAL.get(oid);
868      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
869    }
870
871    CommonSchemaElements.checkSafeProperties(extraProperties);
872
873    return new AttributeType(value.toString(), primaryName, typeNames, oid,
874                             description, superiorType, syntax,
875                             approximateMatchingRule, equalityMatchingRule,
876                             orderingMatchingRule, substringMatchingRule,
877                             attributeUsage, isCollective, isNoUserModification,
878                             isObsolete, isSingleValue, extraProperties);
879  }
880
881
882
883  /**
884   * Reads the next token name from the attribute type definition, skipping over
885   * any leading or trailing spaces, and appends it to the provided buffer.
886   *
887   * @param  valueStr   The string representation of the attribute type
888   *                    definition.
889   * @param  tokenName  The buffer into which the token name will be written.
890   * @param  startPos   The position in the provided string at which to start
891   *                    reading the token name.
892   *
893   * @return  The position of the first character that is not part of the token
894   *          name or one of the trailing spaces after it.
895   *
896   * @throws  DirectoryException  If a problem is encountered while reading the
897   *                              token name.
898   */
899  private static int readTokenName(String valueStr, StringBuilder tokenName,
900                                   int startPos)
901          throws DirectoryException
902  {
903    // Skip over any spaces at the beginning of the value.
904    char c = '\u0000';
905    int  length = valueStr.length();
906    while (startPos < length && ((c = valueStr.charAt(startPos)) == ' '))
907    {
908      startPos++;
909    }
910
911    if (startPos >= length)
912    {
913      LocalizableMessage message = ERR_ATTR_SYNTAX_ATTRTYPE_TRUNCATED_VALUE.get(valueStr);
914      throw new DirectoryException(
915              ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
916    }
917
918
919    // Read until we find the next space.
920    while (startPos < length
921        && ((c = valueStr.charAt(startPos)) != ' ')
922        && ((c = valueStr.charAt(startPos)) != ')'))
923    {
924      tokenName.append(c);
925      startPos++;
926    }
927
928    //We may be left with only ')' which is not part of the token yet.
929    //Let us see if it is the case.
930    if(tokenName.length()==0 && c == ')')
931    {
932      tokenName.append(c);
933      startPos++;
934    }
935
936    // Skip over any trailing spaces after the value.
937    while (startPos < length && ((c = valueStr.charAt(startPos)) == ' '))
938    {
939      startPos++;
940    }
941
942
943    // Return the position of the first non-space character after the token.
944    return startPos;
945  }
946
947
948
949  /**
950   * Reads the value of a string enclosed in single quotes, skipping over the
951   * quotes and any leading or trailing spaces, and appending the string to the
952   * provided buffer.
953   *
954   * @param  valueStr     The user-provided representation of the attribute type
955   *                      definition.
956   * @param  valueBuffer  The buffer into which the user-provided representation
957   *                      of the value will be placed.
958   * @param  startPos     The position in the provided string at which to start
959   *                      reading the quoted string.
960   *
961   * @return  The position of the first character that is not part of the quoted
962   *          string or one of the trailing spaces after it.
963   *
964   * @throws  DirectoryException  If a problem is encountered while reading the
965   *                              quoted string.
966   */
967  private static int readQuotedString(String valueStr,
968                                      StringBuilder valueBuffer, int startPos)
969          throws DirectoryException
970  {
971    // Skip over any spaces at the beginning of the value.
972    char c = '\u0000';
973    int  length = valueStr.length();
974    while (startPos < length && ((c = valueStr.charAt(startPos)) == ' '))
975    {
976      startPos++;
977    }
978
979    if (startPos >= length)
980    {
981      LocalizableMessage message = ERR_ATTR_SYNTAX_ATTRTYPE_TRUNCATED_VALUE.get(valueStr);
982      throw new DirectoryException(
983              ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
984    }
985
986
987    // The next character must be a single quote.
988    if (c != '\'')
989    {
990      LocalizableMessage message = WARN_ATTR_SYNTAX_ATTRTYPE_EXPECTED_QUOTE_AT_POS.get(valueStr, startPos, c);
991      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
992    }
993
994
995    // Read until we find the closing quote.
996    startPos++;
997    while (startPos < length && ((c = valueStr.charAt(startPos)) != '\''))
998    {
999      valueBuffer.append(c);
1000      startPos++;
1001    }
1002
1003
1004    // Skip over any trailing spaces after the value.
1005    startPos++;
1006    while (startPos < length && ((c = valueStr.charAt(startPos)) == ' '))
1007    {
1008      startPos++;
1009    }
1010
1011
1012    // If we're at the end of the value, then that's illegal.
1013    if (startPos >= length)
1014    {
1015      LocalizableMessage message = ERR_ATTR_SYNTAX_ATTRTYPE_TRUNCATED_VALUE.get(valueStr);
1016      throw new DirectoryException(
1017              ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
1018    }
1019
1020
1021    // Return the position of the first non-space character after the token.
1022    return startPos;
1023  }
1024
1025
1026
1027  /**
1028   * Reads the value of a string enclosed in single quotes, skipping over the
1029   * quotes and any leading or trailing spaces, and appending the string to the
1030   * provided buffer.
1031   *
1032   * @param  valueStr     The user-provided representation of the attribute type
1033   *                      definition.
1034   * @param  lowerStr     The all-lowercase representation of the attribute type
1035   *                      definition.
1036   * @param  userBuffer   The buffer into which the user-provided representation
1037   *                      of the value will be placed.
1038   * @param  lowerBuffer  The buffer into which the all-lowercase representation
1039   *                      of the value will be placed.
1040   * @param  startPos     The position in the provided string at which to start
1041   *                      reading the quoted string.
1042   *
1043   * @return  The position of the first character that is not part of the quoted
1044   *          string or one of the trailing spaces after it.
1045   *
1046   * @throws  DirectoryException  If a problem is encountered while reading the
1047   *                              quoted string.
1048   */
1049  private static int readQuotedString(String valueStr, String lowerStr,
1050                                      StringBuilder userBuffer,
1051                                      StringBuilder lowerBuffer, int startPos)
1052          throws DirectoryException
1053  {
1054    // Skip over any spaces at the beginning of the value.
1055    char c = '\u0000';
1056    int  length = lowerStr.length();
1057    while (startPos < length && ((c = lowerStr.charAt(startPos)) == ' '))
1058    {
1059      startPos++;
1060    }
1061
1062    if (startPos >= length)
1063    {
1064      LocalizableMessage message = ERR_ATTR_SYNTAX_ATTRTYPE_TRUNCATED_VALUE.get(lowerStr);
1065      throw new DirectoryException(
1066              ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
1067    }
1068
1069
1070    // The next character must be a single quote.
1071    if (c != '\'')
1072    {
1073      LocalizableMessage message = WARN_ATTR_SYNTAX_ATTRTYPE_EXPECTED_QUOTE_AT_POS.get(valueStr, startPos, c);
1074      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
1075    }
1076
1077
1078    // Read until we find the closing quote.
1079    startPos++;
1080    while (startPos < length && ((c = lowerStr.charAt(startPos)) != '\''))
1081    {
1082      lowerBuffer.append(c);
1083      userBuffer.append(valueStr.charAt(startPos));
1084      startPos++;
1085    }
1086
1087
1088    // Skip over any trailing spaces after the value.
1089    startPos++;
1090    while (startPos < length && ((c = lowerStr.charAt(startPos)) == ' '))
1091    {
1092      startPos++;
1093    }
1094
1095
1096    // If we're at the end of the value, then that's illegal.
1097    if (startPos >= length)
1098    {
1099      LocalizableMessage message = ERR_ATTR_SYNTAX_ATTRTYPE_TRUNCATED_VALUE.get(lowerStr);
1100      throw new DirectoryException(
1101              ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
1102    }
1103
1104
1105    // Return the position of the first non-space character after the token.
1106    return startPos;
1107  }
1108
1109
1110
1111  /**
1112   * Reads the attribute description or numeric OID from the provided string,
1113   * skipping over any leading or trailing spaces, and appending the value to
1114   * the provided buffer.
1115   *
1116   * @param  lowerStr    The string from which the name or OID is to be read.
1117   * @param  woidBuffer  The buffer into which the name or OID should be
1118   *                     appended.
1119   * @param  startPos    The position at which to start reading.
1120   *
1121   * @return  The position of the first character after the name or OID that is
1122   *          not a space.
1123   *
1124   * @throws  DirectoryException  If a problem is encountered while reading the
1125   *                              name or OID.
1126   */
1127  private static int readWOID(String lowerStr, StringBuilder woidBuffer,
1128                              int startPos)
1129          throws DirectoryException
1130  {
1131    // Skip over any spaces at the beginning of the value.
1132    char c = '\u0000';
1133    int  length = lowerStr.length();
1134    while (startPos < length && ((c = lowerStr.charAt(startPos)) == ' '))
1135    {
1136      startPos++;
1137    }
1138
1139    if (startPos >= length)
1140    {
1141      LocalizableMessage message = ERR_ATTR_SYNTAX_ATTRTYPE_TRUNCATED_VALUE.get(lowerStr);
1142      throw new DirectoryException(
1143              ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
1144    }
1145
1146
1147    // The next character must be either numeric (for an OID) or alphabetic (for
1148    // an attribute description).
1149    if (isDigit(c))
1150    {
1151      // This must be a numeric OID.  In that case, we will accept only digits
1152      // and periods, but not consecutive periods.
1153      boolean lastWasPeriod = false;
1154      while (startPos < length && ((c = lowerStr.charAt(startPos++)) != ' '))
1155      {
1156        if (c == '.')
1157        {
1158          if (lastWasPeriod)
1159          {
1160            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
1161                ERR_ATTR_SYNTAX_ATTRTYPE_DOUBLE_PERIOD_IN_NUMERIC_OID
1162                    .get(lowerStr, startPos - 1));
1163          }
1164          else
1165          {
1166            woidBuffer.append(c);
1167            lastWasPeriod = true;
1168          }
1169        }
1170        else if (! isDigit(c))
1171        {
1172          // Technically, this must be an illegal character.  However, it is
1173          // possible that someone just got sloppy and did not include a space
1174          // between the name/OID and a closing parenthesis.  In that case,
1175          // we'll assume it's the end of the value.  What's more, we'll have
1176          // to prematurely return to nasty side effects from stripping off
1177          // additional characters.
1178          if (c == ')')
1179          {
1180            return startPos - 1;
1181          }
1182
1183          // This must have been an illegal character.
1184          LocalizableMessage message =
1185              ERR_ATTR_SYNTAX_ATTRTYPE_ILLEGAL_CHAR_IN_NUMERIC_OID.get(lowerStr, c, startPos-1);
1186          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
1187        }
1188        else
1189        {
1190          woidBuffer.append(c);
1191          lastWasPeriod = false;
1192        }
1193      }
1194    }
1195    else if (isAlpha(c))
1196    {
1197      // This must be an attribute description.  In this case, we will only
1198      // accept alphabetic characters, numeric digits, and the hyphen.
1199      while (startPos < length && ((c = lowerStr.charAt(startPos++)) != ' '))
1200      {
1201        if (isAlpha(c)
1202            || isDigit(c)
1203            || c == '-'
1204            || (c == '_' && DirectoryServer.allowAttributeNameExceptions()))
1205        {
1206          woidBuffer.append(c);
1207        }
1208        else
1209        {
1210          // Technically, this must be an illegal character.  However, it is
1211          // possible that someone just got sloppy and did not include a space
1212          // between the name/OID and a closing parenthesis.  In that case,
1213          // we'll assume it's the end of the value.  What's more, we'll have
1214          // to prematurely return to nasty side effects from stripping off
1215          // additional characters.
1216          if (c == ')')
1217          {
1218            return startPos - 1;
1219          }
1220
1221          // This must have been an illegal character.
1222          LocalizableMessage message = ERR_ATTR_SYNTAX_ATTRTYPE_ILLEGAL_CHAR_IN_STRING_OID.get(
1223              lowerStr, c, startPos - 1);
1224          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
1225        }
1226      }
1227    }
1228    else
1229    {
1230      LocalizableMessage message = ERR_ATTR_SYNTAX_ATTRTYPE_ILLEGAL_CHAR.get(lowerStr, c, startPos);
1231      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
1232    }
1233
1234
1235    // Skip over any trailing spaces after the value.
1236    while (startPos < length && ((c = lowerStr.charAt(startPos)) == ' '))
1237    {
1238      startPos++;
1239    }
1240
1241
1242    // If we're at the end of the value, then that's illegal.
1243    if (startPos >= length)
1244    {
1245      LocalizableMessage message = ERR_ATTR_SYNTAX_ATTRTYPE_TRUNCATED_VALUE.get(lowerStr);
1246      throw new DirectoryException(
1247              ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
1248    }
1249
1250
1251    // Return the position of the first non-space character after the token.
1252    return startPos;
1253  }
1254
1255
1256
1257  /**
1258   * Reads the value for an "extra" parameter.  It will handle a single unquoted
1259   * word (which is technically illegal, but we'll allow it), a single quoted
1260   * string, or an open parenthesis followed by a space-delimited set of quoted
1261   * strings or unquoted words followed by a close parenthesis.
1262   *
1263   * @param  valueStr   The string containing the information to be read.
1264   * @param  valueList  The list of "extra" parameter values read so far.
1265   * @param  startPos   The position in the value string at which to start
1266   *                    reading.
1267   *
1268   * @return  The "extra" parameter value that was read.
1269   *
1270   * @throws  DirectoryException  If a problem occurs while attempting to read
1271   *                              the value.
1272   */
1273  private static int readExtraParameterValues(String valueStr,
1274                          List<String> valueList, int startPos)
1275          throws DirectoryException
1276  {
1277    // Skip over any leading spaces.
1278    int length = valueStr.length();
1279    char c = '\u0000';
1280    while (startPos < length && ((c = valueStr.charAt(startPos)) == ' '))
1281    {
1282      startPos++;
1283    }
1284
1285    if (startPos >= length)
1286    {
1287      LocalizableMessage message =
1288          ERR_ATTR_SYNTAX_ATTRTYPE_TRUNCATED_VALUE.get(valueStr);
1289      throw new DirectoryException(
1290              ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
1291    }
1292
1293
1294    // Look at the next character.  If it is a quote, then parse until the next
1295    // quote and end.  If it is an open parenthesis, then parse individual
1296    // values until the close parenthesis and end.  Otherwise, parse until the
1297    // next space and end.
1298    if (c == '\'')
1299    {
1300      // Parse until the closing quote.
1301      StringBuilder valueBuffer = new StringBuilder();
1302      startPos++;
1303      while (startPos < length && ((c = valueStr.charAt(startPos)) != '\''))
1304      {
1305        valueBuffer.append(c);
1306        startPos++;
1307      }
1308      startPos++;
1309      valueList.add(valueBuffer.toString());
1310    }
1311    else if (c == '(')
1312    {
1313      startPos++;
1314      // We're expecting a list of values. Quoted, space separated.
1315      while (true)
1316      {
1317        // Skip over any leading spaces;
1318        while (startPos < length && ((c = valueStr.charAt(startPos)) == ' '))
1319        {
1320          startPos++;
1321        }
1322
1323        if (startPos >= length)
1324        {
1325          LocalizableMessage message =
1326              ERR_ATTR_SYNTAX_ATTRTYPE_TRUNCATED_VALUE.get(valueStr);
1327          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
1328                                       message);
1329        }
1330
1331        if (c == ')')
1332        {
1333          // This is the end of the list.
1334          startPos++;
1335          break;
1336        }
1337        else if (c == '(')
1338        {
1339          // This is an illegal character.
1340          LocalizableMessage message =
1341              ERR_ATTR_SYNTAX_ATTRSYNTAX_EXTENSION_INVALID_CHARACTER.get(
1342                      valueStr, startPos);
1343          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
1344                                       message);
1345        }
1346        else if (c == '\'')
1347        {
1348          // We have a quoted string
1349          StringBuilder valueBuffer = new StringBuilder();
1350          startPos++;
1351          while (startPos < length
1352              && ((c = valueStr.charAt(startPos)) != '\''))
1353          {
1354            valueBuffer.append(c);
1355            startPos++;
1356          }
1357
1358          valueList.add(valueBuffer.toString());
1359          startPos++;
1360        }
1361        else
1362        {
1363          //Consider unquoted string
1364          StringBuilder valueBuffer = new StringBuilder();
1365          while (startPos < length
1366              && ((c = valueStr.charAt(startPos)) != ' '))
1367          {
1368            valueBuffer.append(c);
1369            startPos++;
1370          }
1371
1372          valueList.add(valueBuffer.toString());
1373        }
1374
1375        if (startPos >= length)
1376        {
1377          LocalizableMessage message =
1378              ERR_ATTR_SYNTAX_ATTRTYPE_TRUNCATED_VALUE.get(valueStr);
1379          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
1380                                       message);
1381        }
1382      }
1383    }
1384    else
1385    {
1386      // Parse until the next space.
1387      StringBuilder valueBuffer = new StringBuilder();
1388      while (startPos < length && ((c = valueStr.charAt(startPos)) != ' '))
1389      {
1390        valueBuffer.append(c);
1391        startPos++;
1392      }
1393
1394      valueList.add(valueBuffer.toString());
1395    }
1396
1397    // Skip over any trailing spaces.
1398    while (startPos < length && valueStr.charAt(startPos) == ' ')
1399    {
1400      startPos++;
1401    }
1402
1403    if (startPos >= length)
1404    {
1405      LocalizableMessage message =
1406          ERR_ATTR_SYNTAX_ATTRTYPE_TRUNCATED_VALUE.get(valueStr);
1407      throw new DirectoryException(
1408              ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
1409    }
1410
1411    return startPos;
1412  }
1413
1414
1415
1416  /** {@inheritDoc} */
1417  @Override
1418  public ConfigChangeResult applyConfigurationChange(
1419              AttributeTypeDescriptionAttributeSyntaxCfg configuration)
1420  {
1421    currentConfig = configuration;
1422    stripMinimumUpperBound = configuration.isStripSyntaxMinUpperBound();
1423    updateNewSchema();
1424    return new ConfigChangeResult();
1425  }
1426
1427
1428
1429  /** {@inheritDoc} */
1430  @Override
1431  public boolean isConfigurationChangeAcceptable(
1432                      AttributeTypeDescriptionAttributeSyntaxCfg configuration,
1433                      List<LocalizableMessage> unacceptableReasons)
1434  {
1435    // The configuration will always be acceptable.
1436    return true;
1437  }
1438
1439  /**
1440   * Boolean that indicates that the minimum upper bound value should be
1441   * stripped from the Attribute Type Syntax Description.
1442   *
1443   * @return True if the minimum upper bound value should be stripped.
1444   */
1445  public static boolean isStripSyntaxMinimumUpperBound() {
1446    return stripMinimumUpperBound;
1447  }
1448}