All Downloads are FREE. Search and download functionalities are using the official Maven repository.

gov.nasa.worldwind.data.CachedDataRaster Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2012 United States Government as represented by the Administrator of the
 * National Aeronautics and Space Administration.
 * All Rights Reserved.
 */

package gov.nasa.worldwind.data;

import gov.nasa.worldwind.avlist.*;
import gov.nasa.worldwind.cache.*;
import gov.nasa.worldwind.exception.WWRuntimeException;
import gov.nasa.worldwind.geom.Sector;
import gov.nasa.worldwind.util.*;

import java.io.IOException;
import java.text.MessageFormat;
import java.util.logging.Level;

/**
 * The CachedDataRaster is used to hold data raster's source and metadata, while the actual data raster may
 * not be loaded in to the memory. This is mostly used together with a memory caches. CachedDataRaster
 * actually implements all interfaces of the DataRaster, and acts as a proxy, that loads a real data raster
 * only when it is actually needed.
 *
 * @author Lado Garakanidze
 * @version $Id: CachedDataRaster.java 3037 2015-04-17 23:08:47Z tgaskins $
 */
public class CachedDataRaster extends AVListImpl implements DataRaster
{
    protected enum ErrorHandlerMode
    {
        ALLOW_EXCEPTIONS, DISABLE_EXCEPTIONS
    }

    protected Object dataSource = null;
    protected DataRasterReader dataReader = null;

    protected MemoryCache rasterCache = null;
    protected MemoryCache.CacheListener cacheListener = null;

    protected final Object rasterUsageLock = new Object();
    protected final Object rasterRetrievalLock = new Object();

    protected String[] requiredKeys = new String[] {AVKey.SECTOR, AVKey.PIXEL_FORMAT};

    /**
     * Create a cached data raster.
     *
     * @param source the location of the local file, expressed as either a String path, a File, or a file URL.
     * @param params metadata as AVList, it is expected to next parameters: AVKey.WIDTH, AVKey.HEIGHT, AVKey.SECTOR,
     *               AVKey.PIXEL_FORMAT.
     *               

* If any of these keys is missing, there will be an attempt made to retrieve missign metadata from * the source using the reader. * @param reader A reference to a DataRasterReader instance * @param cache A reference to a MemoryCache instance * * @throws java.io.IOException thrown if there is an error to read metadata from the source * @throws IllegalArgumentException thrown when a source or a reader are null */ public CachedDataRaster(Object source, AVList params, DataRasterReader reader, MemoryCache cache) throws java.io.IOException, IllegalArgumentException { if (source == null) { String message = Logging.getMessage("nullValue.SourceIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (reader == null) { String message = Logging.getMessage("nullValue.ReaderIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } params = (null == params) ? new AVListImpl() : params; this.assembleMetadata(source, params, reader); this.dataSource = source; this.dataReader = reader; this.setValues(params.copy()); this.rasterCache = cache; if (this.rasterCache != null) { this.cacheListener = new CacheListener(this.dataSource); this.rasterCache.addCacheListener(this.cacheListener); } } protected void assembleMetadata(Object source, AVList params, DataRasterReader reader) throws java.io.IOException, IllegalArgumentException { if (!this.hasRequiredMetadata(params, ErrorHandlerMode.DISABLE_EXCEPTIONS)) { if (!reader.canRead(source, params)) { String message = Logging.getMessage("DataRaster.CannotRead", source); Logging.logger().severe(message); throw new java.io.IOException(message); } if (!this.hasRequiredMetadata(params, ErrorHandlerMode.DISABLE_EXCEPTIONS)) { reader.readMetadata(source, params); this.hasRequiredMetadata(params, ErrorHandlerMode.ALLOW_EXCEPTIONS); } } } protected String[] getRequiredKeysList() { return this.requiredKeys; } /** * Validates if params (AVList) has all required keys. * * @param params AVList of key/value pairs * @param throwException specifies weather to throw exception when a key/value is missing, or just return false. * * @return TRUE, if all required keys are present in the params list, or both params and required keys are empty, * otherwise returns FALSE (if throwException is false) * * @throws IllegalArgumentException If a key/value is missing and throwException is set to TRUE */ protected boolean hasRequiredMetadata(AVList params, ErrorHandlerMode throwException) throws IllegalArgumentException { String[] keys = this.getRequiredKeysList(); if (null == params || params.getEntries().size() == 0) { // return TRUE if required keys is empty, otherwise return FALSE return (null == keys || keys.length == 0); } if (null != keys && keys.length > 0) { for (String key : keys) { Object value = params.getValue(key); if (WWUtil.isEmpty(value)) { if (throwException == ErrorHandlerMode.ALLOW_EXCEPTIONS) { String message = Logging.getMessage("generic.MissingRequiredParameter", key); Logging.logger().finest(message); throw new IllegalArgumentException(message); } else return false; } } } return true; } public int getWidth() { Object o = this.getValue(AVKey.WIDTH); if (null != o && o instanceof Integer) return (Integer) o; throw new WWRuntimeException(Logging.getMessage("generic.MissingRequiredParameter", AVKey.WIDTH)); } public int getHeight() { Object o = this.getValue(AVKey.HEIGHT); if (null != o && o instanceof Integer) return (Integer) o; throw new WWRuntimeException(Logging.getMessage("generic.MissingRequiredParameter", AVKey.HEIGHT)); } public Sector getSector() { Object o = this.getValue(AVKey.SECTOR); if (null != o && o instanceof Sector) return (Sector) o; throw new WWRuntimeException(Logging.getMessage("generic.MissingRequiredParameter", AVKey.SECTOR)); } public Object getDataSource() { return this.dataSource; } public AVList getParams() { return this.getMetadata(); } public AVList getMetadata() { return this.copy(); } public DataRasterReader getDataRasterReader() { return this.dataReader; } public void dispose() { String message = Logging.getMessage("generic.ExceptionWhileDisposing", this.dataSource); Logging.logger().severe(message); throw new IllegalStateException(message); } protected DataRaster[] getDataRasters() throws IOException, WWRuntimeException { synchronized (this.rasterRetrievalLock) { DataRaster[] rasters = (this.rasterCache != null) ? (DataRaster[]) this.rasterCache.getObject(this.dataSource) : null; if (null != rasters) return rasters; // prevent an attempt to re-read rasters which failed to load if (this.rasterCache == null || !this.rasterCache.contains(this.dataSource)) { long memoryDelta = 0L; try { AVList rasterParams = this.copy(); try { long before = getTotalUsedMemory(); rasters = this.dataReader.read(this.getDataSource(), rasterParams); memoryDelta = getTotalUsedMemory() - before; } catch (OutOfMemoryError e) { Logging.logger().finest(this.composeExceptionReason(e)); this.releaseMemory(); // let's retry after the finalization and GC long before = getTotalUsedMemory(); rasters = this.dataReader.read(this.getDataSource(), rasterParams); memoryDelta = getTotalUsedMemory() - before; } } catch (Throwable t) { disposeRasters(rasters); // cleanup in case of exception rasters = null; String message = Logging.getMessage("DataRaster.CannotRead", this.composeExceptionReason(t)); Logging.logger().severe(message); throw new WWRuntimeException(message); } finally { // Add rasters to the cache, even if "rasters" is null to prevent multiple failed reads. if (this.rasterCache != null) { long totalBytes = getSizeInBytes(rasters); totalBytes = (memoryDelta > totalBytes) ? memoryDelta : totalBytes; if (totalBytes > 0L) this.rasterCache.add(this.dataSource, rasters, totalBytes); } } } if (null == rasters || rasters.length == 0) { String message = Logging.getMessage("generic.CannotCreateRaster", this.getDataSource()); Logging.logger().severe(message); throw new WWRuntimeException(message); } return rasters; } } public void drawOnTo(DataRaster canvas) { synchronized (this.rasterUsageLock) { try { DataRaster[] rasters; try { rasters = this.getDataRasters(); for (DataRaster raster : rasters) { raster.drawOnTo(canvas); } } catch (OutOfMemoryError e) { Logging.logger().finest(this.composeExceptionReason(e)); this.releaseMemory(); rasters = this.getDataRasters(); for (DataRaster raster : rasters) { raster.drawOnTo(canvas); } } } catch (Throwable t) { String reason = this.composeExceptionReason(t); Logging.logger().log(Level.SEVERE, reason, t); } } } public DataRaster getSubRaster(AVList params) { synchronized (this.rasterUsageLock) { try { DataRaster[] rasters; try { rasters = this.getDataRasters(); return rasters[0].getSubRaster(params); } catch (OutOfMemoryError e) { Logging.logger().finest(this.composeExceptionReason(e)); this.releaseMemory(); // let's retry after the finalization and GC rasters = this.getDataRasters(); return rasters[0].getSubRaster(params); } } catch (Throwable t) { String reason = this.composeExceptionReason(t); Logging.logger().log(Level.SEVERE, reason, t); } String message = Logging.getMessage("generic.CannotCreateRaster", this.getDataSource()); Logging.logger().severe(message); throw new WWRuntimeException(message); } } public DataRaster getSubRaster(int width, int height, Sector sector, AVList params) { if (null == params) params = new AVListImpl(); params.setValue(AVKey.WIDTH, width); params.setValue(AVKey.HEIGHT, height); params.setValue(AVKey.SECTOR, sector); return this.getSubRaster(params); } protected void releaseMemory() { if (this.rasterCache != null) this.rasterCache.clear(); System.runFinalization(); System.gc(); Thread.yield(); } protected String composeExceptionReason(Throwable t) { StringBuffer sb = new StringBuffer(); if (null != this.dataSource) sb.append(this.dataSource).append(" : "); sb.append(WWUtil.extractExceptionReason(t)); return sb.toString(); } protected long getSizeInBytes(DataRaster[] rasters) { long totalBytes = 0L; if (rasters != null) { for (DataRaster raster : rasters) { if (raster != null && raster instanceof Cacheable) totalBytes += ((Cacheable) raster).getSizeInBytes(); } } return totalBytes; } protected static void disposeRasters(DataRaster[] rasters) { if (rasters != null) { for (DataRaster raster : rasters) { raster.dispose(); } } } private static class CacheListener implements MemoryCache.CacheListener { private Object key; private CacheListener(Object key) { this.key = key; } public void entryRemoved(Object key, Object clientObject) { if (key != this.key) return; if (clientObject == null || !(clientObject instanceof DataRaster[])) { String message = MessageFormat.format("Cannot dispose {0}", clientObject); Logging.logger().warning(message); return; } try { disposeRasters((DataRaster[]) clientObject); } catch (Exception e) { String message = Logging.getMessage("generic.ExceptionWhileDisposing", clientObject); Logging.logger().log(java.util.logging.Level.SEVERE, message, e); } } public void removalException(Throwable t, Object key, Object clientObject) { String reason = t.getMessage(); reason = (WWUtil.isEmpty(reason) && null != t.getCause()) ? t.getCause().getMessage() : reason; String msg = Logging.getMessage("BasicMemoryCache.ExceptionFromRemovalListener", reason); Logging.logger().info(msg); } } protected static long getTotalUsedMemory() { Runtime runtime = Runtime.getRuntime(); return (runtime.totalMemory() - runtime.freeMemory()); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy