/**
   * Indicates whether the provided object is equal to this attribute type. The object will be
   * considered equal if it is an attribute type with the same OID as the current type.
   *
   * @param o The object for which to make the determination.
   * @return <code>true</code> if the provided object is equal to this schema definition, or <code>
   *     false</code> if not.
   */
  @Override
  public final boolean equals(Object o) {
    if (this == o) {
      return true;
    }

    if (o instanceof CommonSchemaElements) {
      CommonSchemaElements other = (CommonSchemaElements) o;
      return getNameOrOID().equals(other.getNameOrOID());
    }

    return false;
  }
  /**
   * Decodes the contents of the provided byte sequence as an ldap syntax definition according to
   * the rules of this syntax. Note that the provided byte sequence value does not need to be
   * normalized (and in fact, it should not be in order to allow the desired capitalization to be
   * preserved).
   *
   * @param value The byte sequence containing the value to decode (it does not need to be
   *     normalized).
   * @param schema The schema to use to resolve references to other schema elements.
   * @param allowUnknownElements Indicates whether to allow values that are not defined in the
   *     server schema. This should only be true when called by {@code valueIsAcceptable}. Not used
   *     for LDAP Syntaxes
   * @return The decoded ldapsyntax definition.
   * @throws DirectoryException If the provided value cannot be decoded as an ldapsyntax definition.
   */
  public static LDAPSyntaxDescription decodeLDAPSyntax(
      ByteSequence value, Schema schema, boolean allowUnknownElements) throws DirectoryException {
    // Get string representations of the provided value using the provided form.
    String valueStr = value.toString();

    // We'll do this a character at a time.  First, skip over any leading
    // whitespace.
    int pos = 0;
    int length = valueStr.length();
    while ((pos < length) && (valueStr.charAt(pos) == ' ')) {
      pos++;
    }

    if (pos >= length) {
      // This means that the value was empty or contained only whitespace.  That
      // is illegal.

      Message message = ERR_ATTR_SYNTAX_LDAPSYNTAX_EMPTY_VALUE.get();
      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
    }

    // The next character must be an open parenthesis.  If it is not, then that
    // is an error.
    char c = valueStr.charAt(pos++);
    if (c != '(') {

      Message message =
          ERR_ATTR_SYNTAX_LDAPSYNTAX_EXPECTED_OPEN_PARENTHESIS.get(
              valueStr, (pos - 1), String.valueOf(c));
      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
    }

    // Skip over any spaces immediately following the opening parenthesis.
    while ((pos < length) && ((c = valueStr.charAt(pos)) == ' ')) {
      pos++;
    }

    if (pos >= length) {
      // This means that the end of the value was reached before we could find
      // the OID.  Ths is illegal.
      Message message = ERR_ATTR_SYNTAX_LDAPSYNTAX_TRUNCATED_VALUE.get(valueStr);
      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
    }

    int oidStartPos = pos;
    if (isDigit(c)) {
      // This must be a numeric OID.  In that case, we will accept only digits
      // and periods, but not consecutive periods.
      boolean lastWasPeriod = false;
      while ((pos < length)
          && ((c = valueStr.charAt(pos)) != ' ')
          && (c = valueStr.charAt(pos)) != ')') {
        if (c == '.') {
          if (lastWasPeriod) {
            Message message =
                ERR_ATTR_SYNTAX_LDAPSYNTAX_DOUBLE_PERIOD_IN_NUMERIC_OID.get(valueStr, (pos - 1));
            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
          } else {
            lastWasPeriod = true;
          }
        } else if (!isDigit(c)) {
          // This must have been an illegal character.
          Message message =
              ERR_ATTR_SYNTAX_LDAPSYNTAX_ILLEGAL_CHAR_IN_NUMERIC_OID.get(
                  valueStr, String.valueOf(c), (pos - 1));
          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
        } else {
          lastWasPeriod = false;
        }
        pos++;
      }
    } else {
      // This must be a "fake" OID.  In this case, we will only accept
      // alphabetic characters, numeric digits, and the hyphen.
      while ((pos < length)
          && ((c = valueStr.charAt(pos)) != ' ')
          && (c = valueStr.charAt(pos)) != ')') {
        if (isAlpha(c)
            || isDigit(c)
            || (c == '-')
            || ((c == '_') && DirectoryServer.allowAttributeNameExceptions())) {
          // This is fine.  It is an acceptable character.
          pos++;
        } else {
          // This must have been an illegal character.
          Message message =
              ERR_ATTR_SYNTAX_LDAPSYNTAX_ILLEGAL_CHAR_IN_STRING_OID.get(
                  valueStr, String.valueOf(c), (pos - 1));
          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
        }
      }
    }

    // If we're at the end of the value, then it isn't a valid attribute type
    // description.  Otherwise, parse out the OID.
    String oid;
    if (pos >= length) {
      Message message = ERR_ATTR_SYNTAX_LDAPSYNTAX_TRUNCATED_VALUE.get(valueStr);
      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
    } else {
      oid = toLowerCase(valueStr.substring(oidStartPos, pos));
    }

    // Skip over the space(s) after the OID.
    while ((pos < length) && ((c = valueStr.charAt(pos)) == ' ')) {
      pos++;
    }

    if (pos >= length) {
      // This means that the end of the value was reached before we could find
      // the OID.  Ths is illegal.
      Message message = ERR_ATTR_SYNTAX_LDAPSYNTAX_TRUNCATED_VALUE.get(valueStr);
      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
    }

    // At this point, we should have a pretty specific syntax that describes
    // what may come next, but some of the components are optional and it would
    // be pretty easy to put something in the wrong order, so we will be very
    // flexible about what we can accept.  Just look at the next token, figure
    // out what it is and how to treat what comes after it, then repeat until
    // we get to the end of the value.  But before we start, set default values
    // for everything else we might need to know.
    String description = null;
    LDAPSyntaxDescriptionSyntax syntax = null;
    HashMap<String, List<String>> extraProperties = new LinkedHashMap<String, List<String>>();
    boolean hasXSyntaxToken = false;

    while (true) {
      StringBuilder tokenNameBuffer = new StringBuilder();
      pos = readTokenName(valueStr, tokenNameBuffer, pos);
      String tokenName = tokenNameBuffer.toString();
      String lowerTokenName = toLowerCase(tokenName);
      if (tokenName.equals(")")) {
        // We must be at the end of the value.  If not, then that's a problem.
        if (pos < length) {
          Message message =
              ERR_ATTR_SYNTAX_LDAPSYNTAX_UNEXPECTED_CLOSE_PARENTHESIS.get(valueStr, (pos - 1));
          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
        }

        break;
      } else if (lowerTokenName.equals("desc")) {
        // This specifies the description for the attribute type.  It is an
        // arbitrary string of characters enclosed in single quotes.
        StringBuilder descriptionBuffer = new StringBuilder();
        pos = readQuotedString(valueStr, descriptionBuffer, pos);
        description = descriptionBuffer.toString();
      } else if (lowerTokenName.equals("x-subst")) {
        if (hasXSyntaxToken) {
          // We've already seen syntax extension. More than 1 is not allowed
          Message message = ERR_ATTR_SYNTAX_LDAPSYNTAX_TOO_MANY_EXTENSIONS.get(valueStr);
          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
        }
        hasXSyntaxToken = true;
        StringBuilder woidBuffer = new StringBuilder();
        pos = readQuotedString(valueStr, woidBuffer, pos);
        String syntaxOID = toLowerCase(woidBuffer.toString());
        AttributeSyntax<?> subSyntax = schema.getSyntax(syntaxOID);
        if (subSyntax == null) {
          Message message =
              ERR_ATTR_SYNTAX_LDAPSYNTAX_UNKNOWN_SYNTAX.get(String.valueOf(oid), syntaxOID);
          throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
        }
        syntax = new SubstitutionSyntax(subSyntax, valueStr, description, oid);
      } else if (lowerTokenName.equals("x-pattern")) {
        if (hasXSyntaxToken) {
          // We've already seen syntax extension. More than 1 is not allowed
          Message message = ERR_ATTR_SYNTAX_LDAPSYNTAX_TOO_MANY_EXTENSIONS.get(valueStr);
          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
        }
        hasXSyntaxToken = true;
        StringBuilder regexBuffer = new StringBuilder();
        pos = readQuotedString(valueStr, regexBuffer, pos);
        String regex = regexBuffer.toString().trim();
        if (regex.length() == 0) {
          Message message = WARN_ATTR_SYNTAX_LDAPSYNTAX_REGEX_NO_PATTERN.get(valueStr);
          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
        }

        try {
          Pattern pattern = Pattern.compile(regex);
          syntax = new RegexSyntax(pattern, valueStr, description, oid);
        } catch (Exception e) {
          Message message = WARN_ATTR_SYNTAX_LDAPSYNTAX_REGEX_INVALID_PATTERN.get(valueStr, regex);
          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
        }
      } else if (lowerTokenName.equals("x-enum")) {
        if (hasXSyntaxToken) {
          // We've already seen syntax extension. More than 1 is not allowed
          Message message = ERR_ATTR_SYNTAX_LDAPSYNTAX_TOO_MANY_EXTENSIONS.get(valueStr);
          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
        }
        hasXSyntaxToken = true;
        LinkedList<String> values = new LinkedList<String>();
        pos = readExtraParameterValues(valueStr, values, pos);

        if (values.isEmpty()) {
          Message message = ERR_ATTR_SYNTAX_LDAPSYNTAX_ENUM_NO_VALUES.get(valueStr);
          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
        }
        // Parse all enum values, check for uniqueness
        LinkedList<ByteSequence> entries = new LinkedList<ByteSequence>();
        for (String v : values) {
          ByteString entry = ByteString.valueOf(v);
          if (entries.contains(entry)) {
            Message message =
                WARN_ATTR_SYNTAX_LDAPSYNTAX_ENUM_DUPLICATE_VALUE.get(
                    valueStr, entry.toString(), pos);
            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
          }
          entries.add(entry);
        }
        syntax = new EnumSyntax(entries, valueStr, description, oid);
      } else if (tokenName.matches("X\\-[_\\p{Alpha}-]+")) {
        // This must be a non-standard property and it must be followed by
        // either a single value in single quotes or an open parenthesis
        // followed by one or more values in single quotes separated by spaces
        // followed by a close parenthesis.
        List<String> valueList = new ArrayList<String>();
        pos = readExtraParameterValues(valueStr, valueList, pos);
        extraProperties.put(tokenName, valueList);
      } else {
        // Unknown Token
        Message message = ERR_ATTR_SYNTAX_LDAPSYNTAX_UNKNOWN_EXT.get(valueStr, tokenName, pos);
        throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
      }
    }
    if (syntax == null) {
      // Create a plain Syntax. That seems to be required by export/import
      // Schema backend.
      syntax = new LDAPSyntaxDescriptionSyntax();
    }

    CommonSchemaElements.checkSafeProperties(extraProperties);

    // Since we reached here it means everything is OK.
    return new LDAPSyntaxDescription(
        valueStr, syntax,
        description, extraProperties);
  }