mobac.program.atlascreators.impl.rmp.RmpWriter.java Source code

Java tutorial

Introduction

Here is the source code for mobac.program.atlascreators.impl.rmp.RmpWriter.java

Source

/*******************************************************************************
 * Copyright (c) MOBAC developers
 * 
 * This program 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 2 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/>.
 ******************************************************************************/
/* *********************************************
 * Copyright: Andreas Sander
 *
 *
 * ********************************************* */

package mobac.program.atlascreators.impl.rmp;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.util.ArrayList;

import mobac.program.atlascreators.impl.rmp.interfaces.RmpFileEntry;
import mobac.program.atlascreators.impl.rmp.rmpfile.RmpIni;
import mobac.utilities.Utilities;
import mobac.utilities.stream.CountingOutputStream;
import mobac.utilities.stream.RandomAccessFileOutputStream;

import org.apache.commons.io.output.NullOutputStream;
import org.apache.log4j.Logger;

/**
 * Class that writes files in RMP archive format
 * 
 */
public class RmpWriter {

    /**
     * Max file size: 2147483647 bytes = 2047,99 MiB
     */
    public static final long MAX_FILE_SIZE = 0xffffffffl;

    private static final Logger log = Logger.getLogger(RmpWriter.class);

    private final ArrayList<EntryInfo> entries = new ArrayList<EntryInfo>();
    private final File rmpFile;
    private final RandomAccessFile rmpOutputFile;
    private int projectedEntryCount;

    private ChecksumOutputStream entryOut;

    /**
     * @param imageName
     * @param layerCount
     *            projected number of layers that will be written to this rmp file
     * @param rmpFile
     * @throws IOException
     * @throws InterruptedException
     */
    public RmpWriter(String imageName, int layerCount, File rmpFile) throws IOException, InterruptedException {
        this.rmpFile = rmpFile;
        // We only use one A00 entry per map/layer - therefore we can
        // pre-calculate the number of entries:
        // RmpIni + (TLM & A00) per layer + Bmp2Bit + Bmp4bit
        this.projectedEntryCount = (3 + (2 * layerCount));
        if (rmpFile.exists())
            Utilities.deleteFile(rmpFile);
        log.debug("Writing data to " + rmpFile.getAbsolutePath());
        rmpOutputFile = new RandomAccessFile(rmpFile, "rw");
        // Calculate offset to the directory end
        int directoryEndOffset = projectedEntryCount * 24 + 10;
        rmpOutputFile.seek(directoryEndOffset);
        entryOut = new ChecksumOutputStream(new RandomAccessFileOutputStream(rmpOutputFile));
        /* --- Write the directory-end marker --- */
        RmpTools.writeFixedString(entryOut, "MAGELLAN", 30);

        RmpIni rmpIni = new RmpIni(imageName, layerCount);

        /* --- Create packer and fill it with content --- */
        writeFileEntry(rmpIni);
    }

    public void writeFileEntry(RmpFileEntry entry) throws IOException, InterruptedException {
        EntryInfo info = new EntryInfo();
        info.name = entry.getFileName();
        info.extendsion = entry.getFileExtension();
        info.offset = rmpOutputFile.getFilePointer();
        entry.writeFileContent(entryOut);
        info.length = rmpOutputFile.getFilePointer() - info.offset;
        if ((info.length % 2) != 0)
            entryOut.write(0);
        entries.add(info);
        if (rmpOutputFile.getFilePointer() > MAX_FILE_SIZE)
            throwRmpTooLarge();
        log.debug("Written data of entry " + entry + " bytes=" + info.length);
    }

    public void prepareFileEntry(RmpFileEntry entry) throws IOException, InterruptedException {
        EntryInfo info = new EntryInfo();
        info.name = entry.getFileName();
        info.extendsion = entry.getFileExtension();
        long pos = rmpOutputFile.getFilePointer();
        info.offset = pos;
        CountingOutputStream cout = new CountingOutputStream(new NullOutputStream());
        entry.writeFileContent(cout);
        info.length = cout.getBytesWritten();
        long newPos = pos + info.length;
        if ((info.length % 2) != 0)
            newPos++;
        if (newPos > MAX_FILE_SIZE)
            throwRmpTooLarge();
        rmpOutputFile.seek(newPos);
        entries.add(info);
        log.debug("Prepared data of entry " + entry + " bytes=" + info.length);
    }

    public void writePreparedFileEntry(RmpFileEntry entry) throws IOException, InterruptedException {
        long pos = rmpOutputFile.getFilePointer();
        EntryInfo info = new EntryInfo();
        info.name = entry.getFileName();
        info.extendsion = entry.getFileExtension();
        int index = entries.indexOf(info);
        if (index < 0)
            throw new RuntimeException("Index for entry not found");
        info = entries.get(index);

        rmpOutputFile.seek(info.offset);
        entry.writeFileContent(entryOut);
        if (rmpOutputFile.getFilePointer() > MAX_FILE_SIZE)
            throwRmpTooLarge();
        long newLength = rmpOutputFile.getFilePointer() - info.offset;
        if (newLength != info.length)
            throw new RuntimeException("Length of entry has changed!");
        if ((newLength % 2) != 0)
            entryOut.write(0);

        // restore old file position
        rmpOutputFile.seek(pos);
    }

    private void throwRmpTooLarge() throws IOException {
        throw new IOException("RMP file size exeeds 2GiB! The RMP file format does not support that.");
    }

    /**
     * Writes the directory of the archive into the rmp file
     * 
     * @throws IOException
     *             Error accessing disk
     */
    public void writeDirectory() throws IOException {
        if (projectedEntryCount != entries.size())
            throw new RuntimeException(
                    "Entry count does not correspond " + "to the projected layer count: \nProjected: "
                            + projectedEntryCount + "\nPresent:" + entries.size());

        // Finalize the list of written entries
        RmpTools.writeFixedString(entryOut, "MAGELLAN", 8);
        entryOut.writeChecksum();

        log.debug("Finished writing entries, updating directory");

        /* --- Create file --- */
        rmpOutputFile.seek(0);
        OutputStream out = new RandomAccessFileOutputStream(rmpOutputFile);
        ChecksumOutputStream cout = new ChecksumOutputStream(out);

        /* --- Write header with number of files --- */
        RmpTools.writeValue(cout, entries.size(), 4);
        RmpTools.writeValue(cout, entries.size(), 4);

        /* --- Write the directory --- */
        log.debug("Writing directory: " + entries.size() + " entries");
        for (EntryInfo entryInfo : entries) {

            log.trace("Entry: " + entryInfo);
            /* --- Write directory entry --- */
            RmpTools.writeFixedString(cout, entryInfo.name, 9);
            RmpTools.writeFixedString(cout, entryInfo.extendsion, 7);
            RmpTools.writeValue(cout, entryInfo.offset, 4);
            RmpTools.writeValue(cout, entryInfo.length, 4);
        }

        /* --- Write the header checksum (2 bytes) --- */
        cout.writeChecksum();

    }

    public void close() {
        try {
            rmpOutputFile.close();
        } catch (Exception e) {
            log.error("", e);
        }
    }

    public void delete() throws IOException {
        close();
        Utilities.deleteFile(rmpFile);
    }

    private static class EntryInfo {
        String name;
        String extendsion;
        long offset;
        long length;

        @Override
        public String toString() {
            return "\"" + name + "." + extendsion + "\" offset=" + offset + " length=" + length;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((extendsion == null) ? 0 : extendsion.hashCode());
            result = prime * result + ((name == null) ? 0 : name.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            EntryInfo other = (EntryInfo) obj;
            if (extendsion == null) {
                if (other.extendsion != null)
                    return false;
            } else if (!extendsion.equals(other.extendsion))
                return false;
            if (name == null) {
                if (other.name != null)
                    return false;
            } else if (!name.equals(other.name))
                return false;
            return true;
        }

    }
}