org.geoserver.jai.ConcurrentTileFactory.java Source code

Java tutorial

Introduction

Here is the source code for org.geoserver.jai.ConcurrentTileFactory.java

Source

/* Copyright (c) 2001 - 2013 OpenPlans - www.openplans.org. All rights reserved.
 * This code is licensed under the GPL 2.0 license, available at the root
 * application directory.
 */
package org.geoserver.jai;

import java.awt.Point;
import java.awt.image.ComponentSampleModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferInt;
import java.awt.image.DataBufferShort;
import java.awt.image.DataBufferUShort;
import java.awt.image.MultiPixelPackedSampleModel;
import java.awt.image.Raster;
import java.awt.image.SampleModel;
import java.awt.image.SinglePixelPackedSampleModel;
import java.awt.image.WritableRaster;
import java.lang.ref.SoftReference;
import java.util.Arrays;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.media.jai.TileFactory;
import javax.media.jai.TileRecycler;

import org.apache.commons.beanutils.PropertyUtils;
import org.geotools.util.logging.Logging;

import com.sun.media.jai.util.DataBufferUtils;

/**
 * A thread safe recycling tile factory that using Java 5 Concurrent data structures
 * 
 * @author Andrea Aime - GeoSolutions
 */
public class ConcurrentTileFactory implements TileFactory, TileRecycler {

    /**
     * Cache of the tile recycled arrays
     */
    private volatile ArrayCache recycledArrays = new ArrayCache();

    /**
     * A concurrent multimap geared towards tile data array caching
     * 
     * @author Andrea Aime - GeoSolutions
     * 
     */
    private static class ArrayCache extends ConcurrentHashMap<Long, ConcurrentLinkedQueue<SoftReference<?>>> {
        private static final long serialVersionUID = -6905685668738379653L;

        static final Logger LOGGER = Logging.getLogger(ConcurrentTileFactory.class);

        /**
         * Retrieve an array of the specified type and length.
         */
        Object getRecycledArray(int arrayType, long numBanks, long arrayLength) {
            Long key = getKey(arrayType, numBanks, arrayLength);

            ConcurrentLinkedQueue<SoftReference<?>> arrays = get(key);

            if (arrays != null) {
                SoftReference<?> arrayRef;
                while ((arrayRef = arrays.poll()) != null) {
                    Object array = arrayRef.get();
                    if (array != null) {
                        if (LOGGER.isLoggable(Level.FINER)) {
                            LOGGER.log(Level.FINER, "Recycling tile hit on type:{1}, banks: {2}, arrayLength: {3}",
                                    new Object[] { arrayType, numBanks, arrayLength });
                        }
                        return array;
                    }
                }
            }

            if (LOGGER.isLoggable(Level.FINER)) {
                LOGGER.log(Level.FINER, "Recycling tile miss on type:{1}, banks: {2}, arrayLength: {3}",
                        new Object[] { arrayType, numBanks, arrayLength });
            }
            return null;
        }

        private Long getKey(int arrayType, long numBanks, long arrayLength) {
            return Long.valueOf((((long) arrayType << 56) | numBanks << 32 | arrayLength));
        }

        public void recycleTile(Raster tile) {
            DataBuffer db = tile.getDataBuffer();

            Long key = getKey(db.getDataType(), db.getNumBanks(), db.getSize());
            if (LOGGER.isLoggable(Level.FINER)) {
                LOGGER.log(Level.FINER, "Recycling tile hit on type:{1}, banks: {2}, arrayLength: {3}",
                        new Object[] { db.getDataType(), db.getNumBanks(), db.getSize() });
            }

            ConcurrentLinkedQueue<SoftReference<?>> arrays = get(key);
            if (arrays == null) {
                arrays = new ConcurrentLinkedQueue<SoftReference<?>>();
                arrays.add(getBankReference(db));
                put(key, arrays);
                return;
            } else {
                arrays.add(getBankReference(db));
            }
        }

        /**
         * Returns a <code>SoftReference</code> to the actual data stored into the DataBuffer
         */
        private static SoftReference<?> getBankReference(DataBuffer db) {
            try {
                Object array = PropertyUtils.getProperty(db, "bankData");
                return new SoftReference<Object>(array);
            } catch (Exception e) {
                throw new UnsupportedOperationException("Unknown data buffer type " + db);
            }
        }

    }

    /**
     * Constructs a <code>RecyclingTileFactory</code>.
     */
    public ConcurrentTileFactory() {
    }

    /**
     * Returns <code>true</code>.
     */
    public boolean canReclaimMemory() {
        return true;
    }

    /**
     * Returns <code>true</code>.
     */
    public boolean isMemoryCache() {
        return true;
    }

    /**
     * Always returns -1, does not do used memory accounting
     */
    public long getMemoryUsed() {
        return -1;
    }

    /**
     * Clean up the cache
     */
    public void flush() {
        recycledArrays.clear();
    }

    /**
     * Builds a new tile, eventually recycling the data array backing it
     */
    public WritableRaster createTile(SampleModel sampleModel, Point location) {
        // sanity checks
        if (sampleModel == null) {
            throw new NullPointerException("sampleModel cannot be null");
        }
        if (location == null) {
            location = new Point(0, 0);
        }

        DataBuffer db = null;

        // get the three elements making the key into the recycled array map
        int type = sampleModel.getTransferType();
        long numBanks = 0;
        long size = 0;
        if (sampleModel instanceof ComponentSampleModel) {
            ComponentSampleModel csm = (ComponentSampleModel) sampleModel;
            numBanks = getNumBanksCSM(csm);
            size = getBufferSizeCSM(csm);
        } else if (sampleModel instanceof MultiPixelPackedSampleModel) {
            MultiPixelPackedSampleModel mppsm = (MultiPixelPackedSampleModel) sampleModel;
            numBanks = 1;
            int dataTypeSize = DataBuffer.getDataTypeSize(type);
            size = mppsm.getScanlineStride() * mppsm.getHeight()
                    + (mppsm.getDataBitOffset() + dataTypeSize - 1) / dataTypeSize;
        } else if (sampleModel instanceof SinglePixelPackedSampleModel) {
            SinglePixelPackedSampleModel sppsm = (SinglePixelPackedSampleModel) sampleModel;
            numBanks = 1;
            size = sppsm.getScanlineStride() * (sppsm.getHeight() - 1) + sppsm.getWidth();
        }

        if (size > 0) {
            // try to build a new data buffer starting from 
            Object array = recycledArrays.getRecycledArray(type, numBanks, size);
            if (array != null) {
                switch (type) {
                case DataBuffer.TYPE_BYTE: {
                    byte[][] bankData = (byte[][]) array;
                    for (int i = 0; i < numBanks; i++) {
                        Arrays.fill(bankData[i], (byte) 0);
                    }
                    db = new DataBufferByte(bankData, (int) size);
                }
                    break;
                case DataBuffer.TYPE_USHORT: {
                    short[][] bankData = (short[][]) array;
                    for (int i = 0; i < numBanks; i++) {
                        Arrays.fill(bankData[i], (short) 0);
                    }
                    db = new DataBufferUShort(bankData, (int) size);
                }
                    break;
                case DataBuffer.TYPE_SHORT: {
                    short[][] bankData = (short[][]) array;
                    for (int i = 0; i < numBanks; i++) {
                        Arrays.fill(bankData[i], (short) 0);
                    }
                    db = new DataBufferShort(bankData, (int) size);
                }
                    break;
                case DataBuffer.TYPE_INT: {
                    int[][] bankData = (int[][]) array;
                    for (int i = 0; i < numBanks; i++) {
                        Arrays.fill(bankData[i], 0);
                    }
                    db = new DataBufferInt(bankData, (int) size);
                }
                    break;
                case DataBuffer.TYPE_FLOAT: {
                    float[][] bankData = (float[][]) array;
                    for (int i = 0; i < numBanks; i++) {
                        Arrays.fill(bankData[i], 0.0F);
                    }
                    db = DataBufferUtils.createDataBufferFloat(bankData, (int) size);
                }
                    break;
                case DataBuffer.TYPE_DOUBLE: {
                    double[][] bankData = (double[][]) array;
                    for (int i = 0; i < numBanks; i++) {
                        Arrays.fill(bankData[i], 0.0);
                    }
                    db = DataBufferUtils.createDataBufferDouble(bankData, (int) size);
                }
                    break;
                default:
                    throw new IllegalArgumentException("Unknown array type");
                }
            }
        }

        if (db == null) {
            db = sampleModel.createDataBuffer();
        }

        return Raster.createWritableRaster(sampleModel, db, location);
    }

    static long getBufferSizeCSM(ComponentSampleModel csm) {
        int[] bandOffsets = csm.getBandOffsets();
        int maxBandOff = bandOffsets[0];
        for (int i = 1; i < bandOffsets.length; i++)
            maxBandOff = Math.max(maxBandOff, bandOffsets[i]);

        long size = 0;
        if (maxBandOff >= 0)
            size += maxBandOff + 1;
        int pixelStride = csm.getPixelStride();
        if (pixelStride > 0)
            size += pixelStride * (csm.getWidth() - 1);
        int scanlineStride = csm.getScanlineStride();
        if (scanlineStride > 0)
            size += scanlineStride * (csm.getHeight() - 1);
        return size;
    }

    private static long getNumBanksCSM(ComponentSampleModel csm) {
        int[] bankIndices = csm.getBankIndices();
        int maxIndex = bankIndices[0];
        for (int i = 1; i < bankIndices.length; i++) {
            int bankIndex = bankIndices[i];
            if (bankIndex > maxIndex) {
                maxIndex = bankIndex;
            }
        }
        return maxIndex + 1;
    }

    /**
     * Recycle the given tile.
     */
    public void recycleTile(Raster tile) {
        if (tile.getWidth() != tile.getHeight() || tile.getWidth() > 512) {
            return;
        }
        recycledArrays.recycleTile(tile);
    }

}