The CopyFile program copies a file to another directory.
It can optionally verify the copied version by creating a CRC-32 checksum on it and
comparing that to the checksum created on the original input file.
It is a boiled-down version (in the sense that only one file can be copied per call)
of the copy programs that come with most operating systems,
like cp under Unix and copy under Windows.
This page discusses a non-trivial but still comprehensible example
of using Java's I/O classes in the package java.io.
It is aimed at Java beginners who have already mastered the basics
of the Java programming language and are now interested in learning about core classes
of the Java runtime library (API).
The program is given as source code,
the reader should compile the program, run it, read the
explanation and study the source code.
There are also suggestions on how to extend the program as a student project
(studying code is all fine, actual programming is required to really advance).
This program covers the following topics:
These instructions are hopefully beginner-friendly. That's why they are a bit verbose.
javac CopyFile.javaNow you should have a new file CopyFile.class in the same directory.
java CopyFile FILE DIRwhere FILE is the input file you want to copy and DIR is the destination directory. Hint: Use a large file so that the clocking option can measure something. Make sure that the destination directory has enough free space left.
This section gives a guided tour of the program.
We follow its execution path starting, as always with Java programs, at
the public static main(String[]) method.
The program requires two arguments, the name of the input file to be copied, and
the destination directory in which the copy will be created,
in that order.
The program therefore checks if the args array has exactly two
elements and creates File objects from them.
The File class has quite a few helpful methods that access the underlying
file system, study its API documentation to learn more about what it can do.
Three methods are used to make sure the input file is a file (isFile),
the destination directory is indeed a directory (isDirectory) and
that reading from the file is allowed (canRead).
A File object is then assembled from the source file's name (without any path)
and the destination directory as the path.
A common misconception about the File class is that a File
object always corresponds to a real file and creating a File object
will create a file of the name given to the constructor.
In fact File is just a wrapper around a file name.
The presence of the exists method emphasizes that.
Note that the program has a couple of options that influence program execution. The actual settings of those options are stored in class variables declared at the beginning of the class (line program options initialized to default values in the source code). These option variables will be referenced throughout the main method.
The method doCopy is used to check if the file is really to be copied.
If that is not the case, the program is terminated using a call to System.exit.
Otherwise, execution continues by doing the actual copying in the copyFile method.
If the file verification option is enabled, a Long object is returned by that
method storing the CRC-32 checksum created on the input file.
In case the copy timestamp option is enabled, the date and time of last modification
of the new copied version of the file is set to date and time of the input file.
Otherwise, the new file has a timestamp of the current date and time, which may or may not
be what the user wants.
Two methods of File are used to accomplish that, lastModified to read
the timestamp of the source and setLastModified to set that value for the destination file.
If the verify option that was already discussed is enabled, the program calls createChecksum in order to create a checksum by reading the entire file we just wrote, creating another checksum in the process and comparing it to the one we created when reading the source file in the copyFile method. The program then prints if the two differ, which means that there was an error while copying, or it prints OK if the two values are equal. The program then reaches the end of the main method and therefore terminates.
This method determines whether file copying will actually take place. It returns false (no copying) if the the argument destination file exists and
This helper method prints the argument text message to standard output
(usually the command line, shell, prompt) using the System.out print stream.
It then reads lines from standard input (user keyboard inputs confirmed by pressing the enter key)
until a line is either y for yes or n for no.
This is done by wrapping System.in—the standard input stream—into
a InputStreamReader which is then given to a
BufferedReader and calls its readLine method.
The method returns false for a n user input and
true for a y input.
Finally, the method that does the actual data copying. The two arguments are File objects representing source and target file, respectively. A FileInputStream and a FileOutputStream are created for source and target. A buffer of the size specified in the bufferSize variable is allocated. In a while loop, data is read from input and written to output. If the verify option is enabled, a checksum is created on all data read from input. When the end of the input stream is reached, both streams are closed. If the clocking option is enabled, the time used on copying is printed to standard output. Finally, the checksum (or null if verify is disabled) is returned.
This method is in some ways similar to copyFile, it just restricts itself to reading. It opens a FileInputStream for the argument File object, allocates another buffer and reads all data updating a checksum on it. Finally, the input stream is closed, the time printed to stdout again (if clock is true) and the checksum is returned for the caller to compare against the checksum created on the source file.
If you think you've understood this program and if you are interested in modifying it a bit, here are some suggestions for features you could add in order to learn more about Java programming and the Java runtime library.
args argument given to main.
java.nio
package introduced in Java 1.4 and use that to do the copying.
Java comes with a huge runtime library, still there are quite a few things not built into it, for different reasons. Using JNI would be one solution, but the program would be no longer platform-independent. Here are some features that cannot be easily implemented with Java:
java.io.File class.
import java.io.*;
import java.util.zip.*;
/**
* Command line program to copy a file to another directory.
* @author Marco Schmidt
*/
public class CopyFile {
// constant values for the override option
public static final int OVERWRITE_ALWAYS = 1;
public static final int OVERWRITE_NEVER = 2;
public static final int OVERWRITE_ASK = 3;
// program options initialized to default values
private static int bufferSize = 4 * 1024;
private static boolean clock = true;
private static boolean copyOriginalTimestamp = true;
private static boolean verify = true;
private static int override = OVERWRITE_ASK;
public static Long copyFile(File srcFile, File destFile)
throws IOException {
InputStream in = new FileInputStream(srcFile);
OutputStream out = new FileOutputStream(destFile);
long millis = System.currentTimeMillis();
CRC32 checksum = null;
if (verify) {
checksum = new CRC32();
checksum.reset();
}
byte[] buffer = new byte[bufferSize];
int bytesRead;
while ((bytesRead = in.read(buffer)) >= 0) {
if (verify) {
checksum.update(buffer, 0, bytesRead);
}
out.write(buffer, 0, bytesRead);
}
out.close();
in.close();
if (clock) {
millis = System.currentTimeMillis() - millis;
System.out.println("Second(s): " + (millis/1000L));
}
if (verify) {
return new Long(checksum.getValue());
} else {
return null;
}
}
public static Long createChecksum(File file) throws IOException {
long millis = System.currentTimeMillis();
InputStream in = new FileInputStream(file);
CRC32 checksum = new CRC32();
checksum.reset();
byte[] buffer = new byte[bufferSize];
int bytesRead;
while ((bytesRead = in.read(buffer)) >= 0) {
checksum.update(buffer, 0, bytesRead);
}
in.close();
if (clock) {
millis = System.currentTimeMillis() - millis;
System.out.println("Second(s): " + (millis/1000L));
}
return new Long(checksum.getValue());
}
/**
* Determine if data is to be copied to given file.
* Take into consideration override option and
* ask user in case file exists and override option is ask.
* @param file File object for potential destination file
* @return true if data is to be copied to file, false if not
*/
public static boolean doCopy(File file) {
boolean exists = file.exists();
if (override == OVERWRITE_ALWAYS || !exists) {
return true;
} else
if (override == OVERWRITE_NEVER) {
return false;
} else
if (override == OVERWRITE_ASK) {
return readYesNoFromStandardInput("File exists. " +
"Overwrite (y/n)?");
} else {
throw new InternalError("Program error. Invalid " +
"value for override: " + override);
}
}
public static void main(String[] args) throws IOException {
// make sure there are exactly two arguments
if (args.length != 2) {
System.err.println("Usage: CopyFile SRC-FILE-NAME DEST-DIR-NAME");
System.exit(1);
}
// make sure the source file is indeed a readable file
File srcFile = new File(args[0]);
if (!srcFile.isFile() || !srcFile.canRead()) {
System.err.println("Not a readable file: " + srcFile.getName());
System.exit(1);
}
// make sure the second argument is a directory
File destDir = new File(args[1]);
if (!destDir.isDirectory()) {
System.err.println("Not a directory: " + destDir.getName());
System.exit(1);
}
// create File object for destination file
File destFile = new File(destDir, srcFile.getName());
// check if copying is desired given overwrite option
if (!doCopy(destFile)) {
return;
}
// copy file, optionally creating a checksum
Long checksumSrc = copyFile(srcFile, destFile);
// copy timestamp of last modification
if (copyOriginalTimestamp) {
if (!destFile.setLastModified(srcFile.lastModified())) {
System.err.println("Error: Could not set " +
"timestamp of copied file.");
}
}
// optionally verify file
if (verify) {
System.out.print("Verifying destination file...");
Long checksumDest = createChecksum(destFile);
if (checksumSrc.equals(checksumDest)) {
System.out.println(" OK, files are equal.");
} else {
System.out.println(" Error: Checksums differ.");
}
}
}
/**
* Print a message to standard output and read lines from
* standard input until yes or no (y or n) is entered.
* @param message informative text to be answered by user
* @return user answer, true for yes, false for no.
*/
public static boolean readYesNoFromStandardInput(String message) {
System.out.println(message);
String line;
BufferedReader in = new BufferedReader(new InputStreamReader(
System.in));
Boolean answer = null;
try
{
while ((line = in.readLine()) != null) {
line = line.toLowerCase();
if ("y".equals(line) || "yes".equals(line)) {
answer = Boolean.TRUE;
break;
}
else
if ("n".equals(line) || "no".equals(line)) {
answer = Boolean.FALSE;
break;
}
else
{
System.out.println("Could not understand answer (\"" +
line + "\"). Please use y for yes or n for no.");
}
}
if (answer == null) {
throw new IOException("Unexpected end of input from stdin.");
}
in.close();
return answer.booleanValue();
}
catch (IOException ioe)
{
throw new InternalError(
"Cannot read from stdin or write to stdout.");
}
}
}