/**
   * Decodes the provided list of strings as the representation of a backup info structure.
   *
   * @param backupDirectory The reference to the backup directory with which the backup info is
   *     associated.
   * @param encodedInfo The list of strings that comprise the string representation of the backup
   *     info structure.
   * @return The decoded backup info structure.
   * @throws ConfigException If a problem occurs while attempting to decode the backup info data.
   */
  public static BackupInfo decode(BackupDirectory backupDirectory, LinkedList<String> encodedInfo)
      throws ConfigException {
    String backupID = null;
    Date backupDate = null;
    boolean isIncremental = false;
    boolean isCompressed = false;
    boolean isEncrypted = false;
    byte[] unsignedHash = null;
    byte[] signedHash = null;
    HashSet<String> dependencies = new HashSet<String>();
    HashMap<String, String> backupProperties = new HashMap<String, String>();

    String backupPath = backupDirectory.getPath();
    try {
      for (String line : encodedInfo) {
        int equalPos = line.indexOf('=');
        if (equalPos < 0) {
          Message message = ERR_BACKUPINFO_NO_DELIMITER.get(line, backupPath);
          throw new ConfigException(message);
        } else if (equalPos == 0) {
          Message message = ERR_BACKUPINFO_NO_NAME.get(line, backupPath);
          throw new ConfigException(message);
        }

        String name = line.substring(0, equalPos);
        String value = line.substring(equalPos + 1);

        if (name.equals(PROPERTY_BACKUP_ID)) {
          if (backupID == null) {
            backupID = value;
          } else {
            Message message = ERR_BACKUPINFO_MULTIPLE_BACKUP_IDS.get(backupPath, backupID, value);
            throw new ConfigException(message);
          }
        } else if (name.equals(PROPERTY_BACKUP_DATE)) {
          SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT_GMT_TIME);
          dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
          backupDate = dateFormat.parse(value);
        } else if (name.equals(PROPERTY_IS_INCREMENTAL)) {
          isIncremental = Boolean.valueOf(value);
        } else if (name.equals(PROPERTY_IS_COMPRESSED)) {
          isCompressed = Boolean.valueOf(value);
        } else if (name.equals(PROPERTY_IS_ENCRYPTED)) {
          isEncrypted = Boolean.valueOf(value);
        } else if (name.equals(PROPERTY_UNSIGNED_HASH)) {
          unsignedHash = Base64.decode(value);
        } else if (name.equals(PROPERTY_SIGNED_HASH)) {
          signedHash = Base64.decode(value);
        } else if (name.equals(PROPERTY_DEPENDENCY)) {
          dependencies.add(value);
        } else if (name.startsWith(PROPERTY_CUSTOM_PREFIX)) {
          String propertyName = name.substring(PROPERTY_CUSTOM_PREFIX.length());
          backupProperties.put(propertyName, value);
        } else {
          Message message = ERR_BACKUPINFO_UNKNOWN_PROPERTY.get(backupPath, name, value);
          throw new ConfigException(message);
        }
      }
    } catch (ConfigException ce) {
      throw ce;
    } catch (Exception e) {
      if (debugEnabled()) {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }

      Message message = ERR_BACKUPINFO_CANNOT_DECODE.get(backupPath, getExceptionMessage(e));
      throw new ConfigException(message, e);
    }

    // There must have been at least a backup ID and backup date
    // specified.
    if (backupID == null) {
      Message message = ERR_BACKUPINFO_NO_BACKUP_ID.get(backupPath);
      throw new ConfigException(message);
    }

    if (backupDate == null) {
      Message message = ERR_BACKUPINFO_NO_BACKUP_DATE.get(backupID, backupPath);
      throw new ConfigException(message);
    }

    return new BackupInfo(
        backupDirectory,
        backupID,
        backupDate,
        isIncremental,
        isCompressed,
        isEncrypted,
        unsignedHash,
        signedHash,
        dependencies,
        backupProperties);
  }
  /**
   * Generates an entry for a backup based on the provided DN. The DN must have an RDN component
   * that specifies the backup ID, and the parent DN must have an RDN component that specifies the
   * backup directory.
   *
   * @param entryDN The DN of the backup entry to retrieve.
   * @return The requested backup entry.
   * @throws DirectoryException If the specified backup does not exist or is invalid.
   */
  private Entry getBackupEntry(DN entryDN) throws DirectoryException {
    // First, get the backup ID from the entry DN.
    AttributeType idType = DirectoryServer.getAttributeType(ATTR_BACKUP_ID, true);
    AttributeValue idValue = entryDN.getRDN().getAttributeValue(idType);
    if (idValue == null) {
      Message message = ERR_BACKUP_NO_BACKUP_ID_IN_DN.get(String.valueOf(entryDN));
      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
    }
    String backupID = idValue.getValue().toString();

    // Next, get the backup directory from the parent DN.
    DN parentDN = entryDN.getParentDNInSuffix();
    if (parentDN == null) {
      Message message = ERR_BACKUP_NO_BACKUP_PARENT_DN.get(String.valueOf(entryDN));
      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
    }

    AttributeType t = DirectoryServer.getAttributeType(ATTR_BACKUP_DIRECTORY_PATH, true);
    AttributeValue v = parentDN.getRDN().getAttributeValue(t);
    if (v == null) {
      Message message = ERR_BACKUP_NO_BACKUP_DIR_IN_DN.get(String.valueOf(entryDN));
      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
    }

    BackupDirectory backupDirectory;
    try {
      backupDirectory = BackupDirectory.readBackupDirectoryDescriptor(v.getValue().toString());
    } catch (ConfigException ce) {
      if (debugEnabled()) {
        TRACER.debugCaught(DebugLogLevel.ERROR, ce);
      }

      Message message =
          ERR_BACKUP_INVALID_BACKUP_DIRECTORY.get(String.valueOf(entryDN), ce.getMessageObject());
      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
    } catch (Exception e) {
      if (debugEnabled()) {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }

      Message message = ERR_BACKUP_ERROR_GETTING_BACKUP_DIRECTORY.get(getExceptionMessage(e));
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message);
    }

    BackupInfo backupInfo = backupDirectory.getBackupInfo(backupID);
    if (backupInfo == null) {
      Message message = ERR_BACKUP_NO_SUCH_BACKUP.get(backupID, backupDirectory.getPath());
      throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message, parentDN, null);
    }

    // Construct the backup entry to return.
    LinkedHashMap<ObjectClass, String> ocMap = new LinkedHashMap<ObjectClass, String>(3);
    ocMap.put(DirectoryServer.getTopObjectClass(), OC_TOP);

    ObjectClass oc = DirectoryServer.getObjectClass(OC_BACKUP_INFO, true);
    ocMap.put(oc, OC_BACKUP_INFO);

    oc = DirectoryServer.getObjectClass(OC_EXTENSIBLE_OBJECT_LC, true);
    ocMap.put(oc, OC_EXTENSIBLE_OBJECT);

    LinkedHashMap<AttributeType, List<Attribute>> opAttrs =
        new LinkedHashMap<AttributeType, List<Attribute>>(0);
    LinkedHashMap<AttributeType, List<Attribute>> userAttrs =
        new LinkedHashMap<AttributeType, List<Attribute>>();

    ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
    attrList.add(Attributes.create(idType, idValue));
    userAttrs.put(idType, attrList);

    backupInfo.getBackupDirectory();
    attrList = new ArrayList<Attribute>(1);
    attrList.add(Attributes.create(t, v));
    userAttrs.put(t, attrList);

    Date backupDate = backupInfo.getBackupDate();
    if (backupDate != null) {
      t = DirectoryServer.getAttributeType(ATTR_BACKUP_DATE, true);
      attrList = new ArrayList<Attribute>(1);
      attrList.add(
          Attributes.create(
              t, AttributeValues.create(t, GeneralizedTimeSyntax.format(backupDate))));
      userAttrs.put(t, attrList);
    }

    t = DirectoryServer.getAttributeType(ATTR_BACKUP_COMPRESSED, true);
    attrList = new ArrayList<Attribute>(1);
    attrList.add(Attributes.create(t, BooleanSyntax.createBooleanValue(backupInfo.isCompressed())));
    userAttrs.put(t, attrList);

    t = DirectoryServer.getAttributeType(ATTR_BACKUP_ENCRYPTED, true);
    attrList = new ArrayList<Attribute>(1);
    attrList.add(Attributes.create(t, BooleanSyntax.createBooleanValue(backupInfo.isEncrypted())));
    userAttrs.put(t, attrList);

    t = DirectoryServer.getAttributeType(ATTR_BACKUP_INCREMENTAL, true);
    attrList = new ArrayList<Attribute>(1);
    attrList.add(
        Attributes.create(t, BooleanSyntax.createBooleanValue(backupInfo.isIncremental())));
    userAttrs.put(t, attrList);

    HashSet<String> dependencies = backupInfo.getDependencies();
    if (dependencies != null && !dependencies.isEmpty()) {
      t = DirectoryServer.getAttributeType(ATTR_BACKUP_DEPENDENCY, true);
      AttributeBuilder builder = new AttributeBuilder(t);
      for (String s : dependencies) {
        builder.add(AttributeValues.create(t, s));
      }
      attrList = new ArrayList<Attribute>(1);
      attrList.add(builder.toAttribute());
      userAttrs.put(t, attrList);
    }

    byte[] signedHash = backupInfo.getSignedHash();
    if (signedHash != null) {
      t = DirectoryServer.getAttributeType(ATTR_BACKUP_SIGNED_HASH, true);
      attrList = new ArrayList<Attribute>(1);
      attrList.add(Attributes.create(t, AttributeValues.create(t, ByteString.wrap(signedHash))));
      userAttrs.put(t, attrList);
    }

    byte[] unsignedHash = backupInfo.getUnsignedHash();
    if (unsignedHash != null) {
      t = DirectoryServer.getAttributeType(ATTR_BACKUP_UNSIGNED_HASH, true);
      attrList = new ArrayList<Attribute>(1);
      attrList.add(Attributes.create(t, AttributeValues.create(t, ByteString.wrap(unsignedHash))));
      userAttrs.put(t, attrList);
    }

    HashMap<String, String> properties = backupInfo.getBackupProperties();
    if (properties != null && !properties.isEmpty()) {
      for (Map.Entry<String, String> e : properties.entrySet()) {
        t = DirectoryServer.getAttributeType(toLowerCase(e.getKey()), true);
        attrList = new ArrayList<Attribute>(1);
        attrList.add(Attributes.create(t, AttributeValues.create(t, e.getValue())));
        userAttrs.put(t, attrList);
      }
    }

    Entry e = new Entry(entryDN, ocMap, userAttrs, opAttrs);
    e.processVirtualAttributes();
    return e;
  }