Source: https://academy.oracle.com/en/oa-assets/i/c82/c82-java-prog-logo.jpg
What Will I Learn?
In this tutorial, I will guide you how to archive/zip files or directory using standard Java™ class library in the package of java.util.zip.*. After we grasp it, we'll move on to the lesson about sending it as an attachment in an email over the network using Gmail SMTP. All the classes we need to create the email application are available in the javax.mail package.
- Java™ I/O Stream
- Java™ Zip Output Stream
- Java™ Mail
Requirements
- Any current JDK.
- IntelliJ IDEA.
- At least two google accounts, as sender and recipient.
Difficulty
- Intermediate
Tutorial Contents
1. Create a Archiver Class
Archive a Single File
All we need to archive a file is:
- java.util.zip.ZipEntry - This class is used to represent a ZIP file entry.
- java.util.zip.ZipOutputStream - This class implements an output stream filter for writing files in the ZIP file format.
- java.io.InputStream, in which the implementor is: java.io.FileInputStream - Used for reading files to be archived.
- java.io.OutputStream, in which the implementor is: java.io.FileOutputStream - As a argument of ZipOutputStream.
Input:
Source - The source path will be zipped.
Target - The path of file as the output of zipping the source.
And here's the codes:
if(Files.isRegularFile(source)) { ZipEntry entry = new ZipEntry(source.toString()); try(FileInputStream in = new FileInputStream(source.toFile()); ZipOutputStream zOut = new ZipOutputStream(new FileOutputStream(target.toFile()))) { zOut.putNextEntry(entry); /* * Hold 2MB in memory. */ byte[] BYTES = new byte[2048]; for(int length; (length = in.read(BYTES)) > -1;) zOut.write(BYTES, 0, length); } catch(Exception e) { throw new RuntimeException(e); } }
Archive a Directory
Archive a directory is a bit more complicated, because we will recursively match the representation of a directory in which there are files and other directories inside it.
Here's the codes:public static void test(Path source) { if(Files.isDirectory(source)) { List<Path> paths = new java.util.ArrayList<>(); try(Stream<Path> stream = Files.walk(source, 1)) { stream.forEach((path) -> paths.add(path)); } catch(Exception e) { throw new RuntimeException(e); paths.clear(); return; } paths.remove(source); if(paths.size() > 0) for(Path e : paths) //Recursively, the same action as in archiving files as above zip(e); else { //Keep zipping an empty directory String name = source.toString(); try { zOut.putNextEntry(new ZipEntry(name + java.io.File.separator)); } catch(Exception e) { throw new RuntimeException(e); } } } }
Final Source Code
By setting file archiving as the base case, we can now define the
zip
method based on a path completely as follows:
Right click onsrc/main/java
directory. And on pop-up dialog, type:
com.murez.branch.io.ZipIOStream
.
Next, type this source code:package com.murez.branch.io; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.channels.FileLock; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.Map; import java.util.stream.Stream; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import static java.io.File.separator; public class ZipIOStream { /** * Zip the files or directory. * @param source The path to be zipped * @param target The path of file as the output of zipping {@code source} * @param report Map of errors that occurred while zipping {@code source}. Or just pass {@code null}, if you want to throw the exception. * @return {@code True} if this process was successful, otherwise {@code false}. */ public static boolean zip(Path source, Path target, Map<Path, Throwable> report) { ZipIOStream zipStream; boolean b = report == null; try{ zipStream = new ZipIOStream(source, target); } catch(Throwable e) { e = new RuntimeException("Initializing was failed", e); if(b) throw (RuntimeException) e; report.put(null, e); return false; } if(!zipStream.tryLock(b)) return false; zipStream.zip(source, report); /* * Make sure all sources are closed by first executing the `close()` method under the conditions below. */ return zipStream.close(b) && (b || report.size() < 1); } private final FileOutputStream F_OUT; private final ZipOutputStream Z_OUT; private final int N; private FileLock lock; private ZipIOStream(Path source, Path target) throws Throwable { if(source == null || Files.notExists(source)) throw new IllegalArgumentException("The source never existed"); if(target == null) throw new IllegalArgumentException("The target can't be null"); F_OUT = new FileOutputStream(target.toFile()); Z_OUT = new ZipOutputStream(F_OUT); N = source.getNameCount() - 1; } private void zip(Path source, Map<Path, Throwable> report) { final byte[] BYTES; if(Files.isRegularFile(source)) { ZipEntry entry = new ZipEntry(source.subpath(N, source.getNameCount()).toString()); try(FileInputStream in = new FileInputStream(source.toFile())) { Z_OUT.putNextEntry(entry); /* * Hold 2MB in memory. */ BYTES = new byte[2048]; for(int length; (length = in.read(BYTES)) > -1;) Z_OUT.write(BYTES, 0, length); } catch(Exception e) { e = new RuntimeException(source.toString(), e); if(report == null) throw (RuntimeException) e; report.put(source, e); } } else if(Files.isDirectory(source)) { List paths = new java.util.ArrayList<>(); try(Stream stream = Files.walk(source, 1)) { stream.forEach(paths::add); } catch(Exception e) { e = new RuntimeException("Walking path was failed: " + source, e); if(report == null) throw (RuntimeException) e; report.put(source, e); paths.clear(); return; } paths.remove(source); if(paths.size() > 0) for(Path e : paths) zip(e, report); else { String name = source.subpath(N, source.getNameCount()).toString(); try { Z_OUT.putNextEntry(new ZipEntry(name + separator)); } catch(Exception e) { e = new RuntimeException(source.toString(), e); if(report == null) throw (RuntimeException) e; report.put(source, e); } } } else { if(report != null) report.put(source, new IllegalArgumentException("Source's neither a file nor a directory")); } } private boolean close(boolean thrown) { boolean b = false; try { if(lock != null) lock.close(); Z_OUT.close(); b = true; } catch(IOException e) { if(thrown) throw new RuntimeException("Closing resources was failed", e); } return b; } private boolean tryLock(boolean thrown) { try { lock = F_OUT.getChannel().tryLock(); } catch(Throwable e) { if(thrown) throw new RuntimeException("Locking the target path has failed", e); return false; } return true; } }
Test
Here's a test that we will do:
Mainpublic static void main(String[] args) { final String ROOT = "D:/"; Path source = Paths.get(ROOT, "Sandbox/Test/Test"); Path target = Paths.get(ROOT, "Sandbox/Test/Out.zip"); Map<Path, Throwable> report = new java.util.HashMap<>(); if(!zip(source, target, report)) { System.out.println("Failed!"); for(Map.Entry<Path, Throwable> e : report.entrySet()) { System.out.println("Path: " + e.getKey()); e.getValue().printStackTrace(); } } else System.out.println("Success"); }
Input:
D:/Sandbox/Test/Test
Log
Output:D:/Sandbox/Test/Out.zip
Congratulations! We have successfully completed the module to create a ZIP file using the Java™ standard classes.
2. Create a Mail Class
All we need to send an email with attachment is:
- Add dependencies to
build.gradle
to be able to use the classes in thejavax.mail
package.
compile group: 'javax.mail', name: 'mail', version: '1.4.1'
- javax.activation.DataHandler - It provides a consistent interface to data available in many different sources and formats.
- javax.activation.FileDataSource - It provides data typing services via a FileTypeMap object.
- javax.mail.Authenticator - Represents an object that knows how to obtain authentication for a network connection.
- javax.mail.BodyPart - This class models a Part that is contained within a Multipart.
- javax.mail.Message - This class models an email message.
- javax.mail.Multipart - As a container that holds multiple body parts.
- javax.mail.PasswordAuthentication - As a data holder that is used by Authenticator.
- javax.mail.Session - Represents a mail session and is not subclassed.
- javax.mail.Transport.send - Send a message to all recipient addresses specified in the message.
- Make sure to change sender's settings of
Allow less secure apps
:ON
.
https://myaccount.google.com/lesssecureapps
- Define properties of Gmail SMTP,
private static final java.util.Properties GMAIL = new java.util.Properties();
static {
GMAIL.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
GMAIL.put("mail.smtp.socketFactory.port", "465");
GMAIL.put("mail.smtp.ssl.enable", "true");
GMAIL.put("mail.smtp.auth", "true");
GMAIL.put("mail.smtp.host", "smtp.gmail.com");
GMAIL.put("mail.smtp.port", "465");
}
And here's the complete code:
package com.murez.branch.io;
import javax.activation.DataHandler;
import javax.activation.FileDataSource;
import javax.mail.Authenticator;
import javax.mail.BodyPart;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import java.nio.file.Path;
public class Mail {
private final Message MESSAGE;
private static final java.util.Properties GMAIL = new java.util.Properties();
static {
GMAIL.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
GMAIL.put("mail.smtp.socketFactory.port", "465");
GMAIL.put("mail.smtp.ssl.enable", "true");
GMAIL.put("mail.smtp.auth", "true");
GMAIL.put("mail.smtp.host", "smtp.gmail.com");
GMAIL.put("mail.smtp.port", "465");
}
public static Mail getInstance(String username, String password, String receiver, boolean thrown) {
try {
return new Mail(username, password, receiver);
} catch(Throwable e) {
if(thrown)
throw new RuntimeException(e);
return null;
}
}
public static Mail getInstance(String username, String password, String receiver) {
return getInstance(username, password, receiver, false);
}
private Mail(String username, String password, String receiver) throws Throwable {
Session session = Session.getInstance(GMAIL, new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});
MESSAGE = new MimeMessage(session);
MESSAGE.setFrom(new InternetAddress(username));
MESSAGE.setRecipient(Message.RecipientType.TO, new InternetAddress(receiver));
}
public void setContent(String message, boolean thrown, Path... files) {
Multipart multiPart = new MimeMultipart();
BodyPart body;
try {
(body = new MimeBodyPart()).setText(message);
multiPart.addBodyPart(body);
} catch(MessagingException e) {
if(thrown)
throw new RuntimeException(e);
}
if(files != null)
for(Path file : files)
try {
body = new MimeBodyPart();
body.setDataHandler(new DataHandler(new FileDataSource(file.toFile())));
body.setFileName(file.getFileName().toString());
multiPart.addBodyPart(body);
} catch(MessagingException e) {
if(thrown)
throw new RuntimeException(file + " > " + e.getMessage(), e);
}
try { MESSAGE.setContent(multiPart); }
catch(MessagingException e) {
if(thrown)
throw new RuntimeException(e);
}
}
public boolean setSubject(String subject, boolean thrown) {
boolean b = true;
try { MESSAGE.setSubject(subject); }
catch(MessagingException e) {
if(thrown)
throw new RuntimeException(e);
b = false;
}
return b;
}
public boolean send(boolean thrown) {
boolean b = true;
try { Transport.send(MESSAGE); }
catch(MessagingException e) {
if(thrown)
throw new RuntimeException(e);
b = false;
}
return b;
}
}
Test
Suppose the two google accounts are[email protected]
as the sender and[email protected]
as the recipient, and the following is the scenario:Main
public static void main(String[] args) { String sender = "rezanas***@gmail.com"; String password = "**************"; String receiver = "murez***@gmail.com"; Mail mail = Mail.getInstance(sender, password, receiver, true); if(mail == null) { System.out.println("Failed!"); return; } if(!mail.setSubject("Test")) { System.out.println("Submission of subject has failed"); return; } /* * Plain text file */ final String ROOT = "D:\\Sandbox\\Test"; Path plainText = Paths.get(ROOT, "Plain.txt"); /* * Archive file */ Path zipFile = Paths.get(ROOT, "Out.zip"); ZipIOStream.zip(Paths.get(ROOT, "Test"), zipFile, null); System.out.println("Zipping was complete!"); mail.setContent("Hello World! My First email using Java mail", true, plainText, zipFile); if(!mail.send(true)) { System.out.println("Sending has failed"); return; } System.out.println("Successful!"); }
Input
- Subject: "Test"
- Message: "Hello World! My First email using Java mail"
- Attachments:
D:\Sandbox\Test\Plain.txt
andD:\Sandbox\Test\Out.zip
Log
Sender
Recipient
Congratulations! We have successfully sent an email using Gmail SMTP and have finished this tutorial completely.
Thank you!
Share with Heart.
Curriculum
- Java (Spring Framework) - First App with Connection to MySQL Database
- Java - How to Lock Files Using File Channel (Java I/O Part 1)
- Java (Spring Framework) - First App with Connection to MySQL Database
Posted on Utopian.io - Rewarding Open Source Contributors
Thank you for the contribution. It has been approved.
You can contact us on Discord.
[utopian-moderator]
Keren :)