nz.ac.otago.psyanlab.common.util.FileUtils.java Source code

Java tutorial

Introduction

Here is the source code for nz.ac.otago.psyanlab.common.util.FileUtils.java

Source

/*
 Copyright (c) 2012, 2013 University of Otago, Tonic Artos <tonic.artos@gmail.com>
    
 Otago PsyAn Lab is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.
    
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 GNU General Public License for more details.
    
 You should have received a copy of the GNU General Public License
 along with this program. If not, see <http://www.gnu.org/licenses/>.
    
 In accordance with Section 7(b) of the GNU General Public License version 3,
 all legal notices and author attributions must be preserved.
 */

package nz.ac.otago.psyanlab.common.util;

import com.google.gson.stream.JsonReader;

import nz.ac.otago.psyanlab.common.model.Asset;
import nz.ac.otago.psyanlab.common.model.Experiment;
import nz.ac.otago.psyanlab.common.model.Source;
import nz.ac.otago.psyanlab.common.model.util.ModelUtils;

import org.json.JSONException;

import android.text.TextUtils;
import android.text.format.Time;

import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.text.DecimalFormat;
import java.util.Enumeration;
import java.util.Map.Entry;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

//

public class FileUtils {
    /**
     * Compress a working directory to some destination.
     * 
     * @param workingDir Directory whose contents will be archived.
     * @param destFile Destination file which will be written to.
     * @return Archive file written.
     * @throws FileNotFoundException
     * @throws IOException
     */
    @Deprecated
    public static File compress(File workingDir, File destFile) throws FileNotFoundException, IOException {
        ZipOutputStream out = new ZipOutputStream(new FileOutputStream(destFile));
        addFiles(workingDir, workingDir, out);
        out.close();

        return destFile;
    }

    public static void compress(File destination, Experiment experiment, File workingDir) throws IOException {
        ZipOutputStream out = new ZipOutputStream(new FileOutputStream(destination));

        try {
            writeExperimentDefinitionToArchive(out, copyAssetsToArchive(out, experiment, workingDir));
        } finally {
            out.close();
        }
    }

    /**
     * Inflate a pale file to a given working directory.
     * 
     * @param paleFile .pale file to inflate.
     * @param workingDir Path to extract to.
     * @return Path of the location the file was extracted to.
     * @throws FileNotFoundException
     * @throws IOException
     */
    public static File decompress(File paleFile, File workingDir) throws FileNotFoundException, IOException {
        ZipFile archive = new ZipFile(paleFile);

        try {
            // Iterate over elements in zip file and extract them to working
            // directory.
            Enumeration<? extends ZipEntry> entries = archive.entries();
            while (entries.hasMoreElements()) {
                ZipEntry ze = entries.nextElement();
                File newFile = new File(workingDir, ze.getName());

                if (ze.isDirectory()) {
                    if (!newFile.exists()) {
                        newFile.mkdirs();
                    }
                    continue;
                }

                if (!newFile.getParentFile().exists()) {
                    // Dir tree missing for some reason so create it.
                    newFile.getParentFile().mkdirs();
                }

                InputStream in = archive.getInputStream(ze);
                OutputStream out = new BufferedOutputStream(new FileOutputStream(newFile));

                try {
                    copy(in, out);
                } finally {
                    in.close();
                    out.close();
                }
            }
        } finally {
            archive.close();
        }

        return workingDir;
    }

    /**
     * Extract a single named file from an archive.
     * 
     * @param needle Archive relative path of file to extract.
     * @param paleFile Experiment archive file.
     * @throws IOException
     * @throws ZipException
     */
    public static byte[] extractJust(String needle, File paleFile) throws ZipException, IOException {
        ZipFile archive = new ZipFile(paleFile);
        try {
            ZipEntry ze = archive.getEntry(needle);
            if (ze == null) {
                throw new RuntimeException("Could not find experiment json in " + paleFile.getName());
            }
            InputStream in = archive.getInputStream(ze);
            ByteArrayOutputStream out = new ByteArrayOutputStream();

            try {
                copy(in, out);
                return out.toByteArray();
            } finally {
                in.close();
                out.close();
            }
        } finally {
            archive.close();
        }
    }

    /**
     * Format Bytes to a human readable quantity.
     * 
     * @param fileSize File size in bytes.
     * @return Human readable string of bytes. e.g. 1.2 TiB.
     */
    public static String formatBytes(long fileSize) {
        if (fileSize <= 0) {
            return "0 B";
        }
        final String[] units = new String[] { "B", "KiB", "MiB", "GiB", "TiB" };
        int digitGroups = (int) (Math.log10(fileSize) / Math.log10(1024));
        return new DecimalFormat("#,##0.#").format(fileSize / Math.pow(1024, digitGroups)) + " "
                + units[digitGroups];
    }

    /**
     * Generates a relatively unique path to use as a temporary directory.
     * 
     * @return
     */
    public static String generateTempPath() {
        Time now = new Time();
        now.setToNow();
        return "tmp-" + now.toMillis(false) + "/";
    }

    /**
     * Load PALE definition from deflated file.
     * 
     * @param paleFile Deflated PALE file.
     * @return Experiment definition.
     * @throws IOException
     * @throws JSONException
     */
    public static Experiment loadExperimentDefinitionFromArchive(File paleFile) throws IOException, JSONException {
        String paleDefinition = new String(FileUtils.extractJust("experiment.json", paleFile));

        if (TextUtils.isEmpty(paleDefinition)) {
            throw new IllegalStateException("PALE definition was empty.");
        }

        try {
            return ModelUtils.readDefinition(paleDefinition);
        } catch (RuntimeException e) {
            throw new JSONException("Failed to parse PALE definition.");
        }
    }

    public static Experiment loadExperimentDefinition(File experimentJsonFile) throws FileNotFoundException {
        try {
            return ModelUtils.getDataReaderWriter().fromJson(
                    new JsonReader(new InputStreamReader(new FileInputStream(experimentJsonFile), "UTF-16")),
                    Experiment.class);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("Unexpected internal error.", e);
        }
    }

    /**
     * Recursively add files in directory to zip archive.
     * 
     * @param dir Directory of files to add to archive.
     * @param baseDir Base directory of archive. For first call should be the
     *            same as dir.
     * @param out Zip archive output stream.
     * @throws IOException
     */
    private static void addFiles(File dir, File baseDir, ZipOutputStream out) throws IOException {
        File[] files = dir.listFiles();

        for (int i = 0; i < files.length; i++) {
            if (files[i].isDirectory()) {
                addFiles(files[i], baseDir, out);
            } else {
                ZipEntry ze = new ZipEntry(files[i].getPath().substring(baseDir.getPath().length() + 1));
                out.putNextEntry(ze);
                InputStream in = new FileInputStream(files[i]);

                copy(in, out);

                in.close();
            }
        }

    }

    /**
     * Copies assets files referenced in experiment to the specified archive.
     * 
     * @param archive Archive to copy assets into.
     * @param experiment The experiment.
     * @return Modified experiment file with updated asset references.
     * @throws IOException
     */
    private static Experiment copyAssetsToArchive(ZipOutputStream out, Experiment experiment, File workingDir)
            throws IOException {

        for (Entry<Long, Asset> entry : experiment.assets.entrySet()) {
            long key = entry.getKey();
            Asset asset = entry.getValue();
            File assetFile;
            if (asset.isExternal()) {
                assetFile = new File(asset.path);
            } else {
                assetFile = new File(workingDir, asset.path);
            }
            InputStream in = new FileInputStream(assetFile);

            // Rename files so as to prevent name collision.
            String newPath = "assets/" + asset.getTypeId() + "_" + key;
            ZipEntry ze = new ZipEntry(newPath);
            out.putNextEntry(ze);

            // Update definition with new asset location.
            asset.path = newPath;
            experiment.assets.put(key, asset);

            try {
                copy(in, out);
                out.closeEntry();
            } finally {
                in.close();
            }
        }

        for (Entry<Long, Source> entry : experiment.sources.entrySet()) {
            long key = entry.getKey();
            Source source = entry.getValue();
            File assetFile;
            if (source.isExternal()) {
                assetFile = new File(source.path);
            } else {
                assetFile = new File(workingDir, source.path);
            }
            InputStream in = new FileInputStream(assetFile);

            // Rename files so as to prevent name collision.
            String newPath = "sources/" + key;
            ZipEntry ze = new ZipEntry(newPath);
            out.putNextEntry(ze);

            // Update definition with new asset location.
            source.path = newPath;
            experiment.sources.put(key, source);

            try {
                copy(in, out);
                out.closeEntry();
            } finally {
                in.close();
            }
        }

        return experiment;
    }

    /**
     * Write the experiment definition to the zip output stream. This should be
     * done as the final operation in compressing the entire experiment as
     * earlier stages may modify this data that is to be written.
     * 
     * @param out Output stream for encoding the data.
     * @param experiment Experiment definition.
     * @throws IOException
     */
    private static void writeExperimentDefinitionToArchive(ZipOutputStream out, Experiment experiment)
            throws IOException {
        String paleDef = ModelUtils.getDataReaderWriter().toJson(experiment);
        InputStream in = new ByteArrayInputStream(paleDef.getBytes("UTF-16"));

        ZipEntry ze = new ZipEntry("experiment.json");
        out.putNextEntry(ze);

        try {
            copy(in, out);
            out.closeEntry();
        } finally {
            in.close();
        }
    }

    protected static void copy(InputStream in, OutputStream out) throws IOException {
        final byte[] buffer = new byte[8192];
        int len = 0;

        while (-1 != (len = in.read(buffer))) {
            out.write(buffer, 0, len);
        }
    }
}