name.martingeisse.stackd.server.section.storage.FolderBasedSectionStorage.java Source code

Java tutorial

Introduction

Here is the source code for name.martingeisse.stackd.server.section.storage.FolderBasedSectionStorage.java

Source

/**
 * Copyright (c) 2010 Martin Geisse
 *
 * This file is distributed under the terms of the MIT license.
 */

package name.martingeisse.stackd.server.section.storage;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import name.martingeisse.stackd.common.geometry.ClusterSize;
import name.martingeisse.stackd.common.geometry.SectionId;
import org.apache.commons.io.IOUtils;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;

/**
 * This class is responsible for actually storing sections in files.
 * Each file contains a "super-section" with as many sections as a
 * section has cubes.
 */
public final class FolderBasedSectionStorage extends AbstractSectionStorage {

    /**
     * the storageFolder
     */
    private final File storageFolder;

    /**
     * the fileHandleCache
     */
    private final LoadingCache<SectionId, RandomAccessFile> fileHandleCache;

    /**
     * Constructor.
     * @param clusterSize the cluster-size of sections
     * @param storageFolder the folder that contains world storage files
     */
    public FolderBasedSectionStorage(final ClusterSize clusterSize, final File storageFolder) {
        super(clusterSize);
        this.storageFolder = storageFolder;
        this.fileHandleCache = CacheBuilder.newBuilder().maximumSize(100)
                .removalListener(new RemovalListener<SectionId, RandomAccessFile>() {
                    @Override
                    public void onRemoval(RemovalNotification<SectionId, RandomAccessFile> notification) {
                        try {
                            notification.getValue().close();
                        } catch (IOException e) {
                        }
                    }
                }).build(new CacheLoader<SectionId, RandomAccessFile>() {
                    @Override
                    public RandomAccessFile load(SectionId superSectionId) throws Exception {
                        File file = getSectionFile(superSectionId);

                        // create an empty file if there is none yet
                        if (!file.exists()) {
                            final FileOutputStream fileOutputStream = new FileOutputStream(file);
                            try {
                                final byte[] emptyToc = new byte[16 * 16 * 16 * 3 * 4];
                                fileOutputStream.write(emptyToc);
                            } finally {
                                fileOutputStream.close();
                            }
                        }

                        return new RandomAccessFile(file, "rw");
                    }
                });
    }

    /**
     * Getter method for the storageFolder.
     * @return the storageFolder
     */
    public File getStorageFolder() {
        return storageFolder;
    }

    /* (non-Javadoc)
     * @see name.martingeisse.stackd.server.section.AbstractSectionStorageBackend#loadSectionCubes0(name.martingeisse.stackd.common.geometry.SectionId[])
     */
    @Override
    public byte[][] loadSectionCubes0(SectionId[] sectionIds) {
        byte[][] result = new byte[sectionIds.length][];
        for (int i = 0; i < result.length; i++) {
            final SectionId sectionId = sectionIds[i];
            final SectionId superSectionId = getSuperSectionIdFromSectionId(sectionId);
            final int tocIndex = getSectionTocIndex(sectionId);
            final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            try {
                loadSectionFromFile(byteArrayOutputStream, fileHandleCache.get(superSectionId), tocIndex);
            } catch (final Exception e) {
                throw new RuntimeException(e);
            }
            result[i] = byteArrayOutputStream.toByteArray();
        }
        return result;
    }

    /* (non-Javadoc)
     * @see name.martingeisse.stackd.server.section.AbstractSectionStorageBackend#saveSection(java.io.InputStream, name.martingeisse.stackd.common.geometry.SectionId)
     */
    @Override
    public void saveSectionCubes0(SectionId sectionId, InputStream in) {
        final SectionId superSectionId = getSuperSectionIdFromSectionId(sectionId);
        final int tocIndex = getSectionTocIndex(sectionId);
        try {
            saveSectionToFile(in, fileHandleCache.get(superSectionId), tocIndex);
        } catch (final Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 
     */
    private boolean loadSectionFromFile(final OutputStream out, final RandomAccessFile access, final int tocIndex)
            throws IOException {

        // read the ToC entry
        access.seek(tocIndex * 12);
        final int dataStartAddress = access.readInt();
        final int dataSize = access.readInt();
        /* int dataFlags = */access.readInt();

        // handle missing sections
        if (dataStartAddress < 1) {
            return false;
        }

        // read the data
        final byte[] compressedCubeData = new byte[dataSize];
        access.seek(dataStartAddress);
        access.readFully(compressedCubeData);

        // write data to the stream
        out.write(compressedCubeData);

        return true;

    }

    /**
     * 
     */
    private void saveSectionToFile(final InputStream in, final RandomAccessFile access, final int tocIndex)
            throws IOException {

        // write the section to the end of the file
        final int dataAddress = (int) access.length();
        access.seek(dataAddress);
        final byte[] compressedCubeData = IOUtils.toByteArray(in);
        access.write(compressedCubeData);

        // update the ToC entry
        access.seek(tocIndex * 12);
        access.writeInt(dataAddress);
        access.writeInt(compressedCubeData.length);
        access.writeInt(0);

    }

    /**
     * Obtains the super-section ID from the section ID.
     * @param sectionId the section ID
     * @return the super-section ID
     */
    private SectionId getSuperSectionIdFromSectionId(final SectionId sectionId) {
        return new SectionId(sectionId.getX() >> 4, sectionId.getY() >> 4, sectionId.getZ() >> 4);
    }

    /**
     * Returns the file for the specified super-section.
     * 
     * @param id the super-section ID
     * @return the storage file
     */
    private File getSectionFile(final SectionId superSectionId) {
        return new File(storageFolder,
                "sc_" + superSectionId.getX() + "_" + superSectionId.getY() + "_" + superSectionId.getZ());
    }

    /**
     * Returns the index in the ToC of the section file that is used for
     * the section with the specified ID.
     * 
     * @param sectionId the section ID
     * @return the storage file
     */
    private int getSectionTocIndex(final SectionId sectionId) {
        final int x = (sectionId.getX() & 15);
        final int y = (sectionId.getY() & 15);
        final int z = (sectionId.getZ() & 15);
        return (x << 8) + (y << 4) + z;
    }

    /* (non-Javadoc)
     * @see name.martingeisse.stackd.server.section.AbstractSectionStorageBackend#saveSectionRenderModel0(name.martingeisse.stackd.common.geometry.SectionId, java.io.InputStream)
     */
    @Override
    public void saveSectionRenderModel0(SectionId sectionId, InputStream in) {
        throw new RuntimeException("not yet implemented");
    }

    /* (non-Javadoc)
     * @see name.martingeisse.stackd.server.section.AbstractSectionStorageBackend#deleteSectionRenderModel0(name.martingeisse.stackd.common.geometry.SectionId)
     */
    @Override
    public void deleteSectionRenderModel0(SectionId sectionId) {
        throw new RuntimeException("not yet implemented");
    }

}