MemoryUtil.java 7.48 KB
/*
 * Decompiled with CFR 0_118.
 * 
 * Could not load the following classes:
 *  com.day.cq.dam.api.Asset
 *  org.apache.commons.io.FileUtils
 *  org.apache.commons.lang.StringUtils
 *  org.slf4j.Logger
 *  org.slf4j.LoggerFactory
 */
package com.day.cq.dam.commons.util;

import com.day.cq.dam.api.Asset;
import java.io.IOException;
import java.util.Map;
import javax.imageio.IIOException;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MemoryUtil {
    private static final String TIFF_BITS_PER_SAMPLE = "tiff:BitsPerSample";
    private static final String DAM_BITSPERPIXEL = "dam:Bitsperpixel";
    private static final String PHOTOSHOP_COLOR_MODE = "photoshop:ColorMode";
    private static final String TIFF_SAMPLES_PER_PIXEL = "tiff:SamplesPerPixel";
    private static final Logger log = LoggerFactory.getLogger(MemoryUtil.class);
    public static long MIN_CQ_MEMORY_REQUIREMENT = 104857600;
    private static final int DEFAULT_MAX_TRIALS = 100;

    public static <T> T tryUntilEnoughMemory(Asset asset, Callback<T> callback) {
        return MemoryUtil.tryUntilEnoughMemory(asset, 100, callback);
    }

    public static <T> T tryUntilEnoughMemory(Asset asset, int maxTrials, Callback<T> callback) {
        int trials;
        if (!MemoryUtil.hasEnoughSystemMemory(asset)) {
            log.warn("Failed loading image, insufficient memory. Increase heap size up to {} for asset: {}.", (Object)FileUtils.byteCountToDisplaySize((long)MemoryUtil.suggestMaxHeapSize(asset)), (Object)asset.getPath());
            return null;
        }
        int n = trials = maxTrials <= 0 ? 1 : maxTrials;
        while (trials > 0) {
            try {
                return callback.execute();
            }
            catch (IOException e) {
                if (e instanceof IIOException && e.getMessage().contains("Not enough memory")) {
                    --trials;
                    log.info("Insufficient memory, reloading image. Free memory: {}, asset: {}", (Object)FileUtils.byteCountToDisplaySize((long)Runtime.getRuntime().freeMemory()), (Object)asset.getPath());
                    try {
                        Thread.sleep((long)(2500.0 * (Math.random() + 0.5)));
                        continue;
                    }
                    catch (InterruptedException ie) {
                        return null;
                    }
                }
                log.warn("Error while loading image for asset {}: ", (Object)asset.getPath(), (Object)e);
                return null;
            }
        }
        log.warn("Failed loading image, insufficient memory even after {} trials for asset: {}", (Object)maxTrials, (Object)asset.getPath());
        return null;
    }

    public static boolean hasEnoughSystemMemory(Asset asset) {
        if (MemoryUtil.canCalculate(asset)) {
            long expectedImageMem = MemoryUtil.getExpectedImageMemory(asset);
            return Runtime.getRuntime().maxMemory() > MIN_CQ_MEMORY_REQUIREMENT + expectedImageMem;
        }
        log.debug("Cannot calculate memory requirements for " + asset.getPath());
        return true;
    }

    public static long getExpectedImageMemory(Asset asset) {
        if (MemoryUtil.canCalculate(asset)) {
            Long length = MemoryUtil.getAsLong(asset, "exif:PixelXDimension");
            Long width = MemoryUtil.getAsLong(asset, "exif:PixelYDimension");
            if (length == null && width == null) {
                length = MemoryUtil.getAsLong(asset, "tiff:ImageLength");
                width = MemoryUtil.getAsLong(asset, "tiff:ImageWidth");
            }
            if (length == null && width == null) {
                return 4 * width * length;
            }
        }
        return -1;
    }

    private static int getPixelSize(Asset asset, Map<String, Object> metadata) {
        int pixelSize = 4;
        int oneByte = 8;
        if (metadata.containsKey("dam:Bitsperpixel")) {
            pixelSize = (int)MemoryUtil.getAsLong(asset, metadata, "dam:Bitsperpixel");
            return pixelSize /= oneByte;
        }
        int bitsPerChannel = (int)MemoryUtil.getBitsPerSample(asset, metadata);
        int bytesPerChannel = bitsPerChannel / oneByte;
        if (metadata.containsKey("photoshop:ColorMode")) {
            int colorMode = (int)MemoryUtil.getAsLong(asset, metadata, "photoshop:ColorMode");
            switch (colorMode) {
                case 0: 
                case 1: 
                case 2: {
                    pixelSize = 1;
                    break;
                }
                case 3: {
                    pixelSize = 3;
                    break;
                }
                default: {
                    pixelSize = 4;
                }
            }
            return pixelSize * bytesPerChannel;
        }
        if (metadata.containsKey("tiff:SamplesPerPixel")) {
            pixelSize = (int)MemoryUtil.getAsLong(asset, metadata, "tiff:SamplesPerPixel");
            return pixelSize * bytesPerChannel;
        }
        return pixelSize;
    }

    private static long getBitsPerSample(Asset asset, Map<String, Object> metadata) {
        Object[] values;
        Object value = metadata.get("tiff:BitsPerSample");
        if (value instanceof Long) {
            return (Long)value;
        }
        if (value instanceof Object[] && (values = (Object[])value).length > 0) {
            return MemoryUtil.getAsLong(asset, values[0]);
        }
        return 8;
    }

    private static long getAsLong(Asset asset, Map<String, Object> metadata, String name) {
        String value = asset.getMetadataValue(name);
        Long retValue = null;
        try {
            retValue = Long.parseLong(value);
        }
        catch (NumberFormatException ignore) {
            // empty catch block
        }
        return retValue;
    }

    private static long getAsLong(Asset asset, Object value) {
        if (value instanceof Long) {
            return (Long)value;
        }
        if (value instanceof Double) {
            return ((Double)value).longValue();
        }
        if (value instanceof String) {
            String strVal = (String)value;
            try {
                return Long.valueOf(strVal);
            }
            catch (NumberFormatException nme) {
                log.debug("Cannot convert {} to number for asset {}", value, (Object)asset.getPath());
            }
        }
        return 0;
    }

    public static boolean hasEnoughMemory(Asset asset) {
        if (MemoryUtil.canCalculate(asset)) {
            long expectedImageMem = MemoryUtil.getExpectedImageMemory(asset);
            Runtime rt = Runtime.getRuntime();
            long maxFreeMem = rt.maxMemory() - (rt.totalMemory() - rt.freeMemory());
            return maxFreeMem >= expectedImageMem;
        }
        log.debug("Cannot calculate memory requirements for " + asset.getPath());
        return true;
    }

    public static long suggestMaxHeapSize(Asset asset) {
        return MemoryUtil.getExpectedImageMemory(asset) + MIN_CQ_MEMORY_REQUIREMENT;
    }

    private static boolean canCalculate(Asset asset) {
        return StringUtils.isNotEmpty((String)asset.getMetadataValue("tiff:ImageLength")) && StringUtils.isNotEmpty((String)asset.getMetadataValue("tiff:ImageWidth")) || StringUtils.isNotEmpty((String)asset.getMetadataValue("exif:PixelXDimension")) && StringUtils.isNotEmpty((String)asset.getMetadataValue("exif:PixelYDimension"));
    }

    public static interface Callback<T> {
        public T execute() throws IOException;
    }

}