*  @param reason (optional) a reason for the rollback. Use "" for the
     *  default ([[MediaWiki:Revertpage]]).
     *  @throws IOException if a network error occurs
     *  @throws CredentialExpiredException if cookies have expired
     *  @throws CredentialNotFoundException if the user is not an admin
     *  @throws AccountLockedException if the user is blocked
     *  @since 0.19
     */
    public synchronized void rollback(Wiki.Revision revision, boolean bot, String reason) throws IOException, LoginException
    {
        // check rights
        if (user == null || !user.isAllowedTo("rollback"))
            throw new CredentialNotFoundException("Permission denied: cannot rollback.");
        statusCheck();
        // check if cookies have expired
        if (!cookies.containsValue(user.getUsername()))
        {
            logger.log(Level.SEVERE, "Cookies have expired.");
            logout();
            throw new CredentialExpiredException("Cookies have expired.");
        }

        // check whether we are "on top".
        Wiki.Revision top = getTopRevision(revision.getPage());
        if (!top.equals(revision))
        {
            log(Level.INFO, "Rollback failed: revision is not the most recent", "rollback");
            return;
        }

        // get the rollback token
        String token = URLEncoder.encode(top.getRollbackToken(), "UTF-8");

        // Perform the rollback. Although it's easier through the human interface, we want
        // to make sense of any resulting errors.
        StringBuilder buffer = new StringBuilder(10000);
        buffer.append("title=");
        buffer.append(revision.getPage());
        buffer.append("&user=");
        buffer.append(revision.getUser());
        buffer.append("&token=");
        buffer.append(token);
        if (bot && user.isAllowedTo("markbotedits"))
            buffer.append("&markbot=1");
        if (!reason.isEmpty())
        {
            buffer.append("&summary=");
            buffer.append(reason);
        }
        String response = post(apiUrl + "action=rollback", buffer.toString(), "rollback");

        // done
        try
        {
            // ignorable errors
            if (response.contains("alreadyrolled"))
                log(Level.INFO, "Edit has already been rolled back.", "rollback");
            else if (response.contains("onlyauthor"))
                log(Level.INFO, "Cannot rollback as the page only has one author.", "rollback");
            // probably not ignorable (otherwise success)
            else if (!response.contains("rollback title="))
                checkErrors(response, "rollback");
        }
        catch (IOException e)
        {
            // retry once
            if (retry)
            {
                retry = false;
                log(Level.WARNING, "Exception: " + e.getMessage() + " Retrying...", "rollback");
                rollback(revision, bot, reason);
            }
            else
            {
                logger.logp(Level.SEVERE, "Wiki", "rollback()", "[" + domain + "] EXCEPTION:  ", e);
                throw e;
            }
        }
        if (retry)
            log(Level.INFO, "Successfully reverted edits by " + user + " on " + revision.getPage(), "rollback");
        retry = true;
    }