package org.oddjob.io;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
import org.oddjob.arooa.deploy.annotations.ArooaAttribute;
import org.oddjob.util.OddjobConfigException;
/**
* @oddjob.description A Copy job. Copy either:
* <ul>
* <li>A file from one name to another.</li>
* <li>A file or files or directories to a different directory.</li>
* <li>An input (from another job) to a file.</li>
* <li>A file to an output.</li>
* <li>An input to an output.</li>
* </ul>
*
* @oddjob.example
*
* Copy a file.
*
* {@oddjob.xml.resource org/oddjob/io/CopyFileExample.xml}
*
* @oddjob.example
*
* Copy a directory.
*
* {@oddjob.xml.resource org/oddjob/io/CopyDirectory.xml}
*
* @oddjob.example
*
* Copy from a file to a buffer.
*
* {@oddjob.xml.resource org/oddjob/io/CopyFileToBuffer.xml}
*
*/
public class CopyJob implements Runnable, Serializable {
private static final long serialVersionUID = 20050806;
private static final Logger logger = Logger.getLogger(CopyJob.class);
/**
* @oddjob.property
* @oddjob.description A name, can be any text.
* @oddjob.required No.
*/
private String name;
/**
* @oddjob.property
* @oddjob.description The from file.
* @oddjob.required Yes unless input supplied.
*/
private File[] from;
/**
* @oddjob.property
* @oddjob.description The from file.
* @oddjob.required Yes unless output supplied.
*/
private File to;
/**
* @oddjob.property
* @oddjob.description An input.
* @oddjob.required Yes unless from supplied.
*/
private transient InputStream input;
/**
* @oddjob.property
* @oddjob.description The output.
* @oddjob.required Yes unless to supplied.
*/
private transient OutputStream output;
/**
* @oddjob.property
* @oddjob.description The number of files copied.
* @oddjob.required Read Only.
*/
private int filesCopied;
/**
* @oddjob.property
* @oddjob.description The number of directories copied.
* @oddjob.required Read Only.
*/
private int directoriesCopied;
/**
* Get the name.
*
* @return The name.
*/
public String getName() {
return name;
}
/**
* Set the name
*
* @param name The name.
*/
public void setName(String name) {
this.name = name;
}
/**
* Get the from file.
*
* @return The from file.
*/
synchronized public File[] getFrom() {
return from;
}
/**
* Set the from file.
*
* @param The from file.
*/
synchronized public void setFrom(File[] file) {
this.from = file;
}
/**
* Get the to file.
*
* @return The to file.
*/
synchronized public File getTo() {
return to;
}
/**
* Set the to file.
*
* @param The to file.
*/
@ArooaAttribute
synchronized public void setTo(File file) {
this.to = file;
}
/**
* Set the InputStream.
*
* @param The InputStream.
*/
synchronized public void setInput(InputStream in) {
this.input = in;
}
/**
* Set the OutputStream.
*
* @param The OutputStream.
*/
synchronized public void setOutput(OutputStream out) {
this.output = out;
}
public int getFilesCopied() {
return filesCopied;
}
public int getDirectoriesCopied() {
return directoriesCopied;
}
public void run() {
try {
CopyCommand command = command();
if (command == null) {
throw new NullPointerException(
"Failed to find anything to copy.");
}
logger.info("Performing " + command.toString());
CopyStats stats = new CopyStats();
command.copy(stats);
logger.info("Copied " + stats.files + " files, "
+ stats.directories + " directories.");
this.filesCopied = stats.files;
this.directoriesCopied = stats.directories;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private CopyCommand command() throws IOException {
if (input != null) {
if (output != null) {
return new StreamCopy(input, output);
}
else {
return new StreamCopy(input, to);
}
}
if (from == null) {
throw new RuntimeException("Nothing to copy from!");
}
File[] possiblyMany = Files.expand(from);
Files.verifyReadable(possiblyMany);
File singleFrom = null;
if (possiblyMany.length == 0) {
throw new RuntimeException("From does not specify any files.");
}
if (possiblyMany.length == 1) {
singleFrom = possiblyMany[0];
}
if (singleFrom != null) {
if (output != null) {
return new StreamCopy(singleFrom, output);
}
else if (singleFrom.isDirectory()) {
return new DirectoryCopy(singleFrom, to);
} else {
return new FileCopy(singleFrom, to);
}
}
return new MultiFileCopy(possiblyMany, to);
}
/**
* @return Returns the input.
*/
public InputStream getInput() {
return input;
}
/**
* @return Returns the output.
*/
public OutputStream getOutput() {
return output;
}
public String toString() {
if (name == null) {
return "Copy Files or Directories";
}
return name;
}
/**
*
*
*/
interface CopyCommand {
public void copy(CopyStats stats) throws IOException;
}
static class StreamCopy implements CopyCommand {
private final InputStream in;
private final OutputStream out;
StreamCopy(InputStream in, OutputStream out) {
this.in = in;
this.out = out;
}
StreamCopy(InputStream in, File to) throws IOException {
this.in = in;
if (to == null) {
throw new RuntimeException("To file is not specified.");
}
if (to.isDirectory()) {
throw new OddjobConfigException("Can copy stream to a directory.");
}
this.out = new FileOutputStream(to);
}
StreamCopy(File from, OutputStream out) throws IOException {
this.in = new FileInputStream(from);
this.out = out;
}
@Override
public void copy(CopyStats stats) throws IOException {
IOUtils.copy(in, out);
in.close();
out.close();
stats.files++;
}
@Override
public String toString() {
return "Copy between to binary streams.";
}
}
static class FileCopy implements CopyCommand {
private final File from;
private final File to;
FileCopy(File from, File to) {
this.from = from;
this.to = to;
}
@Override
public void copy(CopyStats stats) throws IOException {
if (to.isDirectory()) {
FileUtils.copyFileToDirectory(from, to);
}
else {
FileUtils.copyFile(from, to);
}
stats.files++;
}
@Override
public String toString() {
return "File copy from " + from + " to " + to;
}
}
static class DirectoryCopy implements CopyCommand {
private final File fromDir;
private final File toDir;
DirectoryCopy(File from, File to) {
this.fromDir = from;
if (to == null) {
throw new RuntimeException("To dir not specified.");
}
if (to.exists()) {
if (!to.isDirectory()) {
throw new OddjobConfigException("To must be a directory.");
}
this.toDir = new File(to, from.getName());
} else {
this.toDir = to;
}
}
@Override
public void copy(CopyStats stats) throws IOException {
FileUtils.copyDirectory(fromDir, toDir);
stats.directories++;
}
@Override
public String toString() {
return "Directory copy from " + fromDir + " to " +
toDir;
}
}
static class MultiFileCopy implements CopyCommand {
private final File[] files;
private final File toDir;
MultiFileCopy(File[] files, File toDir) {
this.files = files;
if (toDir == null) {
throw new RuntimeException("To dir is not specified.");
}
if (!toDir.isDirectory()) {
throw new RuntimeException("To must be a directory.");
}
this.toDir = toDir;
}
@Override
public void copy(CopyStats stats) throws IOException {
for (int i = 0; i < files.length; ++i) {
CopyCommand command;
if (files[i].isDirectory()) {
command = new DirectoryCopy(files[i], toDir);
} else {
command = new FileCopy(files[i], toDir);
}
command.copy(stats);
}
}
@Override
public String toString() {
return "Multiple file copy of " + files.length +
" files to " + toDir;
}
}
class CopyStats {
int files;
int directories;
}
}