name.martingeisse.stackd.server.section.SectionWorkingSet.java Source code

Java tutorial

Introduction

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

Source

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

package name.martingeisse.stackd.server.section;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import name.martingeisse.stackd.common.cubes.Cubes;
import name.martingeisse.stackd.common.cubes.UniformCubes;
import name.martingeisse.stackd.common.geometry.ClusterSize;
import name.martingeisse.stackd.common.network.SectionDataId;
import name.martingeisse.stackd.server.network.StackdServer;
import name.martingeisse.stackd.server.section.entry.InteractiveSectionImageCacheEntry;
import name.martingeisse.stackd.server.section.entry.SectionCubesCacheEntry;
import name.martingeisse.stackd.server.section.entry.SectionDataCacheEntry;
import name.martingeisse.stackd.server.section.storage.AbstractSectionStorage;
import org.apache.commons.collections.iterators.ArrayIterator;
import sun.reflect.generics.reflectiveObjects.NotImplementedException;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableMap;

/**
 * This class is the front-end to section storage. It maintains a cache of recently
 * used section-related objects as well as an {@link AbstractSectionStorage} in the
 * background that handles actual storage of the sections.
 * 
 * Note that since objects are just cached, not permanently stored, fetching an
 * object may return a different object than before. This implies that code should
 * not hold on an old instance to avoid concurrent modification on two different
 * section objects, with save operations overwriting each other's changes.
 * 
 * The cache keeps section-related data objects using their {@link SectionDataId}.
 * Each entry is the actual data, wrapped in a {@link SectionDataCacheEntry}
 * subclass instance.
 * 
 * TODO save on evict
 */
public final class SectionWorkingSet {

    /**
     * the server
     */
    private final StackdServer<?> server;

    /**
     * the storage
     */
    private final AbstractSectionStorage storage;

    /**
     * the cache
     */
    private final LoadingCache<SectionDataId, SectionDataCacheEntry> cache;

    /**
     * Constructor.
     * @param server the server that uses this storage
     * @param storageFolder the storage folder to use for actually storing sections
     */
    public SectionWorkingSet(final StackdServer<?> server, final AbstractSectionStorage storageFolder) {
        this.server = server;
        this.storage = storageFolder;
        this.cache = CacheBuilder.newBuilder().maximumSize(1000)
                .build(new CacheLoader<SectionDataId, SectionDataCacheEntry>() {

                    @Override
                    public SectionDataCacheEntry load(final SectionDataId sectionDataId) throws Exception {
                        final byte[] data = storage.loadSectionRelatedObject(sectionDataId);
                        return createOrUnserialize(sectionDataId, data);
                    }

                    @Override
                    public Map<SectionDataId, SectionDataCacheEntry> loadAll(
                            final Iterable<? extends SectionDataId> keys) throws Exception {
                        final ArrayList<SectionDataId> ids = new ArrayList<SectionDataId>();
                        for (final SectionDataId key : keys) {
                            ids.add(key);
                        }
                        final Map<SectionDataId, byte[]> dataMap = storage.loadSectionRelatedObjects(ids);
                        final Map<SectionDataId, SectionDataCacheEntry> result = new HashMap<SectionDataId, SectionDataCacheEntry>();
                        for (SectionDataId key : keys) {
                            result.put(key, createOrUnserialize(key, dataMap.get(key)));
                        }
                        return result;
                    }

                });
    }

    /**
     * Getter method for the clusterSize.
     * @return the clusterSize
     */
    public ClusterSize getClusterSize() {
        return storage.getClusterSize();
    }

    /**
     * Getter method for the server.
     * @return the server
     */
    public StackdServer<?> getServer() {
        return server;
    }

    /**
     * Getter method for the storage.
     * @return the storage
     */
    public AbstractSectionStorage getStorage() {
        return storage;
    }

    /**
     * Removes all cached objects.
     */
    public void clearCache() {
        cache.invalidateAll();
    }

    /**
     * Returns a single object, loading it if necessary.
     * 
     * @param sectionDataId the section data ID
     * @return the section-related object
     */
    public SectionDataCacheEntry get(final SectionDataId sectionDataId) {
        try {
            return cache.get(sectionDataId);
        } catch (final ExecutionException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Returns a single object if present in the cache, null if not present.
     * 
     * @param sectionDataId the section data ID
     * @return the section-related object or null
     */
    public SectionDataCacheEntry getIfPresent(final SectionDataId sectionDataId) {
        return cache.getIfPresent(sectionDataId);
    }

    /**
     * Returns multiple objects, loading them if necessary.
     * 
     * @param sectionDataIds the section data IDs
     * @return the section-related objects
     */
    public ImmutableMap<SectionDataId, SectionDataCacheEntry> getAll(final SectionDataId... sectionDataIds) {
        try {
            return cache.getAll(new Iterable<SectionDataId>() {
                @Override
                @SuppressWarnings("unchecked")
                public Iterator<SectionDataId> iterator() {
                    return new ArrayIterator(sectionDataIds);
                }
            });
        } catch (final ExecutionException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Of multiple objects, returns those that are already present in the cache.
     * 
     * @param sectionDataIds the section data IDs
     * @return the cached section-related objects
     */
    public ImmutableMap<SectionDataId, SectionDataCacheEntry> getAllPresent(final SectionDataId... sectionDataIds) {
        return cache.getAllPresent(new Iterable<SectionDataId>() {
            @Override
            @SuppressWarnings("unchecked")
            public Iterator<SectionDataId> iterator() {
                return new ArrayIterator(sectionDataIds);
            }
        });
    }

    /**
     * Returns multiple objects, loading them if necessary.
     * 
     * @param sectionDataIds the section data IDs
     * @return the section-related objects
     */
    public ImmutableMap<SectionDataId, SectionDataCacheEntry> getAll(final Iterable<SectionDataId> sectionDataIds) {
        try {
            return cache.getAll(sectionDataIds);
        } catch (final ExecutionException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Of multiple objects, returns those that are already present in the cache.
     * 
     * @param sectionDataIds the section data IDs
     * @return the section-related objects
     */
    public ImmutableMap<SectionDataId, SectionDataCacheEntry> getAllPresent(
            final Iterable<SectionDataId> sectionDataIds) {
        return cache.getAllPresent(sectionDataIds);
    }

    /**
     * Pre-caches the objects for the specified IDs.
     * 
     * @param sectionDataIds the section data IDs
     */
    public final void precache(final SectionDataId[] sectionDataIds) {

        // step through the IDs and only keep those for which the object isn't yet cached
        final List<SectionDataId> missingIds = new ArrayList<>();
        for (final SectionDataId sectionDataId : sectionDataIds) {
            if (cache.getIfPresent(sectionDataId) == null) {
                missingIds.add(sectionDataId);
            }
        }
        if (missingIds.isEmpty()) {
            return;
        }

        // load the section-related objects and store them in the cache
        final Map<SectionDataId, byte[]> datas = storage.loadSectionRelatedObjects(missingIds);
        for (final Map.Entry<SectionDataId, byte[]> dataEntry : datas.entrySet()) {
            final SectionDataId sectionDataId = dataEntry.getKey();
            final SectionDataCacheEntry entry = createOrUnserialize(sectionDataId, dataEntry.getValue());
            cache.asMap().putIfAbsent(sectionDataId, entry);
        }

    }

    /**
     * Calls createDefault() or unserialize(), depending on whether the data
     * argument is null.
     */
    private SectionDataCacheEntry createOrUnserialize(final SectionDataId sectionDataId, final byte[] data) {
        if (data == null) {
            return createDefault(sectionDataId);
        } else {
            return unserializeForLoad(sectionDataId, data);
        }
    }

    /**
     * Creates a default object for an ID that does not have that object in storage.
     * 
     * Note: there is currently no way to mark such objects as dirty before they have
     * been placed into the cache, in case this function creates an object in a
     * non-deterministic way. Just calling markModified() on the returned object
     * is NOT correct as it creates a race condition with the worker thread that
     * actually saves objects.
     * --
     * In case this gets implemented, the object should recognize that it was not added to
     * the cache yet and handle this in markModified() (that is, add a task item when it
     * later gets added). This should not require another method because the caller could
     * still use markModified() accidentally which would be incorrect.
     * 
     * @param sectionDataId the section data ID
     * @return the cache entry
     */
    private SectionDataCacheEntry createDefault(final SectionDataId sectionDataId) {
        switch (sectionDataId.getType()) {

        case DEFINITIVE:
            return new SectionCubesCacheEntry(this, sectionDataId, new UniformCubes((byte) 0));

        case INTERACTIVE:
            return new InteractiveSectionImageCacheEntry(this, sectionDataId, null);

        case VIEW_LOD_0:
            throw new NotImplementedException();

        default:
            throw new IllegalArgumentException("invalid section data type in: " + sectionDataId);

        }
    }

    /**
     * Creates a cached object from a serialized representation.
     * 
     * @param sectionDataId the section data ID
     * @param data the loaded data
     * @return the cache entry
     */
    protected SectionDataCacheEntry unserializeForLoad(final SectionDataId sectionDataId, final byte[] data) {
        switch (sectionDataId.getType()) {

        case DEFINITIVE: {
            final Cubes sectionCubes = Cubes.createFromCompressedData(this.getClusterSize(), data);
            return new SectionCubesCacheEntry(this, sectionDataId, sectionCubes);
        }

        case INTERACTIVE:
            return new InteractiveSectionImageCacheEntry(this, sectionDataId, data);

        case VIEW_LOD_0:
            throw new NotImplementedException();

        default:
            throw new IllegalArgumentException("invalid section data type in: " + sectionDataId);

        }
    }

}