/*******************************************************************************
* This file is part of the Symfony eclipse plugin.
*
* (c) Robert Gruendler <r.gruendler@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
******************************************************************************/
package com.dubture.symfony.core.util;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
import org.apache.commons.compress.compressors.gzip.GzipUtils;
import org.apache.commons.compress.utils.IOUtils;
public final class UncompressUtils {
private static final int BUFFER_SIZE = 8 * 1024;
private static final EntryNameTranslator IDENTITY_TRANSLATOR = new EntryNameTranslator() {
@Override
public String translate(String entryName) {
return entryName;
}
};
private UncompressUtils() {
// Cannot instantiate
}
/**
* Uncompress a gzip archive and returns the file where it has been
* extracted.
*
* @param archiveFile The archive file to uncompress
* @param outputDirectory The output directory where to put the uncompressed archive
*
* @return The output file where the archive has been uncompressed
*
* @throws IOException When a problem occurs with either the input or output stream
*/
public static File uncompressGzipArchive(File archiveFile, File outputDirectory) throws IOException {
FileInputStream fileInputStream = new FileInputStream(archiveFile);
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
GzipCompressorInputStream gzipInputStream = new GzipCompressorInputStream(bufferedInputStream);
String tarArchiveFilename = GzipUtils.getUncompressedFilename(archiveFile.getName());
File outputFile = new File(outputDirectory, tarArchiveFilename);
FileOutputStream outputStream = new FileOutputStream(outputFile);
int byteReadCount = 0;
final byte[] data = new byte[BUFFER_SIZE];
try {
while ((byteReadCount = gzipInputStream.read(data, 0, BUFFER_SIZE)) != -1) {
outputStream.write(data, 0, byteReadCount);
}
} finally {
outputStream.close();
gzipInputStream.close();
}
return outputFile;
}
/**
* Uncompress all entries found in a tar archive file into the given output
* directory.
*
* @param archiveFile The tar archive file to uncompress
* @param outputDirectory The output directory where to put uncompressed entries
*
* @throws IOException When an error occurs while uncompressing the tar archive
*/
public static void uncompressTarArchive(File archiveFile, File outputDirectory) throws IOException {
uncompressTarArchive(archiveFile, outputDirectory, IDENTITY_TRANSLATOR);
}
/**
* Uncompress all entries found in a tar archive file into the given output
* directory. Entry names are translated using the translator before being
* put on the file system.
*
* @param archiveFile The tar archive file to uncompress
* @param outputDirectory The output directory where to put uncompressed entries
* @param entryNameTranslator The entry name translator to use
*
* @throws IOException When an error occurs while uncompressing the tar archive
*/
public static void uncompressTarArchive(File archiveFile,
File outputDirectory,
EntryNameTranslator entryNameTranslator) throws IOException {
FileInputStream fileInputStream = new FileInputStream(archiveFile);
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
TarArchiveInputStream tarInputStream = new TarArchiveInputStream(bufferedInputStream);
try {
TarArchiveEntry tarEntry = null;
while ((tarEntry = tarInputStream.getNextTarEntry()) != null) {
uncompressTarArchiveEntry(tarInputStream, tarEntry, outputDirectory, entryNameTranslator);
}
} finally {
tarInputStream.close();
}
}
/**
* Uncompress a tar archive entry to the specified output directory. Entry names are
* translated using the translator before being put on the file system.
*
* @param tarInputStream The tar input stream associated with the entry
* @param entry The tar archive entry to uncompress
* @param outputDirectory The output directory where to put the uncompressed entry
* @param entryNameTranslator The entry name translator to use
*
* @throws IOException If an error occurs within the input or output stream
*/
public static void uncompressTarArchiveEntry(TarArchiveInputStream tarInputStream,
TarArchiveEntry entry,
File outputDirectory) throws IOException {
uncompressTarArchiveEntry(tarInputStream, entry, outputDirectory, IDENTITY_TRANSLATOR);
}
/**
* Uncompress a tar archive entry to the specified output directory.
*
* @param tarInputStream The tar input stream associated with the entry
* @param entry The tar archive entry to uncompress
* @param outputDirectory The output directory where to put the uncompressed entry
*
* @throws IOException If an error occurs within the input or output stream
*/
public static void uncompressTarArchiveEntry(TarArchiveInputStream tarInputStream,
TarArchiveEntry entry,
File outputDirectory,
EntryNameTranslator entryNameTranslator) throws IOException {
String entryName = entryNameTranslator.translate(entry.getName());
if (entry.isDirectory()) {
createDirectory(new File(outputDirectory, entryName));
return;
}
File outputFile = new File(outputDirectory, entryName);
ensureDirectoryHierarchyExists(outputFile);
BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(outputFile));
try {
IOUtils.copy(tarInputStream, outputStream);
addExecutableBit(outputFile, Permissions.fromMode(entry.getMode()));
} finally {
outputStream.close();
}
}
/**
* Add the executable bit to a file. The file must not be a directory otherwise
* false is returned. If the executable bit has been applied successfully, the
* method returns true. If the permissions specify
*
* @param file The file to add executable bit to
* @param permissions The permissions associated with the file
*
* @return True if the application succeeded, false otherwise
*/
private static boolean addExecutableBit(File file, Permissions permissions) {
if (file.isDirectory() || !permissions.hasExecutableBit()){
return false;
}
return file.setExecutable(true, permissions.hasOnlyOwnerExecutableBit());
}
/**
* Method that creates the directory structure of the directory
* received in argument.
*
* @param directory The directory for which directory structure should be created
*/
private static void createDirectory(File directory) {
if (directory.exists()) {
// Directory already exists, no need to do anything
return;
}
if (!directory.mkdirs()) {
throw new RuntimeException("Cannot create directory " + directory);
}
}
/**
* This will ensure the hierarchy of parent directories by creating
* them if they do not exist.
*
* @param outputFile The output file for which hierarchy must exists
*/
private static void ensureDirectoryHierarchyExists(File outputFile) {
if (!outputFile.getParentFile().exists()){
createDirectory(outputFile.getParentFile());
}
}
/**
* A simple interface used to translate an entry name to something
* else. This is used by the uncompress utils to let the end user
* of the utility class change the value of an entry name.
*
* @author Matthieu Vachon <matthieu.o.vachon@gmail.com>
*/
public static interface EntryNameTranslator {
/**
* Translate the entry name to something else. This is useful
* for example to remove the prefix of the entry.
*
* @param entryName The original entry name
* @return The translated entry name
*/
String translate(String entryName);
}
/**
* A class used to wrap permissions. This class uses unix
* style permissions encoding. It is used mainly by the UncompressUtils
* class to add executable bit to extracted file if they have it
* in the archive. This class is incomplete and does the work
* only for executable bit for now.
*
* @author Matthieu Vachon <matthieu.o.vachon@gmail.com>
*/
protected static class Permissions {
/** Mask for the three least significant bits */
private static final int PACK_MASK = 7;
/** Mask for the least significant bit */
private static final int EXECUTABLE_MASK = 1;
int mode;
boolean hasExecutableBitOwner;
boolean hasExecutableBitGroup;
boolean hasExecutableBitOther;
public static Permissions fromMode(int mode) {
return new Permissions(mode);
}
public static Permissions fromUnixMode(String mode) {
return new Permissions(Integer.parseInt(mode, 8));
}
private Permissions(int mode) {
this.mode = mode;
setOwner(getBitsPack(0));
setGroup(getBitsPack(1));
setOther(getBitsPack(2));
}
/**
* Check if the permissions have an executable bit in any of
* the three possible pack: owner, group and other.
*
* @return True if the permissions has executable bit for any packs
*/
public boolean hasExecutableBit() {
return hasExecutableBitOwner || hasExecutableBitGroup || hasExecutableBitOther;
}
/**
* Check if the permissions have executable bit only for the owner of
* the file.
*
* @return True if the permissions has executable bit only for owner
*/
public boolean hasOnlyOwnerExecutableBit() {
return hasExecutableBitOwner && !hasExecutableBitGroup && !hasExecutableBitOther;
}
private void setOwner(int ownerPack) {
hasExecutableBitOwner = (ownerPack & EXECUTABLE_MASK) == 1;
}
private void setGroup(int groupPack) {
hasExecutableBitGroup = (groupPack & EXECUTABLE_MASK) == 1;
}
private void setOther(int otherPack) {
hasExecutableBitOther = (otherPack & EXECUTABLE_MASK) == 1;
}
private int getBitsPack(int packId) {
assert(packId > 0 && packId <= 2);
int offset = packId * 3;
return (this.mode >> offset) & PACK_MASK;
}
public String toString() {
String owner = "Owner: " + (hasExecutableBitOwner ? "x" : "-");
String group = "Group: " + (hasExecutableBitGroup ? "x" : "-");
String other = "Other: " + (hasExecutableBitOther ? "x" : "-");
return owner + ", " + group + ", " + other;
}
}
}