/******************************************************************************* * 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; } } }