Ticket #20141: 20141.2.patch

File 20141.2.patch, 21.4 KB (added by taylor.smock, 3 years ago)
  • src/org/openstreetmap/josm/data/cache/JCSCacheManager.java

    Subject: [PATCH] Fix #20141: ImageProvider: cache rendered SVG images using JCS
    ---
    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/org/openstreetmap/josm/data/cache/JCSCacheManager.java b/src/org/openstreetmap/josm/data/cache/JCSCacheManager.java
    a b  
    3030import org.openstreetmap.josm.data.preferences.BooleanProperty;
    3131import org.openstreetmap.josm.data.preferences.IntegerProperty;
    3232import org.openstreetmap.josm.spi.preferences.Config;
     33import org.openstreetmap.josm.tools.ImageResource;
    3334import org.openstreetmap.josm.tools.Logging;
    3435import org.openstreetmap.josm.tools.Utils;
    3536
     
    4950     */
    5051    public static final BooleanProperty USE_BLOCK_CACHE = new BooleanProperty(PREFERENCE_PREFIX + ".use_block_cache", true);
    5152
     53    /**
     54     * The preference key {@code jcs.cache.use_image_resource_cache} controls the caching mechanism used for {@link ImageResource}.
     55     * If set to {@code true}, a combined memory/disk is used. Otherwise, an in-memory-cache is used.
     56     */
     57    public static final BooleanProperty USE_IMAGE_RESOURCE_CACHE = new BooleanProperty(PREFERENCE_PREFIX + ".use_image_resource_cache", false);
     58
    5259    private static final AuxiliaryCacheFactory DISK_CACHE_FACTORY = getDiskCacheFactory();
    5360    private static FileLock cacheDirLock;
    5461
     
    173180     * @return cache access object
    174181     */
    175182    public static <K, V> CacheAccess<K, V> getCache(String cacheName) {
    176         return getCache(cacheName, DEFAULT_MAX_OBJECTS_IN_MEMORY.get().intValue(), 0, null);
     183        return getCache(cacheName, DEFAULT_MAX_OBJECTS_IN_MEMORY.get(), 0, null);
    177184    }
    178185
    179186    /**
     
    187194     * @return cache access object
    188195     */
    189196    public static <K, V> CacheAccess<K, V> getCache(String cacheName, int maxMemoryObjects, int maxDiskObjects, String cachePath) {
     197        // TODO Get block size in Java 9: Files.getFileStore(java.nio.file.Paths.get(cachePath)).getBlockSize();
     198        return getCache(cacheName, maxMemoryObjects, maxDiskObjects, cachePath,
     199                Boolean.TRUE.equals(USE_BLOCK_CACHE.get()) ? 4096 : 0);
     200    }
     201
     202    private static <K, V> CacheAccess<K, V> getCache(String cacheName, int maxMemoryObjects, int maxDiskObjects,
     203                                                     String cachePath, int blockSizeBytes) {
    190204        CacheAccess<K, V> cacheAccess = getCacheAccess(cacheName, getCacheAttributes(maxMemoryObjects));
    191205
    192206        if (cachePath != null && cacheDirLock != null && cacheAccess != null && DISK_CACHE_FACTORY != null) {
    193207            CompositeCache<K, V> cc = cacheAccess.getCacheControl();
    194208            try {
    195                 IDiskCacheAttributes diskAttributes = getDiskCacheAttributes(maxDiskObjects, cachePath, cacheName);
     209                IDiskCacheAttributes diskAttributes = getDiskCacheAttributes(maxDiskObjects, cachePath, cacheName, blockSizeBytes);
    196210                if (cc.getAuxCacheList().isEmpty()) {
    197211                    cc.setAuxCaches(Collections.singletonList(DISK_CACHE_FACTORY.createCache(
    198212                            diskAttributes, null, null, new StandardSerializer())));
     
    215229        }
    216230    }
    217231
     232    /**
     233     * Returns a cache for {@link ImageResource}
     234     * @param <K> key type
     235     * @param <V> value type
     236     * @return cache access object
     237     */
     238    public static <K, V> CacheAccess<K, V> getImageResourceCache() {
     239        if (Boolean.FALSE.equals(USE_IMAGE_RESOURCE_CACHE.get())) {
     240            return getCache("images", 16 * 1024, 0, null);
     241        }
     242        String cachePath = new File(Config.getDirs().getCacheDirectory(true), "images").getAbsolutePath();
     243        Logging.warn("Using experimental disk cache {0} for ImageResource", cachePath);
     244        return getCache("images", 16 * 1024, 512 * 1024, cachePath, 1024);
     245    }
     246
    218247    /**
    219248     * Close all files to ensure, that all indexes and data are properly written
    220249     */
     
    222251        JCS.shutdown();
    223252    }
    224253
    225     private static IDiskCacheAttributes getDiskCacheAttributes(int maxDiskObjects, String cachePath, String cacheName) {
     254    private static IDiskCacheAttributes getDiskCacheAttributes(int maxDiskObjects, String cachePath, String cacheName, int blockSizeBytes) {
    226255        IDiskCacheAttributes ret;
    227         removeStaleFiles(cachePath + File.separator + cacheName, useBlockCache() ? "_INDEX_v2" : "_BLOCK_v2");
    228         String newCacheName = cacheName + (useBlockCache() ? "_BLOCK_v2" : "_INDEX_v2");
     256        boolean isBlockDiskCache = blockSizeBytes > 0 && useBlockCache();
     257        removeStaleFiles(cachePath + File.separator + cacheName, isBlockDiskCache ? "_INDEX_v2" : "_BLOCK_v2");
     258        String newCacheName = cacheName + (isBlockDiskCache ? "_BLOCK_v2" : "_INDEX_v2");
    229259
    230         if (useBlockCache()) {
     260        if (isBlockDiskCache) {
    231261            BlockDiskCacheAttributes blockAttr = new BlockDiskCacheAttributes();
    232262            /*
    233263             * BlockDiskCache never optimizes the file, so when file size is reduced, it will never be truncated to desired size.
     
    241271            } else {
    242272                blockAttr.setMaxKeySize(maxDiskObjects);
    243273            }
    244             blockAttr.setBlockSizeBytes(4096); // use 4k blocks
     274            blockAttr.setBlockSizeBytes(blockSizeBytes);
    245275            ret = blockAttr;
    246276        } else {
    247277            IndexedDiskCacheAttributes indexAttr = new IndexedDiskCacheAttributes();
  • src/org/openstreetmap/josm/gui/MainInitialization.java

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/org/openstreetmap/josm/gui/MainInitialization.java b/src/org/openstreetmap/josm/gui/MainInitialization.java
    a b  
    152152                MainApplication.toolbar.refreshToolbarControl();
    153153                MainApplication.toolbar.control.updateUI();
    154154                MainApplication.contentPanePrivate.updateUI();
     155                // image provider statistics
     156                ImageProvider.printStatistics();
    155157            }))
    156158        );
    157159    }
  • src/org/openstreetmap/josm/tools/ImageProvider.java

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/org/openstreetmap/josm/tools/ImageProvider.java b/src/org/openstreetmap/josm/tools/ImageProvider.java
    a b  
    930930                    if (path == null) {
    931931                        continue;
    932932                    }
    933                     ir = getIfAvailableLocalURL(path, type);
     933                    ir = getIfAvailableLocalURL(subdir + name, path, type);
    934934                    if (ir != null) {
    935935                        cache.put(cacheName, ir);
    936936                        return ir;
     
    960960                    URI uri = getSvgUniverse().loadSVG(is, Utils.fileToURL(cf.getFile()).toString());
    961961                    svg = getSvgUniverse().getDiagram(uri);
    962962                }
    963                 return svg == null ? null : new ImageResource(svg);
     963                return svg == null ? null : new ImageResource(url, svg);
    964964            case OTHER:
    965965                BufferedImage img = null;
    966966                try {
     
    968968                } catch (IOException | UnsatisfiedLinkError e) {
    969969                    Logging.log(Logging.LEVEL_WARN, "Exception while reading HTTP image:", e);
    970970                }
    971                 return img == null ? null : new ImageResource(img);
     971                return img == null ? null : new ImageResource(url, img);
    972972            default:
    973973                throw new AssertionError("Unsupported type: " + type);
    974974            }
     
    10171017                    Logging.warn("Unable to process svg: "+s);
    10181018                    return null;
    10191019                }
    1020                 return new ImageResource(svg);
     1020                return new ImageResource(url, svg);
    10211021            } else {
    10221022                try {
    10231023                    // See #10479: for PNG files, always enforce transparency to be sure tNRS chunk is used even not in paletted mode
     
    10261026                    // hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/dc4322602480/src/share/classes/com/sun/imageio/plugins/png/PNGImageReader.java#l656
    10271027                    // CHECKSTYLE.ON: LineLength
    10281028                    Image img = read(new ByteArrayInputStream(bytes), false, true);
    1029                     return img == null ? null : new ImageResource(img);
     1029                    return img == null ? null : new ImageResource(url, img);
    10301030                } catch (IOException | UnsatisfiedLinkError e) {
    10311031                    Logging.log(Logging.LEVEL_WARN, "Exception while reading image:", e);
    10321032                }
     
    11011101                            URI uri = getSvgUniverse().loadSVG(is, entryName, true);
    11021102                            svg = getSvgUniverse().getDiagram(uri);
    11031103                        }
    1104                         return svg == null ? null : new ImageResource(svg);
     1104                        return svg == null ? null : new ImageResource(fullName, svg);
    11051105                    case OTHER:
    11061106                        while (size > 0) {
    11071107                            int l = is.read(buf, offs, size);
     
    11141114                        } catch (IOException | UnsatisfiedLinkError e) {
    11151115                            Logging.warn(e);
    11161116                        }
    1117                         return img == null ? null : new ImageResource(img);
     1117                        return img == null ? null : new ImageResource(fullName, img);
    11181118                    default:
    11191119                        throw new AssertionError("Unknown ImageType: "+type);
    11201120                    }
     
    11331133     * @param type data type of the image
    11341134     * @return the requested image or null if the request failed
    11351135     */
    1136     private static ImageResource getIfAvailableLocalURL(URL path, ImageType type) {
     1136    private static ImageResource getIfAvailableLocalURL(String cacheKey, URL path, ImageType type) {
    11371137        switch (type) {
    11381138        case SVG:
    1139             SVGDiagram svg = null;
    1140             synchronized (getSvgUniverse()) {
    1141                 try {
    1142                     URI uri = null;
    1143                     try {
    1144                         uri = getSvgUniverse().loadSVG(path);
    1145                     } catch (InvalidPathException e) {
    1146                         Logging.error("Cannot open {0}: {1}", path, e.getMessage());
    1147                         Logging.trace(e);
    1148                     }
    1149                     if (uri == null && "jar".equals(path.getProtocol())) {
    1150                         URL betterPath = Utils.betterJarUrl(path);
    1151                         if (betterPath != null) {
    1152                             uri = getSvgUniverse().loadSVG(betterPath);
    1153                         }
    1154                     }
    1155                     svg = getSvgUniverse().getDiagram(uri);
    1156                 } catch (SecurityException | IOException e) {
    1157                     Logging.log(Logging.LEVEL_WARN, "Unable to read SVG", e);
    1158                 }
    1159             }
    1160             return svg == null ? null : new ImageResource(svg);
     1139            return new ImageResource(cacheKey, () -> {
     1140                synchronized (getSvgUniverse()) {
     1141                    try {
     1142                        URI uri = null;
     1143                        try {
     1144                            uri = getSvgUniverse().loadSVG(path);
     1145                        } catch (InvalidPathException e) {
     1146                            Logging.error("Cannot open {0}: {1}", path, e.getMessage());
     1147                            Logging.trace(e);
     1148                        }
     1149                        if (uri == null && "jar".equals(path.getProtocol())) {
     1150                            URL betterPath = Utils.betterJarUrl(path);
     1151                            if (betterPath != null) {
     1152                                uri = getSvgUniverse().loadSVG(betterPath);
     1153                            }
     1154                        }
     1155                        return getSvgUniverse().getDiagram(uri);
     1156                    } catch (SecurityException | IOException e) {
     1157                        Logging.log(Logging.LEVEL_WARN, "Unable to read SVG", e);
     1158                    }
     1159                }
     1160                return null;
     1161            });
    11611162        case OTHER:
    11621163            BufferedImage img = null;
    11631164            try {
     
    11721173                Logging.log(Logging.LEVEL_WARN, "Unable to read image", e);
    11731174                Logging.debug(e);
    11741175            }
    1175             return img == null ? null : new ImageResource(img);
     1176            return img == null ? null : new ImageResource(path.toString(), img);
    11761177        default:
    11771178            throw new AssertionError();
    11781179        }
     
    13701371     * @since 6172
    13711372     */
    13721373    public static Image createBoundedImage(Image img, int maxSize) {
    1373         return new ImageResource(img).getImageIconBounded(new Dimension(maxSize, maxSize)).getImage();
     1374        return new ImageResource(img.toString(), img).getImageIconBounded(new Dimension(maxSize, maxSize)).getImage();
    13741375    }
    13751376
    13761377    /**
     
    19521953        return new ImageIcon(new BufferedImage(size.getAdjustedWidth(), size.getAdjustedHeight(), BufferedImage.TYPE_INT_ARGB));
    19531954    }
    19541955
     1956    /**
     1957     * Prints statistics concerning the image loading caches.
     1958     */
     1959    public static void printStatistics() {
     1960        Logging.info(getSvgUniverse().statistics());
     1961        Logging.info(ImageResource.statistics());
     1962    }
     1963
    19551964    @Override
    19561965    public String toString() {
    19571966        return ("ImageProvider ["
  • src/org/openstreetmap/josm/tools/ImageResource.java

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/org/openstreetmap/josm/tools/ImageResource.java b/src/org/openstreetmap/josm/tools/ImageResource.java
    a b  
    44import java.awt.Dimension;
    55import java.awt.Image;
    66import java.awt.image.BufferedImage;
     7import java.io.IOException;
     8import java.io.UncheckedIOException;
    79import java.util.List;
    8 import java.util.Map;
    9 import java.util.concurrent.ConcurrentHashMap;
     10import java.util.Locale;
     11import java.util.Objects;
     12import java.util.function.Supplier;
    1013
    1114import javax.swing.AbstractAction;
    1215import javax.swing.Action;
     
    1518import javax.swing.JPanel;
    1619import javax.swing.UIManager;
    1720
     21import org.apache.commons.jcs3.access.behavior.ICacheAccess;
     22import org.openstreetmap.josm.data.cache.BufferedImageCacheEntry;
     23import org.openstreetmap.josm.data.cache.JCSCacheManager;
    1824import org.openstreetmap.josm.tools.ImageProvider.ImageSizes;
    1925
    2026import com.kitfox.svg.SVGDiagram;
     
    3036public class ImageResource {
    3137
    3238    /**
    33      * Caches the image data for resized versions of the same image. The key is obtained using {@link ImageResizeMode#cacheKey(Dimension)}.
     39     * Caches the image data for resized versions of the same image.
     40     * Depending on {@link JCSCacheManager#USE_IMAGE_RESOURCE_CACHE}, a combined memory/disk, or an in-memory-cache is used.
    3441     */
    35     private final Map<Integer, BufferedImage> imgCache = new ConcurrentHashMap<>(4);
     42    private static final ICacheAccess<String, BufferedImageCacheEntry> imgCache = JCSCacheManager.getImageResourceCache();
     43
     44    private final String cacheKey;
    3645    /**
    3746     * SVG diagram information in case of SVG vector image.
    3847     */
    3948    private SVGDiagram svg;
    4049    /**
     50     * Supplier for SVG diagram information in case of possibly cached SVG vector image.
     51     */
     52    private Supplier<SVGDiagram> svgSupplier;
     53    /**
    4154     * Use this dimension to request original file dimension.
    4255     */
    4356    public static final Dimension DEFAULT_DIMENSION = new Dimension(-1, -1);
     
    5669
    5770    /**
    5871     * Constructs a new {@code ImageResource} from an image.
     72     * @param cacheKey the caching identifier of the image
    5973     * @param img the image
    6074     */
    61     public ImageResource(Image img) {
    62         CheckParameterUtil.ensureParameterNotNull(img);
    63         baseImage = img;
     75    public ImageResource(String cacheKey, Image img) {
     76        this.cacheKey = Objects.requireNonNull(cacheKey);
     77        this.baseImage = Objects.requireNonNull(img);
    6478    }
    6579
    6680    /**
    6781     * Constructs a new {@code ImageResource} from SVG data.
     82     * @param cacheKey the caching identifier of the image
    6883     * @param svg SVG data
    6984     */
    70     public ImageResource(SVGDiagram svg) {
    71         CheckParameterUtil.ensureParameterNotNull(svg);
    72         this.svg = svg;
     85    public ImageResource(String cacheKey, SVGDiagram svg) {
     86        this.cacheKey = Objects.requireNonNull(cacheKey);
     87        this.svg = Objects.requireNonNull(svg);
     88    }
     89
     90    public ImageResource(String cacheKey, Supplier<SVGDiagram> svgSupplier) {
     91        this.cacheKey = Objects.requireNonNull(cacheKey);
     92        this.svgSupplier = Objects.requireNonNull(svgSupplier);
    7393    }
    7494
    7595    /**
     
    7999     * @since 8095
    80100     */
    81101    public ImageResource(ImageResource res, List<ImageOverlay> overlayInfo) {
     102        this.cacheKey = res.cacheKey;
    82103        this.svg = res.svg;
     104        this.svgSupplier = res.svgSupplier;
    83105        this.baseImage = res.baseImage;
    84106        this.overlayInfo = overlayInfo;
    85107    }
     
    165187        return getImageIconAlreadyScaled(GuiSizesHelper.getDimensionDpiAdjusted(dim), multiResolution, false, resizeMode);
    166188    }
    167189
     190    private BufferedImage getImageFromCache(String cacheKey) {
     191        try {
     192            BufferedImageCacheEntry image = imgCache.get(cacheKey);
     193            if (image == null || image.getImage() == null) {
     194                return null;
     195            }
     196            Logging.trace("{0} is in cache :-)", cacheKey);
     197            return image.getImage();
     198        } catch (IOException e) {
     199            throw new UncheckedIOException(e);
     200        }
     201    }
     202
    168203    /**
    169204     * Get an ImageIcon object for the image of this resource. A potential UI scaling is assumed
    170205     * to be already taken care of, so dim is already scaled accordingly.
     
    187222        } else if (resizeMode == null) {
    188223            resizeMode = ImageResizeMode.BOUNDED;
    189224        }
    190         final int cacheKey = resizeMode.cacheKey(dim);
    191         BufferedImage img = imgCache.get(cacheKey);
     225        final String cacheKey = String.format(Locale.ROOT, "%s--%s--%d--%d",
     226                this.cacheKey, resizeMode.name(), dim.width, dim.height);
     227        BufferedImage img = getImageFromCache(cacheKey);
    192228        if (img == null) {
     229            if (svgSupplier != null) {
     230                svg = svgSupplier.get();
     231                Logging.trace("{0} is not in cache :-(", cacheKey);
     232                svgSupplier = null;
     233            }
    193234            if (svg != null) {
    194235                img = ImageProvider.createImageFromSvg(svg, dim, resizeMode);
    195236                if (img == null) {
     
    221262                img = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_4BYTE_ABGR);
    222263                disabledIcon.paintIcon(new JPanel(), img.getGraphics(), 0, 0);
    223264            }
    224             imgCache.put(cacheKey, img);
     265            if (img == null) {
     266                return null;
     267            }
     268            BufferedImageCacheEntry cacheEntry = BufferedImageCacheEntry.pngEncoded(img);
     269            Logging.trace("Storing {0} ({1} bytes) in cache...", cacheKey, cacheEntry.getContent().length);
     270            imgCache.put(cacheKey, cacheEntry);
    225271        }
    226272
    227273        if (!multiResolution || svg == null)
     
    261307        return getImageIcon(iconSize, true, ImageResizeMode.PADDED);
    262308    }
    263309
     310    static String statistics() {
     311        return String.format("ImageResource cache: [%s]", imgCache.getStatistics());
     312    }
     313
    264314    @Override
    265315    public String toString() {
    266316        return "ImageResource ["
  • test/unit/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetReaderTest.java

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/test/unit/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetReaderTest.java b/test/unit/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetReaderTest.java
    a b  
    8989     * @throws IOException if any I/O error occurs
    9090     */
    9191    @Test
    92     void testReadDefaulPresets() throws SAXException, IOException {
     92    void testReadDefaultPresets() throws SAXException, IOException {
    9393        String presetfile = "resource://data/defaultpresets.xml";
    9494        final Collection<TaggingPreset> presets = TaggingPresetReader.readAll(presetfile, true);
    9595        assertTrue(presets.size() > 0, "Default presets are empty");
     96        TaggingPresetsTest.waitForIconLoading(presets);
    9697    }
    9798}