/** @see XMPSchemaRegistry#registerNamespace(String, String) */
  public synchronized String registerNamespace(String namespaceURI, String suggestedPrefix)
      throws XMPException {
    ParameterAsserts.assertSchemaNS(namespaceURI);
    ParameterAsserts.assertPrefix(suggestedPrefix);

    if (suggestedPrefix.charAt(suggestedPrefix.length() - 1) != ':') {
      suggestedPrefix += ':';
    }

    if (!Utils.isXMLNameNS(suggestedPrefix.substring(0, suggestedPrefix.length() - 1))) {
      throw new XMPException("The prefix is a bad XML name", XMPError.BADXML);
    }

    String registeredPrefix = (String) namespaceToPrefixMap.get(namespaceURI);
    String registeredNS = (String) prefixToNamespaceMap.get(suggestedPrefix);
    if (registeredPrefix != null) {
      // Return the actual prefix
      return registeredPrefix;
    } else {
      if (registeredNS != null) {
        // the namespace is new, but the prefix is already engaged,
        // we generate a new prefix out of the suggested
        String generatedPrefix = suggestedPrefix;
        for (int i = 1; prefixToNamespaceMap.containsKey(generatedPrefix); i++) {
          generatedPrefix =
              suggestedPrefix.substring(0, suggestedPrefix.length() - 1) + "_" + i + "_:";
        }
        suggestedPrefix = generatedPrefix;
      }
      prefixToNamespaceMap.put(suggestedPrefix, namespaceURI);
      namespaceToPrefixMap.put(namespaceURI, suggestedPrefix);

      // Return the suggested prefix
      return suggestedPrefix;
    }
  }
  /**
   * Associates an alias name with an actual name.
   *
   * <p>Define a alias mapping from one namespace/property to another. Both property names must be
   * simple names. An alias can be a direct mapping, where the alias and actual have the same data
   * type. It is also possible to map a simple alias to an item in an array. This can either be to
   * the first item in the array, or to the 'x-default' item in an alt-text array. Multiple alias
   * names may map to the same actual, as long as the forms match. It is a no-op to reregister an
   * alias in an identical fashion. Note: This method is not locking because only called by
   * registerStandardAliases which is only called by the constructor. Note2: The method is only
   * package-private so that it can be tested with unittests
   *
   * @param aliasNS The namespace URI for the alias. Must not be null or the empty string.
   * @param aliasProp The name of the alias. Must be a simple name, not null or the empty string and
   *     not a general path expression.
   * @param actualNS The namespace URI for the actual. Must not be null or the empty string.
   * @param actualProp The name of the actual. Must be a simple name, not null or the empty string
   *     and not a general path expression.
   * @param aliasForm Provides options for aliases for simple aliases to array items. This is needed
   *     to know what kind of array to create if set for the first time via the simple alias. Pass
   *     <code>XMP_NoOptions</code>, the default value, for all direct aliases regardless of whether
   *     the actual data type is an array or not (see {@link AliasOptions}).
   * @throws XMPException for inconsistant aliases.
   */
  synchronized void registerAlias(
      String aliasNS,
      String aliasProp,
      final String actualNS,
      final String actualProp,
      final AliasOptions aliasForm)
      throws XMPException {
    ParameterAsserts.assertSchemaNS(aliasNS);
    ParameterAsserts.assertPropName(aliasProp);
    ParameterAsserts.assertSchemaNS(actualNS);
    ParameterAsserts.assertPropName(actualProp);

    // Fix the alias options
    final AliasOptions aliasOpts =
        aliasForm != null
            ? new AliasOptions(
                XMPNodeUtils.verifySetOptions(aliasForm.toPropertyOptions(), null).getOptions())
            : new AliasOptions();

    if (p.matcher(aliasProp).find() || p.matcher(actualProp).find()) {
      throw new XMPException("Alias and actual property names must be simple", XMPError.BADXPATH);
    }

    // check if both namespaces are registered
    final String aliasPrefix = getNamespacePrefix(aliasNS);
    final String actualPrefix = getNamespacePrefix(actualNS);
    if (aliasPrefix == null) {
      throw new XMPException("Alias namespace is not registered", XMPError.BADSCHEMA);
    } else if (actualPrefix == null) {
      throw new XMPException("Actual namespace is not registered", XMPError.BADSCHEMA);
    }

    String key = aliasPrefix + aliasProp;

    // check if alias is already existing
    if (aliasMap.containsKey(key)) {
      throw new XMPException("Alias is already existing", XMPError.BADPARAM);
    } else if (aliasMap.containsKey(actualPrefix + actualProp)) {
      throw new XMPException(
          "Actual property is already an alias, use the base property", XMPError.BADPARAM);
    }

    XMPAliasInfo aliasInfo =
        new XMPAliasInfo() {
          /** @see XMPAliasInfo#getNamespace() */
          public String getNamespace() {
            return actualNS;
          }

          /** @see XMPAliasInfo#getPrefix() */
          public String getPrefix() {
            return actualPrefix;
          }

          /** @see XMPAliasInfo#getPropName() */
          public String getPropName() {
            return actualProp;
          }

          /** @see XMPAliasInfo#getAliasForm() */
          public AliasOptions getAliasForm() {
            return aliasOpts;
          }

          public String toString() {
            return actualPrefix
                + actualProp
                + " NS("
                + actualNS
                + "), FORM ("
                + getAliasForm()
                + ")";
          }
        };

    aliasMap.put(key, aliasInfo);
  }