/*
 * ftp4j - A pure Java FTP client library
 * 
 * Copyright (C) 2008-2010 Carlo Pelliccia (www.sauronsoftware.it)
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version
 * 2.1, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License 2.1 for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License version 2.1 along with this program.
 * If not, see 
	 * client.rename("oldname", "newname"); // This one renames
	 * 
	 * 
	 * 
	 * client.rename("the/old/path/oldname", "/a/new/path/newname"); // This one moves
	 * 
	 * 
	 * @param oldPath
	 *            The current path of the file (or directory).
	 * @param newPath
	 *            The new path for the file (or directory).
	 * @throws IllegalStateException
	 *             If the client is not connected or not authenticated.
	 * @throws IOException
	 *             If an I/O error occurs.
	 * @throws FTPIllegalReplyException
	 *             If the server replies in an illegal way.
	 * @throws FTPException
	 *             If the operation fails.
	 */
	public void rename(String oldPath, String newPath)
			throws IllegalStateException, IOException,
			FTPIllegalReplyException, FTPException {
		synchronized (lock) {
			// Is this client connected?
			if (!connected) {
				throw new IllegalStateException("Client not connected");
			}
			// Is this client authenticated?
			if (!authenticated) {
				throw new IllegalStateException("Client not authenticated");
			}
			// Sends the RNFR command.
			communication.sendFTPCommand("RNFR " + oldPath);
			FTPReply r = communication.readFTPReply();
			touchAutoNoopTimer();
			if (r.getCode() != 350) {
				throw new FTPException(r);
			}
			// Sends the RNFR command.
			communication.sendFTPCommand("RNTO " + newPath);
			r = communication.readFTPReply();
			touchAutoNoopTimer();
			if (!r.isSuccessCode()) {
				throw new FTPException(r);
			}
		}
	}
	/**
	 * This method deletes a remote file.
	 * 
	 * @param path
	 *            The path to the file.
	 * @throws IllegalStateException
	 *             If the client is not connected or not authenticated.
	 * @throws IOException
	 *             If an I/O error occurs.
	 * @throws FTPIllegalReplyException
	 *             If the server replies in an illegal way.
	 * @throws FTPException
	 *             If the operation fails.
	 */
	public void deleteFile(String path) throws IllegalStateException,
			IOException, FTPIllegalReplyException, FTPException {
		synchronized (lock) {
			// Is this client connected?
			if (!connected) {
				throw new IllegalStateException("Client not connected");
			}
			// Is this client authenticated?
			if (!authenticated) {
				throw new IllegalStateException("Client not authenticated");
			}
			// Sends the DELE command.
			communication.sendFTPCommand("DELE " + path);
			FTPReply r = communication.readFTPReply();
			touchAutoNoopTimer();
			if (!r.isSuccessCode()) {
				throw new FTPException(r);
			}
		}
	}
	/**
	 * This method deletes a remote directory.
	 * 
	 * @param path
	 *            The path to the directory.
	 * @throws IllegalStateException
	 *             If the client is not connected or not authenticated.
	 * @throws IOException
	 *             If an I/O error occurs.
	 * @throws FTPIllegalReplyException
	 *             If the server replies in an illegal way.
	 * @throws FTPException
	 *             If the operation fails.
	 */
	public void deleteDirectory(String path) throws IllegalStateException,
			IOException, FTPIllegalReplyException, FTPException {
		synchronized (lock) {
			// Is this client connected?
			if (!connected) {
				throw new IllegalStateException("Client not connected");
			}
			// Is this client authenticated?
			if (!authenticated) {
				throw new IllegalStateException("Client not authenticated");
			}
			// Sends the RMD command.
			communication.sendFTPCommand("RMD " + path);
			FTPReply r = communication.readFTPReply();
			touchAutoNoopTimer();
			if (!r.isSuccessCode()) {
				throw new FTPException(r);
			}
		}
	}
	/**
	 * This method creates a new remote directory in the current working one.
	 * 
	 * @param directoryName
	 *            The name of the new directory.
	 * @throws IllegalStateException
	 *             If the client is not connected or not authenticated.
	 * @throws IOException
	 *             If an I/O error occurs.
	 * @throws FTPIllegalReplyException
	 *             If the server replies in an illegal way.
	 * @throws FTPException
	 *             If the operation fails.
	 */
	public void createDirectory(String directoryName)
			throws IllegalStateException, IOException,
			FTPIllegalReplyException, FTPException {
		synchronized (lock) {
			// Is this client connected?
			if (!connected) {
				throw new IllegalStateException("Client not connected");
			}
			// Is this client authenticated?
			if (!authenticated) {
				throw new IllegalStateException("Client not authenticated");
			}
			// Sends the MKD command.
			communication.sendFTPCommand("MKD " + directoryName);
			FTPReply r = communication.readFTPReply();
			touchAutoNoopTimer();
			if (!r.isSuccessCode()) {
				throw new FTPException(r);
			}
		}
	}
	/**
	 * This method calls the HELP command on the remote server, returning a list
	 * of lines with the help contents.
	 * 
	 * @return The help contents, splitted by line.
	 * @throws IllegalStateException
	 *             If the client is not connected or not authenticated.
	 * @throws IOException
	 *             If an I/O error occurs.
	 * @throws FTPIllegalReplyException
	 *             If the server replies in an illegal way.
	 * @throws FTPException
	 *             If the operation fails.
	 */
	public String[] help() throws IllegalStateException, IOException,
			FTPIllegalReplyException, FTPException {
		synchronized (lock) {
			// Is this client connected?
			if (!connected) {
				throw new IllegalStateException("Client not connected");
			}
			// Is this client authenticated?
			if (!authenticated) {
				throw new IllegalStateException("Client not authenticated");
			}
			// Sends the HELP command.
			communication.sendFTPCommand("HELP");
			FTPReply r = communication.readFTPReply();
			touchAutoNoopTimer();
			if (!r.isSuccessCode()) {
				throw new FTPException(r);
			}
			return r.getMessages();
		}
	}
	/**
	 * This method returns the remote server status, as the result of a FTP STAT
	 * command.
	 * 
	 * @return The remote server status, splitted by line.
	 * @throws IllegalStateException
	 *             If the client is not connected or not authenticated.
	 * @throws IOException
	 *             If an I/O error occurs.
	 * @throws FTPIllegalReplyException
	 *             If the server replies in an illegal way.
	 * @throws FTPException
	 *             If the operation fails.
	 */
	public String[] serverStatus() throws IllegalStateException, IOException,
			FTPIllegalReplyException, FTPException {
		synchronized (lock) {
			// Is this client connected?
			if (!connected) {
				throw new IllegalStateException("Client not connected");
			}
			// Is this client authenticated?
			if (!authenticated) {
				throw new IllegalStateException("Client not authenticated");
			}
			// Sends the STAT command.
			communication.sendFTPCommand("STAT");
			FTPReply r = communication.readFTPReply();
			touchAutoNoopTimer();
			if (!r.isSuccessCode()) {
				throw new FTPException(r);
			}
			return r.getMessages();
		}
	}
	/**
	 * This method lists the entries of the current working directory parsing
	 * the reply to a FTP LIST command.
	 * 
	 * The response to the LIST command is parsed through the FTPListParser
	 * objects registered on the client. The distribution of ftp4j contains some
	 * standard parsers already registered on every FTPClient object created. If
	 * they don't work in your case (a FTPListParseException is thrown), you can
	 * build your own parser implementing the FTPListParser interface and add it
	 * to the client by calling its addListParser() method.
	 * 
	 * Calling this method blocks the current thread until the operation is
	 * completed. The operation could be interrupted by another thread calling
	 * abortCurrentDataTransfer(). The list() method will break with a
	 * FTPAbortedException.
	 * 
	 * @param fileSpec
	 *            A file filter string. Depending on the server implementation,
	 *            wildcard characters could be accepted.
	 * @return The list of the files (and directories) in the current working
	 *         directory.
	 * @throws IllegalStateException
	 *             If the client is not connected or not authenticated.
	 * @throws IOException
	 *             If an I/O error occurs.
	 * @throws FTPIllegalReplyException
	 *             If the server replies in an illegal way.
	 * @throws FTPException
	 *             If the operation fails.
	 * @throws FTPDataTransferException
	 *             If a I/O occurs in the data transfer connection. If you
	 *             receive this exception the transfer failed, but the main
	 *             connection with the remote FTP server is in theory still
	 *             working.
	 * @throws FTPAbortedException
	 *             If operation is aborted by another thread.
	 * @throws FTPListParseException
	 *             If none of the registered parsers can handle the response
	 *             sent by the server.
	 * @see FTPListParser
	 * @see FTPClient#addListParser(FTPListParser)
	 * @see FTPClient#getListParsers()
	 * @see FTPClient#abortCurrentDataTransfer(boolean)
	 * @see FTPClient#listNames()
	 * @since 1.2
	 */
	public FTPFile[] list(String fileSpec) throws IllegalStateException,
			IOException, FTPIllegalReplyException, FTPException,
			FTPDataTransferException, FTPAbortedException,
			FTPListParseException {
		synchronized (lock) {
			// Is this client connected?
			if (!connected) {
				throw new IllegalStateException("Client not connected");
			}
			// Is this client authenticated?
			if (!authenticated) {
				throw new IllegalStateException("Client not authenticated");
			}
			// ASCII, please!
			communication.sendFTPCommand("TYPE A");
			FTPReply r = communication.readFTPReply();
			touchAutoNoopTimer();
			if (!r.isSuccessCode()) {
				throw new FTPException(r);
			}
			// Prepares the connection for the data transfer.
			FTPDataTransferConnectionProvider provider = openDataTransferChannel();
			// MLSD or LIST command?
			boolean mlsdCommand;
			if (mlsdPolicy == MLSD_IF_SUPPORTED) {
				mlsdCommand = mlsdSupported;
			} else if (mlsdPolicy == MLSD_ALWAYS) {
				mlsdCommand = true;
			} else {
				mlsdCommand = false;
			}
			String command = mlsdCommand ? "MLSD" : "LIST";
			// Adds the file/directory selector.
			if (fileSpec != null && fileSpec.length() > 0) {
				command += " " + fileSpec;
			}
			// Prepares the lines array.
			ArrayList lines = new ArrayList();
			// Local abort state.
			boolean wasAborted = false;
			// Sends the command.
			communication.sendFTPCommand(command);
			try {
				Socket dtConnection;
				try {
					dtConnection = provider.openDataTransferConnection();
				} finally {
					provider.dispose();
				}
				// Change the operation status.
				synchronized (abortLock) {
					ongoingDataTransfer = true;
					aborted = false;
					consumeAborCommandReply = false;
				}
				// Fetch the list from the data transfer connection.
				NVTASCIIReader dataReader = null;
				try {
					// Opens the data transfer connection.
					dataTransferInputStream = dtConnection.getInputStream();
					// MODE Z enabled?
					if (modezEnabled) {
						dataTransferInputStream = new InflaterInputStream(dataTransferInputStream);
					}
					// Let's do it!
					dataReader = new NVTASCIIReader(dataTransferInputStream, mlsdCommand ? "UTF-8" : pickCharset());
					String line;
					while ((line = dataReader.readLine()) != null) {
						if (line.length() > 0) {
							lines.add(line);
						}
					}
				} catch (IOException e) {
					synchronized (abortLock) {
						if (aborted) {
							throw new FTPAbortedException();
						} else {
							throw new FTPDataTransferException(
									"I/O error in data transfer", e);
						}
					}
				} finally {
					if (dataReader != null) {
						try {
							dataReader.close();
						} catch (Throwable t) {
							;
						}
					}
					try {
						dtConnection.close();
					} catch (Throwable t) {
						;
					}
					// Set to null the instance-level input stream.
					dataTransferInputStream = null;
					// Change the operation status.
					synchronized (abortLock) {
						wasAborted = aborted;
						ongoingDataTransfer = false;
						aborted = false;
					}
				}
			} finally {
				r = communication.readFTPReply();
				touchAutoNoopTimer();
				if (r.getCode() != 150 && r.getCode() != 125) {
					throw new FTPException(r);
				}
				// Consumes the result reply of the transfer.
				r = communication.readFTPReply();
				if (!wasAborted && r.getCode() != 226) {
					throw new FTPException(r);
				}
				// ABOR command response (if needed).
				if (consumeAborCommandReply) {
					communication.readFTPReply();
					consumeAborCommandReply = false;
				}
			}
			// Build an array of lines.
			int size = lines.size();
			String[] list = new String[size];
			for (int i = 0; i < size; i++) {
				list[i] = (String) lines.get(i);
			}
			// Parse the list.
			FTPFile[] ret = null;
			if (mlsdCommand) {
				// Forces the MLSDListParser.
				MLSDListParser parser = new MLSDListParser();
				ret = parser.parse(list);
			} else {
				// Is there any already successful parser?
				if (parser != null) {
					// Yes, let's try with it.
					try {
						ret = parser.parse(list);
					} catch (FTPListParseException e) {
						// That parser doesn't work anymore.
						parser = null;
					}
				}
				// Is there an available result?
				if (ret == null) {
					// Try to parse the list with every available parser.
					for (Iterator i = listParsers.iterator(); i.hasNext();) {
						FTPListParser aux = (FTPListParser) i.next();
						try {
							// Let's try!
							ret = aux.parse(list);
							// This parser smells good!
							parser = aux;
							// Leave the loop.
							break;
						} catch (FTPListParseException e) {
							// Let's try the next one.
							continue;
						}
					}
				}
			}
			if (ret == null) {
				// None of the parsers can handle the list response.
				throw new FTPListParseException();
			} else {
				// Return the parsed list.
				return ret;
			}
		}
	}
	/**
	 * This method lists the entries of the current working directory parsing
	 * the reply to a FTP LIST command.
	 * 
	 * The response to the LIST command is parsed through the FTPListParser
	 * objects registered on the client. The distribution of ftp4j contains some
	 * standard parsers already registered on every FTPClient object created. If
	 * they don't work in your case (a FTPListParseException is thrown), you can
	 * build your own parser implementing the FTPListParser interface and add it
	 * to the client by calling its addListParser() method.
	 * 
	 * Calling this method blocks the current thread until the operation is
	 * completed. The operation could be interrupted by another thread calling
	 * abortCurrentDataTransfer(). The list() method will break with a
	 * FTPAbortedException.
	 * 
	 * @return The list of the files (and directories) in the current working
	 *         directory.
	 * @throws IllegalStateException
	 *             If the client is not connected or not authenticated.
	 * @throws IOException
	 *             If an I/O error occurs.
	 * @throws FTPIllegalReplyException
	 *             If the server replies in an illegal way.
	 * @throws FTPException
	 *             If the operation fails.
	 * @throws FTPDataTransferException
	 *             If a I/O occurs in the data transfer connection. If you
	 *             receive this exception the transfer failed, but the main
	 *             connection with the remote FTP server is in theory still
	 *             working.
	 * @throws FTPAbortedException
	 *             If operation is aborted by another thread.
	 * @throws FTPListParseException
	 *             If none of the registered parsers can handle the response
	 *             sent by the server.
	 * @see FTPListParser
	 * @see FTPClient#addListParser(FTPListParser)
	 * @see FTPClient#getListParsers()
	 * @see FTPClient#abortCurrentDataTransfer(boolean)
	 * @see FTPClient#listNames()
	 */
	public FTPFile[] list() throws IllegalStateException, IOException, FTPIllegalReplyException, FTPException,
			FTPDataTransferException, FTPAbortedException, FTPListParseException {
		return list(null);
	}
	/**
	 * This method lists the entries of the current working directory with a FTP
	 * NLST command.
	 * 
	 * The response consists in an array of string, each one reporting the name
	 * of a file or a directory placed in the current working directory. For a
	 * more detailed directory listing procedure look at the list() method.
	 * 
	 * Calling this method blocks the current thread until the operation is
	 * completed. The operation could be interrupted by another thread calling
	 * abortCurrentDataTransfer(). The listNames() method will break with a
	 * FTPAbortedException.
	 * 
	 * @return The list of the files (and directories) in the current working
	 *         directory.
	 * @throws IllegalStateException
	 *             If the client is not connected or not authenticated.
	 * @throws IOException
	 *             If an I/O error occurs.
	 * @throws FTPIllegalReplyException
	 *             If the server replies in an illegal way.
	 * @throws FTPException
	 *             If the operation fails.
	 * @throws FTPDataTransferException
	 *             If a I/O occurs in the data transfer connection. If you
	 *             receive this exception the transfer failed, but the main
	 *             connection with the remote FTP server is in theory still
	 *             working.
	 * @throws FTPAbortedException
	 *             If operation is aborted by another thread.
	 * @throws FTPListParseException
	 *             If none of the registered parsers can handle the response
	 *             sent by the server.
	 * @see FTPClient#abortCurrentDataTransfer(boolean)
	 * @see FTPClient#list()
	 */
	public String[] listNames() throws IllegalStateException, IOException, FTPIllegalReplyException, FTPException,
			FTPDataTransferException, FTPAbortedException, FTPListParseException {
		synchronized (lock) {
			// Is this client connected?
			if (!connected) {
				throw new IllegalStateException("Client not connected");
			}
			// Is this client authenticated?
			if (!authenticated) {
				throw new IllegalStateException("Client not authenticated");
			}
			// ASCII, please!
			communication.sendFTPCommand("TYPE A");
			FTPReply r = communication.readFTPReply();
			touchAutoNoopTimer();
			if (!r.isSuccessCode()) {
				throw new FTPException(r);
			}
			// Prepares the lines array.
			ArrayList lines = new ArrayList();
			// Local abort state.
			boolean wasAborted = false;
			// Prepares the connection for the data transfer.
			FTPDataTransferConnectionProvider provider = openDataTransferChannel();
			// Send the NLST command.
			communication.sendFTPCommand("NLST");
			try {
				Socket dtConnection;
				try {
					dtConnection = provider.openDataTransferConnection();
				} finally {
					provider.dispose();
				}
				// Change the operation status.
				synchronized (abortLock) {
					ongoingDataTransfer = true;
					aborted = false;
					consumeAborCommandReply = false;
				}
				// Fetch the list from the data transfer connection.
				NVTASCIIReader dataReader = null;
				try {
					// Opens the data transfer connection.
					dataTransferInputStream = dtConnection.getInputStream();
					// MODE Z enabled?
					if (modezEnabled) {
						dataTransferInputStream = new InflaterInputStream(dataTransferInputStream);
					}
					// Let's do it!
					dataReader = new NVTASCIIReader(dataTransferInputStream, pickCharset());
					String line;
					while ((line = dataReader.readLine()) != null) {
						if (line.length() > 0) {
							lines.add(line);
						}
					}
				} catch (IOException e) {
					synchronized (abortLock) {
						if (aborted) {
							throw new FTPAbortedException();
						} else {
							throw new FTPDataTransferException(
									"I/O error in data transfer", e);
						}
					}
				} finally {
					if (dataReader != null) {
						try {
							dataReader.close();
						} catch (Throwable t) {
							;
						}
					}
					try {
						dtConnection.close();
					} catch (Throwable t) {
						;
					}
					// Set to null the instance-level input stream.
					dataTransferInputStream = null;
					// Change the operation status.
					synchronized (abortLock) {
						wasAborted = aborted;
						ongoingDataTransfer = false;
						aborted = false;
					}
				}
			} finally {
				r = communication.readFTPReply();
				if (r.getCode() != 150 && r.getCode() != 125) {
					throw new FTPException(r);
				}
				// Consumes the result reply of the transfer.
				r = communication.readFTPReply();
				if (!wasAborted && r.getCode() != 226) {
					throw new FTPException(r);
				}
				// ABOR command response (if needed).
				if (consumeAborCommandReply) {
					communication.readFTPReply();
					consumeAborCommandReply = false;
				}
			}
			// Build an array.
			int size = lines.size();
			String[] list = new String[size];
			for (int i = 0; i < size; i++) {
				list[i] = (String) lines.get(i);
			}
			return list;
		}
	}
	/**
	 * This method uploads a file to the remote server.
	 * 
	 * Calling this method blocks the current thread until the operation is
	 * completed. The operation could be interrupted by another thread calling
	 * abortCurrentDataTransfer(). The method will break with a
	 * FTPAbortedException.
	 * 
	 * @param file
	 *            The file to upload.
	 * @throws IllegalStateException
	 *             If the client is not connected or not authenticated.
	 * @throws FileNotFoundException
	 *             If the supplied file cannot be found.
	 * @throws IOException
	 *             If an I/O error occurs.
	 * @throws FTPIllegalReplyException
	 *             If the server replies in an illegal way.
	 * @throws FTPException
	 *             If the operation fails.
	 * @throws FTPDataTransferException
	 *             If a I/O occurs in the data transfer connection. If you
	 *             receive this exception the transfer failed, but the main
	 *             connection with the remote FTP server is in theory still
	 *             working.
	 * @throws FTPAbortedException
	 *             If operation is aborted by another thread.
	 * @see FTPClient#abortCurrentDataTransfer(boolean)
	 */
	public void upload(File file) throws IllegalStateException,
			FileNotFoundException, IOException, FTPIllegalReplyException,
			FTPException, FTPDataTransferException, FTPAbortedException {
		upload(file, 0, null);
	}
	/**
	 * This method uploads a file to the remote server.
	 * 
	 * Calling this method blocks the current thread until the operation is
	 * completed. The operation could be interrupted by another thread calling
	 * abortCurrentDataTransfer(). The method will break with a
	 * FTPAbortedException.
	 * 
	 * @param file
	 *            The file to upload.
	 * @param listener
	 *            The listener for the operation. Could be null.
	 * @throws IllegalStateException
	 *             If the client is not connected or not authenticated.
	 * @throws FileNotFoundException
	 *             If the supplied file cannot be found.
	 * @throws IOException
	 *             If an I/O error occurs.
	 * @throws FTPIllegalReplyException
	 *             If the server replies in an illegal way.
	 * @throws FTPException
	 *             If the operation fails.
	 * @throws FTPDataTransferException
	 *             If a I/O occurs in the data transfer connection. If you
	 *             receive this exception the transfer failed, but the main
	 *             connection with the remote FTP server is in theory still
	 *             working.
	 * @throws FTPAbortedException
	 *             If operation is aborted by another thread.
	 * @see FTPClient#abortCurrentDataTransfer(boolean)
	 */
	public void upload(File file, FTPDataTransferListener listener)
			throws IllegalStateException, FileNotFoundException, IOException,
			FTPIllegalReplyException, FTPException, FTPDataTransferException,
			FTPAbortedException {
		upload(file, 0, listener);
	}
	/**
	 * This method uploads a file to the remote server.
	 * 
	 * Calling this method blocks the current thread until the operation is
	 * completed. The operation could be interrupted by another thread calling
	 * abortCurrentDataTransfer(). The method will break with a
	 * FTPAbortedException.
	 * 
	 * @param file
	 *            The file to upload.
	 * @param restartAt
	 *            The restart point (number of bytes already uploaded). Use
	 *            {@link FTPClient#isResumeSupported()} to check if the server
	 *            supports resuming of broken data transfers.
	 * @throws IllegalStateException
	 *             If the client is not connected or not authenticated.
	 * @throws FileNotFoundException
	 *             If the supplied file cannot be found.
	 * @throws IOException
	 *             If an I/O error occurs.
	 * @throws FTPIllegalReplyException
	 *             If the server replies in an illegal way.
	 * @throws FTPException
	 *             If the operation fails.
	 * @throws FTPDataTransferException
	 *             If a I/O occurs in the data transfer connection. If you
	 *             receive this exception the transfer failed, but the main
	 *             connection with the remote FTP server is in theory still
	 *             working.
	 * @throws FTPAbortedException
	 *             If operation is aborted by another thread.
	 * @see FTPClient#abortCurrentDataTransfer(boolean)
	 */
	public void upload(File file, long restartAt) throws IllegalStateException,
			FileNotFoundException, IOException, FTPIllegalReplyException,
			FTPException, FTPDataTransferException, FTPAbortedException {
		upload(file, restartAt, null);
	}
	/**
	 * This method uploads a file to the remote server.
	 * 
	 * Calling this method blocks the current thread until the operation is
	 * completed. The operation could be interrupted by another thread calling
	 * abortCurrentDataTransfer(). The method will break with a
	 * FTPAbortedException.
	 * 
	 * @param file
	 *            The file to upload.
	 * @param restartAt
	 *            The restart point (number of bytes already uploaded). Use
	 *            {@link FTPClient#isResumeSupported()} to check if the server
	 *            supports resuming of broken data transfers.
	 * @param listener
	 *            The listener for the operation. Could be null.
	 * @throws IllegalStateException
	 *             If the client is not connected or not authenticated.
	 * @throws FileNotFoundException
	 *             If the supplied file cannot be found.
	 * @throws IOException
	 *             If an I/O error occurs.
	 * @throws FTPIllegalReplyException
	 *             If the server replies in an illegal way.
	 * @throws FTPException
	 *             If the operation fails.
	 * @throws FTPDataTransferException
	 *             If a I/O occurs in the data transfer connection. If you
	 *             receive this exception the transfer failed, but the main
	 *             connection with the remote FTP server is in theory still
	 *             working.
	 * @throws FTPAbortedException
	 *             If operation is aborted by another thread.
	 * @see FTPClient#abortCurrentDataTransfer(boolean)
	 */
	public void upload(File file, long restartAt,
			FTPDataTransferListener listener) throws IllegalStateException,
			FileNotFoundException, IOException, FTPIllegalReplyException,
			FTPException, FTPDataTransferException, FTPAbortedException {
		if (!file.exists()) {
			throw new FileNotFoundException(file.getAbsolutePath());
		}
		InputStream inputStream = null;
		try {
			inputStream = new FileInputStream(file);
		} catch (IOException e) {
			throw new FTPDataTransferException(e);
		}
		try {
			upload(file.getName(), inputStream, restartAt, restartAt, listener);
		} catch (IllegalStateException e) {
			throw e;
		} catch (IOException e) {
			throw e;
		} catch (FTPIllegalReplyException e) {
			throw e;
		} catch (FTPException e) {
			throw e;
		} catch (FTPDataTransferException e) {
			throw e;
		} catch (FTPAbortedException e) {
			throw e;
		} finally {
			if (inputStream != null) {
				try {
					inputStream.close();
				} catch (Throwable t) {
					;
				}
			}
		}
	}
	/**
	 * This method uploads a content to the remote server.
	 * 
	 * Calling this method blocks the current thread until the operation is
	 * completed. The operation could be interrupted by another thread calling
	 * abortCurrentDataTransfer(). The method will break with a
	 * FTPAbortedException.
	 * 
	 * @param fileName
	 *            The name of the remote file.
	 * @param inputStream
	 *            The source of data.
	 * @param restartAt
	 *            The restart point (number of bytes already uploaded). Use
	 *            {@link FTPClient#isResumeSupported()} to check if the server
	 *            supports resuming of broken data transfers.
	 * @param streamOffset
	 *            The offset to skip in the stream.
	 * @param listener
	 *            The listener for the operation. Could be null.
	 * @throws IllegalStateException
	 *             If the client is not connected or not authenticated.
	 * @throws IOException
	 *             If an I/O error occurs.
	 * @throws FTPIllegalReplyException
	 *             If the server replies in an illegal way.
	 * @throws FTPException
	 *             If the operation fails.
	 * @throws FTPDataTransferException
	 *             If a I/O occurs in the data transfer connection. If you
	 *             receive this exception the transfer failed, but the main
	 *             connection with the remote FTP server is in theory still
	 *             working.
	 * @throws FTPAbortedException
	 *             If operation is aborted by another thread.
	 * @see FTPClient#abortCurrentDataTransfer(boolean)
	 */
	public void upload(String fileName, InputStream inputStream,
			long restartAt, long streamOffset, FTPDataTransferListener listener)
			throws IllegalStateException, IOException,
			FTPIllegalReplyException, FTPException, FTPDataTransferException,
			FTPAbortedException {
		synchronized (lock) {
			// Is this client connected?
			if (!connected) {
				throw new IllegalStateException("Client not connected");
			}
			// Is this client authenticated?
			if (!authenticated) {
				throw new IllegalStateException("Client not authenticated");
			}
			// Select the type of contents.
			int tp = type;
			if (tp == TYPE_AUTO) {
				tp = detectType(fileName);
			}
			if (tp == TYPE_TEXTUAL) {
				communication.sendFTPCommand("TYPE A");
			} else if (tp == TYPE_BINARY) {
				communication.sendFTPCommand("TYPE I");
			}
			FTPReply r = communication.readFTPReply();
			touchAutoNoopTimer();
			if (!r.isSuccessCode()) {
				throw new FTPException(r);
			}
			// Prepares the connection for the data transfer.
			FTPDataTransferConnectionProvider provider = openDataTransferChannel();
			// REST command (if supported and/or requested).
			if (restSupported || restartAt > 0) {
				boolean done = false;
				try {
					communication.sendFTPCommand("REST " + restartAt);
					r = communication.readFTPReply();
					touchAutoNoopTimer();
					if (r.getCode() != 350 && ((r.getCode() != 501 && r.getCode() != 502) || restartAt > 0)) {
						throw new FTPException(r);
					}
					done = true;
				} finally {
					if (!done) {
						provider.dispose();
					}
				}
			}
			// Local abort state.
			boolean wasAborted = false;
			// Send the STOR command.
			communication.sendFTPCommand("STOR " + fileName);
			try {
				Socket dtConnection;
				try {
					dtConnection = provider.openDataTransferConnection();
				} finally {
					provider.dispose();
				}
				// Change the operation status.
				synchronized (abortLock) {
					ongoingDataTransfer = true;
					aborted = false;
					consumeAborCommandReply = false;
				}
				// Upload the stream.
				try {
					// Skips.
					inputStream.skip(streamOffset);
					// Opens the data transfer connection.
					dataTransferOutputStream = dtConnection.getOutputStream();
					// MODE Z enabled?
					if (modezEnabled) {
						dataTransferOutputStream = new DeflaterOutputStream(dataTransferOutputStream);
					}
					// Listeners.
					if (listener != null) {
						listener.started();
					}
					// Let's do it!
					if (tp == TYPE_TEXTUAL) {
						Reader reader = new InputStreamReader(inputStream);
						Writer writer = new OutputStreamWriter(
								dataTransferOutputStream, pickCharset());
						char[] buffer = new char[SEND_AND_RECEIVE_BUFFER_SIZE];
						int l;
						while ((l = reader.read(buffer)) != -1) {
							writer.write(buffer, 0, l);
							writer.flush();
							if (listener != null) {
								listener.transferred(l);
							}
						}
					} else if (tp == TYPE_BINARY) {
						byte[] buffer = new byte[SEND_AND_RECEIVE_BUFFER_SIZE];
						int l;
						while ((l = inputStream.read(buffer)) != -1) {
							dataTransferOutputStream.write(buffer, 0, l);
							dataTransferOutputStream.flush();
							if (listener != null) {
								listener.transferred(l);
							}
						}
					}
				} catch (IOException e) {
					synchronized (abortLock) {
						if (aborted) {
							if (listener != null) {
								listener.aborted();
							}
							throw new FTPAbortedException();
						} else {
							if (listener != null) {
								listener.failed();
							}
							throw new FTPDataTransferException(
									"I/O error in data transfer", e);
						}
					}
				} finally {
					// Closing stream and data connection.
					if (dataTransferOutputStream != null) {
						try {
							dataTransferOutputStream.close();
						} catch (Throwable t) {
							;
						}
					}
					try {
						dtConnection.close();
					} catch (Throwable t) {
						;
					}
					// Set to null the instance-level input stream.
					dataTransferOutputStream = null;
					// Change the operation status.
					synchronized (abortLock) {
						wasAborted = aborted;
						ongoingDataTransfer = false;
						aborted = false;
					}
				}
			} finally {
				// Data transfer command reply.
				r = communication.readFTPReply();
				touchAutoNoopTimer();
				if (r.getCode() != 150 && r.getCode() != 125) {
					throw new FTPException(r);
				}
				// Consumes the result reply of the transfer.
				r = communication.readFTPReply();
				if (!wasAborted && r.getCode() != 226) {
					throw new FTPException(r);
				}
				// ABOR command response (if needed).
				if (consumeAborCommandReply) {
					communication.readFTPReply();
					consumeAborCommandReply = false;
				}
			}
			// Listener notification.
			if (listener != null) {
				listener.completed();
			}
		}
	}
	/**
	 * This method appends the contents of a local file to an existing file on
	 * the remote server.
	 * 
	 * Calling this method blocks the current thread until the operation is
	 * completed. The operation could be interrupted by another thread calling
	 * abortCurrentDataTransfer(). The method will break with a
	 * FTPAbortedException.
	 * 
	 * @param file
	 *            The local file whose contents will be appended to the remote
	 *            file.
	 * @throws IllegalStateException
	 *             If the client is not connected or not authenticated.
	 * @throws FileNotFoundException
	 *             If the supplied file cannot be found.
	 * @throws IOException
	 *             If an I/O error occurs.
	 * @throws FTPIllegalReplyException
	 *             If the server replies in an illegal way.
	 * @throws FTPException
	 *             If the operation fails.
	 * @throws FTPDataTransferException
	 *             If a I/O occurs in the data transfer connection. If you
	 *             receive this exception the transfer failed, but the main
	 *             connection with the remote FTP server is in theory still
	 *             working.
	 * @throws FTPAbortedException
	 *             If operation is aborted by another thread.
	 * @see FTPClient#abortCurrentDataTransfer(boolean)
	 * @since 1.6
	 */
	public void append(File file) throws IllegalStateException,
			FileNotFoundException, IOException, FTPIllegalReplyException,
			FTPException, FTPDataTransferException, FTPAbortedException {
		append(file, null);
	}
	/**
	 * This method uploads a file to the remote server.
	 * 
	 * Calling this method blocks the current thread until the operation is
	 * completed. The operation could be interrupted by another thread calling
	 * abortCurrentDataTransfer(). The method will break with a
	 * FTPAbortedException.
	 * 
	 * @param file
	 *            The local file whose contents will be appended to the remote
	 *            file.
	 * @param listener
	 *            The listener for the operation. Could be null.
	 * @throws IllegalStateException
	 *             If the client is not connected or not authenticated.
	 * @throws FileNotFoundException
	 *             If the supplied file cannot be found.
	 * @throws IOException
	 *             If an I/O error occurs.
	 * @throws FTPIllegalReplyException
	 *             If the server replies in an illegal way.
	 * @throws FTPException
	 *             If the operation fails.
	 * @throws FTPDataTransferException
	 *             If a I/O occurs in the data transfer connection. If you
	 *             receive this exception the transfer failed, but the main
	 *             connection with the remote FTP server is in theory still
	 *             working.
	 * @throws FTPAbortedException
	 *             If operation is aborted by another thread.
	 * @see FTPClient#abortCurrentDataTransfer(boolean)
	 * @since 1.6
	 */
	public void append(File file, FTPDataTransferListener listener)
			throws IllegalStateException, FileNotFoundException, IOException,
			FTPIllegalReplyException, FTPException, FTPDataTransferException,
			FTPAbortedException {
		if (!file.exists()) {
			throw new FileNotFoundException(file.getAbsolutePath());
		}
		InputStream inputStream = null;
		try {
			inputStream = new FileInputStream(file);
		} catch (IOException e) {
			throw new FTPDataTransferException(e);
		}
		try {
			append(file.getName(), inputStream, 0, listener);
		} catch (IllegalStateException e) {
			throw e;
		} catch (IOException e) {
			throw e;
		} catch (FTPIllegalReplyException e) {
			throw e;
		} catch (FTPException e) {
			throw e;
		} catch (FTPDataTransferException e) {
			throw e;
		} catch (FTPAbortedException e) {
			throw e;
		} finally {
			if (inputStream != null) {
				try {
					inputStream.close();
				} catch (Throwable t) {
					;
				}
			}
		}
	}
	/**
	 * This method appends data to an existing file on the remote server.
	 * 
	 * Calling this method blocks the current thread until the operation is
	 * completed. The operation could be interrupted by another thread calling
	 * abortCurrentDataTransfer(). The method will break with a
	 * FTPAbortedException.
	 * 
	 * @param fileName
	 *            The name of the remote file.
	 * @param inputStream
	 *            The source of data.
	 * @param streamOffset
	 *            The offset to skip in the stream.
	 * @param listener
	 *            The listener for the operation. Could be null.
	 * @throws IllegalStateException
	 *             If the client is not connected or not authenticated.
	 * @throws IOException
	 *             If an I/O error occurs.
	 * @throws FTPIllegalReplyException
	 *             If the server replies in an illegal way.
	 * @throws FTPException
	 *             If the operation fails.
	 * @throws FTPDataTransferException
	 *             If a I/O occurs in the data transfer connection. If you
	 *             receive this exception the transfer failed, but the main
	 *             connection with the remote FTP server is in theory still
	 *             working.
	 * @throws FTPAbortedException
	 *             If operation is aborted by another thread.
	 * @see FTPClient#abortCurrentDataTransfer(boolean)
	 * @since 1.6
	 */
	public void append(String fileName, InputStream inputStream,
			long streamOffset, FTPDataTransferListener listener)
			throws IllegalStateException, IOException,
			FTPIllegalReplyException, FTPException, FTPDataTransferException,
			FTPAbortedException {
		synchronized (lock) {
			// Is this client connected?
			if (!connected) {
				throw new IllegalStateException("Client not connected");
			}
			// Is this client authenticated?
			if (!authenticated) {
				throw new IllegalStateException("Client not authenticated");
			}
			// Select the type of contents.
			int tp = type;
			if (tp == TYPE_AUTO) {
				tp = detectType(fileName);
			}
			if (tp == TYPE_TEXTUAL) {
				communication.sendFTPCommand("TYPE A");
			} else if (tp == TYPE_BINARY) {
				communication.sendFTPCommand("TYPE I");
			}
			FTPReply r = communication.readFTPReply();
			touchAutoNoopTimer();
			if (!r.isSuccessCode()) {
				throw new FTPException(r);
			}
			// Local abort state.
			boolean wasAborted = false;
			// Prepares the connection for the data transfer.
			FTPDataTransferConnectionProvider provider = openDataTransferChannel();
			// Send the STOR command.
			communication.sendFTPCommand("APPE " + fileName);
			try {
				Socket dtConnection;
				try {
					dtConnection = provider.openDataTransferConnection();
				} finally {
					provider.dispose();
				}
				// Change the operation status.
				synchronized (abortLock) {
					ongoingDataTransfer = true;
					aborted = false;
					consumeAborCommandReply = false;
				}
				// Upload the stream.
				try {
					// Skips.
					inputStream.skip(streamOffset);
					// Opens the data transfer connection.
					dataTransferOutputStream = dtConnection.getOutputStream();
					// MODE Z enabled?
					if (modezEnabled) {
						dataTransferOutputStream = new DeflaterOutputStream(dataTransferOutputStream);
					}
					// Listeners.
					if (listener != null) {
						listener.started();
					}
					// Let's do it!
					if (tp == TYPE_TEXTUAL) {
						Reader reader = new InputStreamReader(inputStream);
						Writer writer = new OutputStreamWriter(
								dataTransferOutputStream, pickCharset());
						char[] buffer = new char[SEND_AND_RECEIVE_BUFFER_SIZE];
						int l;
						while ((l = reader.read(buffer)) != -1) {
							writer.write(buffer, 0, l);
							writer.flush();
							if (listener != null) {
								listener.transferred(l);
							}
						}
					} else if (tp == TYPE_BINARY) {
						byte[] buffer = new byte[SEND_AND_RECEIVE_BUFFER_SIZE];
						int l;
						while ((l = inputStream.read(buffer)) != -1) {
							dataTransferOutputStream.write(buffer, 0, l);
							dataTransferOutputStream.flush();
							if (listener != null) {
								listener.transferred(l);
							}
						}
					}
				} catch (IOException e) {
					synchronized (abortLock) {
						if (aborted) {
							if (listener != null) {
								listener.aborted();
							}
							throw new FTPAbortedException();
						} else {
							if (listener != null) {
								listener.failed();
							}
							throw new FTPDataTransferException(
									"I/O error in data transfer", e);
						}
					}
				} finally {
					// Closing stream and data connection.
					if (dataTransferOutputStream != null) {
						try {
							dataTransferOutputStream.close();
						} catch (Throwable t) {
							;
						}
					}
					try {
						dtConnection.close();
					} catch (Throwable t) {
						;
					}
					// Set to null the instance-level input stream.
					dataTransferOutputStream = null;
					// Change the operation status.
					synchronized (abortLock) {
						wasAborted = aborted;
						ongoingDataTransfer = false;
						aborted = false;
					}
				}
			} finally {
				r = communication.readFTPReply();
				touchAutoNoopTimer();
				if (r.getCode() != 150 && r.getCode() != 125) {
					throw new FTPException(r);
				}
				// Consumes the result reply of the transfer.
				r = communication.readFTPReply();
				if (!wasAborted && r.getCode() != 226) {
					throw new FTPException(r);
				}
				// ABOR command response (if needed).
				if (consumeAborCommandReply) {
					communication.readFTPReply();
					consumeAborCommandReply = false;
				}
			}
			// Notifies the listener.
			if (listener != null) {
				listener.completed();
			}
		}
	}
	/**
	 * This method downloads a remote file from the server to a local file.
	 * 
	 * Calling this method blocks the current thread until the operation is
	 * completed. The operation could be interrupted by another thread calling
	 * abortCurrentDataTransfer(). The method will break with a
	 * FTPAbortedException.
	 * 
	 * @param remoteFileName
	 *            The name of the file to download.
	 * @param localFile
	 *            The local file.
	 * @throws IllegalStateException
	 *             If the client is not connected or not authenticated.
	 * @throws FileNotFoundException
	 *             If the supplied file cannot be found.
	 * @throws IOException
	 *             If an I/O error occurs.
	 * @throws FTPIllegalReplyException
	 *             If the server replies in an illegal way.
	 * @throws FTPException
	 *             If the operation fails.
	 * @throws FTPDataTransferException
	 *             If a I/O occurs in the data transfer connection. If you
	 *             receive this exception the transfer failed, but the main
	 *             connection with the remote FTP server is in theory still
	 *             working.
	 * @throws FTPAbortedException
	 *             If operation is aborted by another thread.
	 * @see FTPClient#abortCurrentDataTransfer(boolean)
	 */
	public void download(String remoteFileName, File localFile)
			throws IllegalStateException, FileNotFoundException, IOException,
			FTPIllegalReplyException, FTPException, FTPDataTransferException,
			FTPAbortedException {
		download(remoteFileName, localFile, 0, null);
	}
	/**
	 * This method downloads a remote file from the server to a local file.
	 * 
	 * Calling this method blocks the current thread until the operation is
	 * completed. The operation could be interrupted by another thread calling
	 * abortCurrentDataTransfer(). The method will break with a
	 * FTPAbortedException.
	 * 
	 * @param remoteFileName
	 *            The name of the file to download.
	 * @param localFile
	 *            The local file.
	 * @param listener
	 *            The listener for the operation. Could be null.
	 * @throws IllegalStateException
	 *             If the client is not connected or not authenticated.
	 * @throws FileNotFoundException
	 *             If the supplied file cannot be found.
	 * @throws IOException
	 *             If an I/O error occurs.
	 * @throws FTPIllegalReplyException
	 *             If the server replies in an illegal way.
	 * @throws FTPException
	 *             If the operation fails.
	 * @throws FTPDataTransferException
	 *             If a I/O occurs in the data transfer connection. If you
	 *             receive this exception the transfer failed, but the main
	 *             connection with the remote FTP server is in theory still
	 *             working.
	 * @throws FTPAbortedException
	 *             If operation is aborted by another thread.
	 * @see FTPClient#abortCurrentDataTransfer(boolean)
	 */
	public void download(String remoteFileName, File localFile,
			FTPDataTransferListener listener) throws IllegalStateException,
			FileNotFoundException, IOException, FTPIllegalReplyException,
			FTPException, FTPDataTransferException, FTPAbortedException {
		download(remoteFileName, localFile, 0, listener);
	}
	/**
	 * This method resumes a download operation from the remote server to a
	 * local file.
	 * 
	 * Calling this method blocks the current thread until the operation is
	 * completed. The operation could be interrupted by another thread calling
	 * abortCurrentDataTransfer(). The method will break with a
	 * FTPAbortedException.
	 * 
	 * @param remoteFileName
	 *            The name of the file to download.
	 * @param localFile
	 *            The local file.
	 * @param restartAt
	 *            The restart point (number of bytes already downloaded). Use
	 *            {@link FTPClient#isResumeSupported()} to check if the server
	 *            supports resuming of broken data transfers.
	 * @throws IllegalStateException
	 *             If the client is not connected or not authenticated.
	 * @throws FileNotFoundException
	 *             If the supplied file cannot be found.
	 * @throws IOException
	 *             If an I/O error occurs.
	 * @throws FTPIllegalReplyException
	 *             If the server replies in an illegal way.
	 * @throws FTPException
	 *             If the operation fails.
	 * @throws FTPDataTransferException
	 *             If a I/O occurs in the data transfer connection. If you
	 *             receive this exception the transfer failed, but the main
	 *             connection with the remote FTP server is in theory still
	 *             working.
	 * @throws FTPAbortedException
	 *             If operation is aborted by another thread.
	 * @see FTPClient#abortCurrentDataTransfer(boolean)
	 */
	public void download(String remoteFileName, File localFile, long restartAt)
			throws IllegalStateException, FileNotFoundException, IOException,
			FTPIllegalReplyException, FTPException, FTPDataTransferException,
			FTPAbortedException {
		download(remoteFileName, localFile, restartAt, null);
	}
	/**
	 * This method resumes a download operation from the remote server to a
	 * local file.
	 * 
	 * Calling this method blocks the current thread until the operation is
	 * completed. The operation could be interrupted by another thread calling
	 * abortCurrentDataTransfer(). The method will break with a
	 * FTPAbortedException.
	 * 
	 * @param remoteFileName
	 *            The name of the file to download.
	 * @param localFile
	 *            The local file.
	 * @param restartAt
	 *            The restart point (number of bytes already downloaded). Use
	 *            {@link FTPClient#isResumeSupported()} to check if the server
	 *            supports resuming of broken data transfers.
	 * @param listener
	 *            The listener for the operation. Could be null.
	 * @throws IllegalStateException
	 *             If the client is not connected or not authenticated.
	 * @throws FileNotFoundException
	 *             If the supplied file cannot be found.
	 * @throws IOException
	 *             If an I/O error occurs.
	 * @throws FTPIllegalReplyException
	 *             If the server replies in an illegal way.
	 * @throws FTPException
	 *             If the operation fails.
	 * @throws FTPDataTransferException
	 *             If a I/O occurs in the data transfer connection. If you
	 *             receive this exception the transfer failed, but the main
	 *             connection with the remote FTP server is in theory still
	 *             working.
	 * @throws FTPAbortedException
	 *             If operation is aborted by another thread.
	 * @see FTPClient#abortCurrentDataTransfer(boolean)
	 */
	public void download(String remoteFileName, File localFile, long restartAt,
			FTPDataTransferListener listener) throws IllegalStateException,
			FileNotFoundException, IOException, FTPIllegalReplyException,
			FTPException, FTPDataTransferException, FTPAbortedException {
		OutputStream outputStream = null;
		try {
			outputStream = new FileOutputStream(localFile, restartAt > 0);
		} catch (IOException e) {
			throw new FTPDataTransferException(e);
		}
		try {
			download(remoteFileName, outputStream, restartAt, listener);
		} catch (IllegalStateException e) {
			throw e;
		} catch (IOException e) {
			throw e;
		} catch (FTPIllegalReplyException e) {
			throw e;
		} catch (FTPException e) {
			throw e;
		} catch (FTPDataTransferException e) {
			throw e;
		} catch (FTPAbortedException e) {
			throw e;
		} finally {
			if (outputStream != null) {
				try {
					outputStream.close();
				} catch (Throwable t) {
					;
				}
			}
		}
	}
	/**
	 * This method resumes a download operation from the remote server.
	 * 
	 * Calling this method blocks the current thread until the operation is
	 * completed. The operation could be interrupted by another thread calling
	 * abortCurrentDataTransfer(). The method will break with a
	 * FTPAbortedException.
	 * 
	 * @param fileName
	 *            The name of the remote file.
	 * @param outputStream
	 *            The destination stream of data read during the download.
	 * @param restartAt
	 *            The restart point (number of bytes already downloaded). Use
	 *            {@link FTPClient#isResumeSupported()} to check if the server
	 *            supports resuming of broken data transfers.
	 * @param listener
	 *            The listener for the operation. Could be null.
	 * @throws IllegalStateException
	 *             If the client is not connected or not authenticated.
	 * @throws IOException
	 *             If an I/O error occurs.
	 * @throws FTPIllegalReplyException
	 *             If the server replies in an illegal way.
	 * @throws FTPException
	 *             If the operation fails.
	 * @throws FTPDataTransferException
	 *             If a I/O occurs in the data transfer connection. If you
	 *             receive this exception the transfer failed, but the main
	 *             connection with the remote FTP server is in theory still
	 *             working.
	 * @throws FTPAbortedException
	 *             If operation is aborted by another thread.
	 * @see FTPClient#abortCurrentDataTransfer(boolean)
	 */
	public void download(String fileName, OutputStream outputStream,
			long restartAt, FTPDataTransferListener listener)
			throws IllegalStateException, IOException,
			FTPIllegalReplyException, FTPException, FTPDataTransferException,
			FTPAbortedException {
		synchronized (lock) {
			// Is this client connected?
			if (!connected) {
				throw new IllegalStateException("Client not connected");
			}
			// Is this client authenticated?
			if (!authenticated) {
				throw new IllegalStateException("Client not authenticated");
			}
			// Select the type of contents.
			int tp = type;
			if (tp == TYPE_AUTO) {
				tp = detectType(fileName);
			}
			if (tp == TYPE_TEXTUAL) {
				communication.sendFTPCommand("TYPE A");
			} else if (tp == TYPE_BINARY) {
				communication.sendFTPCommand("TYPE I");
			}
			FTPReply r = communication.readFTPReply();
			touchAutoNoopTimer();
			if (!r.isSuccessCode()) {
				throw new FTPException(r);
			}
			// Prepares the connection for the data transfer.
			FTPDataTransferConnectionProvider provider = openDataTransferChannel();
			// REST command (if supported and/or requested).
			if (restSupported || restartAt > 0) {
				boolean done = false;
				try {
					communication.sendFTPCommand("REST " + restartAt);
					r = communication.readFTPReply();
					touchAutoNoopTimer();
					if (r.getCode() != 350 && ((r.getCode() != 501 && r.getCode() != 502) || restartAt > 0)) {
						throw new FTPException(r);
					}
					done = true;
				} finally {
					if (!done) {
						provider.dispose();
					}
				}
			}
			// Local abort state.
			boolean wasAborted = false;
			// Send the RETR command.
			communication.sendFTPCommand("RETR " + fileName);
			try {
				Socket dtConnection;
				try {
					dtConnection = provider.openDataTransferConnection();
				} finally {
					provider.dispose();
				}
				// Change the operation status.
				synchronized (abortLock) {
					ongoingDataTransfer = true;
					aborted = false;
					consumeAborCommandReply = false;
				}
				// Download the stream.
				try {
					// Opens the data transfer connection.
					dataTransferInputStream = dtConnection.getInputStream();
					// MODE Z enabled?
					if (modezEnabled) {
						dataTransferInputStream = new InflaterInputStream(dataTransferInputStream);
					}
					// Listeners.
					if (listener != null) {
						listener.started();
					}
					// Let's do it!
					if (tp == TYPE_TEXTUAL) {
						Reader reader = new InputStreamReader(
								dataTransferInputStream, pickCharset());
						Writer writer = new OutputStreamWriter(outputStream);
						char[] buffer = new char[SEND_AND_RECEIVE_BUFFER_SIZE];
						int l;
						while ((l = reader.read(buffer, 0, buffer.length)) != -1) {
							writer.write(buffer, 0, l);
							writer.flush();
							if (listener != null) {
								listener.transferred(l);
							}
						}
					} else if (tp == TYPE_BINARY) {
						byte[] buffer = new byte[SEND_AND_RECEIVE_BUFFER_SIZE];
						int l;
						while ((l = dataTransferInputStream.read(buffer, 0,
								buffer.length)) != -1) {
							outputStream.write(buffer, 0, l);
							if (listener != null) {
								listener.transferred(l);
							}
						}
					}
				} catch (IOException e) {
					synchronized (abortLock) {
						if (aborted) {
							if (listener != null) {
								listener.aborted();
							}
							throw new FTPAbortedException();
						} else {
							if (listener != null) {
								listener.failed();
							}
							throw new FTPDataTransferException(
									"I/O error in data transfer", e);
						}
					}
				} finally {
					// Closing stream and data connection.
					if (dataTransferInputStream != null) {
						try {
							dataTransferInputStream.close();
						} catch (Throwable t) {
							;
						}
					}
					try {
						dtConnection.close();
					} catch (Throwable t) {
						;
					}
					// Set to null the instance-level input stream.
					dataTransferInputStream = null;
					// Change the operation status.
					synchronized (abortLock) {
						wasAborted = aborted;
						ongoingDataTransfer = false;
						aborted = false;
					}
				}
			} finally {
				r = communication.readFTPReply();
				touchAutoNoopTimer();
				if (r.getCode() != 150 && r.getCode() != 125) {
					throw new FTPException(r);
				}
				// Consumes the result reply of the transfer.
				r = communication.readFTPReply();
				if (!wasAborted && r.getCode() != 226) {
					throw new FTPException(r);
				}
				// ABOR command response (if needed).
				if (consumeAborCommandReply) {
					communication.readFTPReply();
					consumeAborCommandReply = false;
				}
			}
			// Notifies the listener.
			if (listener != null) {
				listener.completed();
			}
		}
	}
	/**
	 * This method detects the type for a file transfer.
	 */
	private int detectType(String fileName) throws IOException,
			FTPIllegalReplyException, FTPException {
		int start = fileName.lastIndexOf('.') + 1;
		int stop = fileName.length();
		if (start > 0 && start < stop - 1) {
			String ext = fileName.substring(start, stop);
			ext = ext.toLowerCase();
			if (textualExtensionRecognizer.isTextualExt(ext)) {
				return TYPE_TEXTUAL;
			} else {
				return TYPE_BINARY;
			}
		} else {
			return TYPE_BINARY;
		}
	}
	/**
	 * This method opens a data transfer channel.
	 */
	private FTPDataTransferConnectionProvider openDataTransferChannel()
			throws IOException, FTPIllegalReplyException, FTPException,
			FTPDataTransferException {
		// MODE Z?
		if (modezSupported && compressionEnabled) {
			if (!modezEnabled) {
				// Sends the MODE Z command.
				communication.sendFTPCommand("MODE Z");
				FTPReply r = communication.readFTPReply();
				touchAutoNoopTimer();
				if (r.isSuccessCode()) {
					modezEnabled = true;
				}
			}
		} else {
			if (modezEnabled) {
				// Sends the MODE S command.
				communication.sendFTPCommand("MODE S");
				FTPReply r = communication.readFTPReply();
				touchAutoNoopTimer();
				if (r.isSuccessCode()) {
					modezEnabled = false;
				}
			}
		}
		// Active or passive?
		if (passive) {
			return openPassiveDataTransferChannel();
		} else {
			return openActiveDataTransferChannel();
		}
	}
	/**
	 * This method opens a data transfer channel in active mode.
	 */
	private FTPDataTransferConnectionProvider openActiveDataTransferChannel()
			throws IOException, FTPIllegalReplyException, FTPException,
			FTPDataTransferException {
		// Create a FTPDataTransferServer object.
		FTPDataTransferServer server = new FTPDataTransferServer() {
			public Socket openDataTransferConnection()
					throws FTPDataTransferException {
				Socket socket = super.openDataTransferConnection();
				if (dataChannelEncrypted) {
					try {
						socket = ssl(socket, socket.getInetAddress().getHostName(), socket.getPort());
					} catch (IOException e) {
						try {
							socket.close();
						} catch (Throwable t) {
						}
						throw new FTPDataTransferException(e);
					}
				}
				return socket;
			}
		};
		int port = server.getPort();
		int p1 = port >>> 8;
		int p2 = port & 0xff;
		int[] addr = pickLocalAddress();
		// Send the port command.
		communication.sendFTPCommand("PORT " + addr[0] + "," + addr[1] + "," + addr[2] + "," +
				addr[3] + "," + p1 + "," + p2);
		FTPReply r = communication.readFTPReply();
		touchAutoNoopTimer();
		if (!r.isSuccessCode()) {
			// Disposes.
			server.dispose();
			// Closes the already open connection (if any).
			try {
				Socket aux = server.openDataTransferConnection();
				aux.close();
			} catch (Throwable t) {
				;
			}
			// Throws the exception.
			throw new FTPException(r);
		}
		return server;
	}
	/**
	 * This method opens a data transfer channel in passive mode.
	 */
	private FTPDataTransferConnectionProvider openPassiveDataTransferChannel()
			throws IOException, FTPIllegalReplyException, FTPException,
			FTPDataTransferException {
		// Send the PASV command.
		communication.sendFTPCommand("PASV");
		// Read the reply.
		FTPReply r = communication.readFTPReply();
		touchAutoNoopTimer();
		if (!r.isSuccessCode()) {
			throw new FTPException(r);
		}
		// Use a regexp to extract the remote address and port.
		String addressAndPort = null;
		String[] messages = r.getMessages();
		for (int i = 0; i < messages.length; i++) {
			Matcher m = PASV_PATTERN.matcher(messages[i]);
			if (m.find()) {
				int start = m.start();
				int end = m.end();
				addressAndPort = messages[i].substring(start, end);
				break;
			}
		}
		if (addressAndPort == null) {
			// The remote server has not sent the coordinates for the
			// data transfer connection.
			throw new FTPIllegalReplyException();
		}
		// Parse the string extracted from the reply.
		StringTokenizer st = new StringTokenizer(addressAndPort, ",");
		int b1 = Integer.parseInt(st.nextToken());
		int b2 = Integer.parseInt(st.nextToken());
		int b3 = Integer.parseInt(st.nextToken());
		int b4 = Integer.parseInt(st.nextToken());
		int p1 = Integer.parseInt(st.nextToken());
		int p2 = Integer.parseInt(st.nextToken());
		final String pasvHost = b1 + "." + b2 + "." + b3 + "." + b4;
		final int pasvPort = (p1 << 8) | p2;
		FTPDataTransferConnectionProvider provider = new FTPDataTransferConnectionProvider() {
			public Socket openDataTransferConnection() throws FTPDataTransferException {
				// Establish the connection.
				Socket dtConnection;
				try {
					String selectedHost = connector.getUseSuggestedAddressForDataConnections() ? pasvHost : host;
					dtConnection = connector.connectForDataTransferChannel(selectedHost, pasvPort);
					if (dataChannelEncrypted) {
						dtConnection = ssl(dtConnection, selectedHost, pasvPort);
					}
				} catch (IOException e) {
					throw new FTPDataTransferException("Cannot connect to the remote server", e);
				}
				return dtConnection;
			}
			public void dispose() {
				// nothing to do
			}
		};
		return provider;
	}
	/**
	 * If there's any ongoing data transfer operation, this method aborts it.
	 * 
	 * @param sendAborCommand
	 *            If true the client will negotiate the abort procedure with the
	 *            server, through the standard FTP ABOR command. Otherwise the
	 *            open data transfer connection will be closed without any
	 *            advise has sent to the server.
	 * @throws IOException
	 *             If the ABOR command cannot be sent due to any I/O error. This
	 *             could happen only if force is false.
	 * @throws FTPIllegalReplyException
	 *             If the server reply to the ABOR command is illegal. This
	 *             could happen only if force is false.
	 */
	public void abortCurrentDataTransfer(boolean sendAborCommand)
			throws IOException, FTPIllegalReplyException {
		synchronized (abortLock) {
			if (ongoingDataTransfer && !aborted) {
				if (sendAborCommand) {
					communication.sendFTPCommand("ABOR");
					touchAutoNoopTimer();
					consumeAborCommandReply = true;
				}
				if (dataTransferInputStream != null) {
					try {
						dataTransferInputStream.close();
					} catch (Throwable t) {
						;
					}
				}
				if (dataTransferOutputStream != null) {
					try {
						dataTransferOutputStream.close();
					} catch (Throwable t) {
						;
					}
				}
				aborted = true;
			}
		}
	}
	/**
	 * Returns the name of the charset that should be used in textual
	 * transmissions.
	 * 
	 * @return The name of the charset that should be used in textual
	 *         transmissions.
	 */
	private String pickCharset() {
		if (charset != null) {
			return charset;
		} else if (utf8Supported) {
			return "UTF-8";
		} else {
			return System.getProperty("file.encoding");
		}
	}
	/**
	 * Picks the local address for an active data transfer operation.
	 * 
	 * @return The local address as a 4 integer values array.
	 * @throws IOException
	 *             If an unexpected I/O error occurs while trying to resolve the
	 *             local address.
	 */
	private int[] pickLocalAddress() throws IOException {
		// Forced address?
		int[] ret = pickForcedLocalAddress();
		// Auto-detect?
		if (ret == null) {
			ret = pickAutoDetectedLocalAddress();
		}
		// Returns.
		return ret;
	}
	/**
	 * If a local address for active data transfers has been supplied through
	 * the {@link FTPKeys#ACTIVE_DT_HOST_ADDRESS}, it returns it as a 4 elements
	 * integer array; otherwise it returns null.
	 * 
	 * @return The forced local address, or null.
	 */
	private int[] pickForcedLocalAddress() {
		int[] ret = null;
		String aux = System.getProperty(FTPKeys.ACTIVE_DT_HOST_ADDRESS);
		if (aux != null) {
			boolean valid = false;
			StringTokenizer st = new StringTokenizer(aux, ".");
			if (st.countTokens() == 4) {
				valid = true;
				int[] arr = new int[4];
				for (int i = 0; i < 4; i++) {
					String tk = st.nextToken();
					try {
						arr[i] = Integer.parseInt(tk);
					} catch (NumberFormatException e) {
						arr[i] = -1;
					}
					if (arr[i] < 0 || arr[i] > 255) {
						valid = false;
						break;
					}
				}
				if (valid) {
					ret = arr;
				}
			}
			if (!valid) {
				// warning to the developer
				logger.warning("WARNING: invalid value \"" + aux
						+ "\" for the " + FTPKeys.ACTIVE_DT_HOST_ADDRESS
						+ " system property. The value should "
						+ "be in the x.x.x.x form.");
			}
		}
		return ret;
	}
	/**
	 * Auto-detects the local network address, and returns it in the form of a 4
	 * elements integer array.
	 * 
	 * @return The detected local address.
	 * @throws IOException
	 *             If an unexpected I/O error occurs while trying to resolve the
	 *             local address.
	 */
	private int[] pickAutoDetectedLocalAddress() throws IOException {
		InetAddress addressObj = InetAddress.getLocalHost();
		byte[] addr = addressObj.getAddress();
		int b1 = addr[0] & 0xff;
		int b2 = addr[1] & 0xff;
		int b3 = addr[2] & 0xff;
		int b4 = addr[3] & 0xff;
		int[] ret = { b1, b2, b3, b4 };
		return ret;
	}
	public String toString() {
		synchronized (lock) {
			StringBuffer buffer = new StringBuffer();
			buffer.append(getClass().getName());
			buffer.append(" [connected=");
			buffer.append(connected);
			if (connected) {
				buffer.append(", host=");
				buffer.append(host);
				buffer.append(", port=");
				buffer.append(port);
			}
			buffer.append(", connector=");
			buffer.append(connector);
			buffer.append(", security=");
			switch (security) {
			case SECURITY_FTP:
				buffer.append("SECURITY_FTP");
				break;
			case SECURITY_FTPS:
				buffer.append("SECURITY_FTPS");
				break;
			case SECURITY_FTPES:
				buffer.append("SECURITY_FTPES");
				break;
			}
			buffer.append(", authenticated=");
			buffer.append(authenticated);
			if (authenticated) {
				buffer.append(", username=");
				buffer.append(username);
				buffer.append(", password=");
				StringBuffer buffer2 = new StringBuffer();
				for (int i = 0; i < password.length(); i++) {
					buffer2.append('*');
				}
				buffer.append(buffer2);
				buffer.append(", restSupported=");
				buffer.append(restSupported);
				buffer.append(", utf8supported=");
				buffer.append(utf8Supported);
				buffer.append(", mlsdSupported=");
				buffer.append(mlsdSupported);
				buffer.append(", mode=modezSupported");
				buffer.append(modezSupported);
				buffer.append(", mode=modezEnabled");
				buffer.append(modezEnabled);
			}
			buffer.append(", transfer mode=");
			buffer.append(passive ? "passive" : "active");
			buffer.append(", transfer type=");
			switch (type) {
			case TYPE_AUTO:
				buffer.append("TYPE_AUTO");
				break;
			case TYPE_BINARY:
				buffer.append("TYPE_BINARY");
				break;
			case TYPE_TEXTUAL:
				buffer.append("TYPE_TEXTUAL");
				break;
			}
			buffer.append(", textualExtensionRecognizer=");
			buffer.append(textualExtensionRecognizer);
			FTPListParser[] listParsers = getListParsers();
			if (listParsers.length > 0) {
				buffer.append(", listParsers=");
				for (int i = 0; i < listParsers.length; i++) {
					if (i > 0) {
						buffer.append(", ");
					}
					buffer.append(listParsers[i]);
				}
			}
			FTPCommunicationListener[] communicationListeners = getCommunicationListeners();
			if (communicationListeners.length > 0) {
				buffer.append(", communicationListeners=");
				for (int i = 0; i < communicationListeners.length; i++) {
					if (i > 0) {
						buffer.append(", ");
					}
					buffer.append(communicationListeners[i]);
				}
			}
			buffer.append(", autoNoopTimeout=");
			buffer.append(autoNoopTimeout);
			buffer.append("]");
			return buffer.toString();
		}
	}
	/**
	 * Starts the auto-noop timer thread.
	 */
	private void startAutoNoopTimer() {
		if (autoNoopTimeout > 0) {
			autoNoopTimer = new AutoNoopTimer();
			autoNoopTimer.start();
		}
	}
	/**
	 * Stops the auto-noop timer thread.
	 * 
	 * @since 1.5
	 */
	private void stopAutoNoopTimer() {
		if (autoNoopTimer != null) {
			autoNoopTimer.interrupt();
			autoNoopTimer = null;
		}
	}
	/**
	 * Resets the auto noop timer.
	 */
	private void touchAutoNoopTimer() {
		if (autoNoopTimer != null) {
			nextAutoNoopTime = System.currentTimeMillis() + autoNoopTimeout;
		}
	}
	/**
	 * The auto noop timer thread.
	 */
	private class AutoNoopTimer extends Thread {
		public void run() {
			synchronized (lock) {
				if (nextAutoNoopTime <= 0 && autoNoopTimeout > 0) {
					nextAutoNoopTime = System.currentTimeMillis() + autoNoopTimeout;
				}
				while (!Thread.interrupted() && autoNoopTimeout > 0) {
					// Sleep till the next NOOP.
					long delay = nextAutoNoopTime - System.currentTimeMillis();
					if (delay > 0) {
						try {
							lock.wait(delay);
						} catch (InterruptedException e) {
							break;
						}
					}
					// Is it really time to NOOP?
					if (System.currentTimeMillis() >= nextAutoNoopTime) {
						// Yes!
						try {
							noop();
						} catch (Throwable t) {
							; // ignore...
						}
					}
				}
			}
		}
	}
}