Ticket #20141: 20141.2.patch
| File 20141.2.patch, 21.4 KB (added by , 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 30 30 import org.openstreetmap.josm.data.preferences.BooleanProperty; 31 31 import org.openstreetmap.josm.data.preferences.IntegerProperty; 32 32 import org.openstreetmap.josm.spi.preferences.Config; 33 import org.openstreetmap.josm.tools.ImageResource; 33 34 import org.openstreetmap.josm.tools.Logging; 34 35 import org.openstreetmap.josm.tools.Utils; 35 36 … … 49 50 */ 50 51 public static final BooleanProperty USE_BLOCK_CACHE = new BooleanProperty(PREFERENCE_PREFIX + ".use_block_cache", true); 51 52 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 52 59 private static final AuxiliaryCacheFactory DISK_CACHE_FACTORY = getDiskCacheFactory(); 53 60 private static FileLock cacheDirLock; 54 61 … … 173 180 * @return cache access object 174 181 */ 175 182 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); 177 184 } 178 185 179 186 /** … … 187 194 * @return cache access object 188 195 */ 189 196 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) { 190 204 CacheAccess<K, V> cacheAccess = getCacheAccess(cacheName, getCacheAttributes(maxMemoryObjects)); 191 205 192 206 if (cachePath != null && cacheDirLock != null && cacheAccess != null && DISK_CACHE_FACTORY != null) { 193 207 CompositeCache<K, V> cc = cacheAccess.getCacheControl(); 194 208 try { 195 IDiskCacheAttributes diskAttributes = getDiskCacheAttributes(maxDiskObjects, cachePath, cacheName );209 IDiskCacheAttributes diskAttributes = getDiskCacheAttributes(maxDiskObjects, cachePath, cacheName, blockSizeBytes); 196 210 if (cc.getAuxCacheList().isEmpty()) { 197 211 cc.setAuxCaches(Collections.singletonList(DISK_CACHE_FACTORY.createCache( 198 212 diskAttributes, null, null, new StandardSerializer()))); … … 215 229 } 216 230 } 217 231 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 218 247 /** 219 248 * Close all files to ensure, that all indexes and data are properly written 220 249 */ … … 222 251 JCS.shutdown(); 223 252 } 224 253 225 private static IDiskCacheAttributes getDiskCacheAttributes(int maxDiskObjects, String cachePath, String cacheName ) {254 private static IDiskCacheAttributes getDiskCacheAttributes(int maxDiskObjects, String cachePath, String cacheName, int blockSizeBytes) { 226 255 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"); 229 259 230 if ( useBlockCache()) {260 if (isBlockDiskCache) { 231 261 BlockDiskCacheAttributes blockAttr = new BlockDiskCacheAttributes(); 232 262 /* 233 263 * BlockDiskCache never optimizes the file, so when file size is reduced, it will never be truncated to desired size. … … 241 271 } else { 242 272 blockAttr.setMaxKeySize(maxDiskObjects); 243 273 } 244 blockAttr.setBlockSizeBytes( 4096); // use 4k blocks274 blockAttr.setBlockSizeBytes(blockSizeBytes); 245 275 ret = blockAttr; 246 276 } else { 247 277 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 152 152 MainApplication.toolbar.refreshToolbarControl(); 153 153 MainApplication.toolbar.control.updateUI(); 154 154 MainApplication.contentPanePrivate.updateUI(); 155 // image provider statistics 156 ImageProvider.printStatistics(); 155 157 })) 156 158 ); 157 159 } -
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 930 930 if (path == null) { 931 931 continue; 932 932 } 933 ir = getIfAvailableLocalURL( path, type);933 ir = getIfAvailableLocalURL(subdir + name, path, type); 934 934 if (ir != null) { 935 935 cache.put(cacheName, ir); 936 936 return ir; … … 960 960 URI uri = getSvgUniverse().loadSVG(is, Utils.fileToURL(cf.getFile()).toString()); 961 961 svg = getSvgUniverse().getDiagram(uri); 962 962 } 963 return svg == null ? null : new ImageResource( svg);963 return svg == null ? null : new ImageResource(url, svg); 964 964 case OTHER: 965 965 BufferedImage img = null; 966 966 try { … … 968 968 } catch (IOException | UnsatisfiedLinkError e) { 969 969 Logging.log(Logging.LEVEL_WARN, "Exception while reading HTTP image:", e); 970 970 } 971 return img == null ? null : new ImageResource( img);971 return img == null ? null : new ImageResource(url, img); 972 972 default: 973 973 throw new AssertionError("Unsupported type: " + type); 974 974 } … … 1017 1017 Logging.warn("Unable to process svg: "+s); 1018 1018 return null; 1019 1019 } 1020 return new ImageResource( svg);1020 return new ImageResource(url, svg); 1021 1021 } else { 1022 1022 try { 1023 1023 // See #10479: for PNG files, always enforce transparency to be sure tNRS chunk is used even not in paletted mode … … 1026 1026 // hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/dc4322602480/src/share/classes/com/sun/imageio/plugins/png/PNGImageReader.java#l656 1027 1027 // CHECKSTYLE.ON: LineLength 1028 1028 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); 1030 1030 } catch (IOException | UnsatisfiedLinkError e) { 1031 1031 Logging.log(Logging.LEVEL_WARN, "Exception while reading image:", e); 1032 1032 } … … 1101 1101 URI uri = getSvgUniverse().loadSVG(is, entryName, true); 1102 1102 svg = getSvgUniverse().getDiagram(uri); 1103 1103 } 1104 return svg == null ? null : new ImageResource( svg);1104 return svg == null ? null : new ImageResource(fullName, svg); 1105 1105 case OTHER: 1106 1106 while (size > 0) { 1107 1107 int l = is.read(buf, offs, size); … … 1114 1114 } catch (IOException | UnsatisfiedLinkError e) { 1115 1115 Logging.warn(e); 1116 1116 } 1117 return img == null ? null : new ImageResource( img);1117 return img == null ? null : new ImageResource(fullName, img); 1118 1118 default: 1119 1119 throw new AssertionError("Unknown ImageType: "+type); 1120 1120 } … … 1133 1133 * @param type data type of the image 1134 1134 * @return the requested image or null if the request failed 1135 1135 */ 1136 private static ImageResource getIfAvailableLocalURL( URL path, ImageType type) {1136 private static ImageResource getIfAvailableLocalURL(String cacheKey, URL path, ImageType type) { 1137 1137 switch (type) { 1138 1138 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 }); 1161 1162 case OTHER: 1162 1163 BufferedImage img = null; 1163 1164 try { … … 1172 1173 Logging.log(Logging.LEVEL_WARN, "Unable to read image", e); 1173 1174 Logging.debug(e); 1174 1175 } 1175 return img == null ? null : new ImageResource( img);1176 return img == null ? null : new ImageResource(path.toString(), img); 1176 1177 default: 1177 1178 throw new AssertionError(); 1178 1179 } … … 1370 1371 * @since 6172 1371 1372 */ 1372 1373 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(); 1374 1375 } 1375 1376 1376 1377 /** … … 1952 1953 return new ImageIcon(new BufferedImage(size.getAdjustedWidth(), size.getAdjustedHeight(), BufferedImage.TYPE_INT_ARGB)); 1953 1954 } 1954 1955 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 1955 1964 @Override 1956 1965 public String toString() { 1957 1966 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 4 4 import java.awt.Dimension; 5 5 import java.awt.Image; 6 6 import java.awt.image.BufferedImage; 7 import java.io.IOException; 8 import java.io.UncheckedIOException; 7 9 import java.util.List; 8 import java.util.Map; 9 import java.util.concurrent.ConcurrentHashMap; 10 import java.util.Locale; 11 import java.util.Objects; 12 import java.util.function.Supplier; 10 13 11 14 import javax.swing.AbstractAction; 12 15 import javax.swing.Action; … … 15 18 import javax.swing.JPanel; 16 19 import javax.swing.UIManager; 17 20 21 import org.apache.commons.jcs3.access.behavior.ICacheAccess; 22 import org.openstreetmap.josm.data.cache.BufferedImageCacheEntry; 23 import org.openstreetmap.josm.data.cache.JCSCacheManager; 18 24 import org.openstreetmap.josm.tools.ImageProvider.ImageSizes; 19 25 20 26 import com.kitfox.svg.SVGDiagram; … … 30 36 public class ImageResource { 31 37 32 38 /** 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. 34 41 */ 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; 36 45 /** 37 46 * SVG diagram information in case of SVG vector image. 38 47 */ 39 48 private SVGDiagram svg; 40 49 /** 50 * Supplier for SVG diagram information in case of possibly cached SVG vector image. 51 */ 52 private Supplier<SVGDiagram> svgSupplier; 53 /** 41 54 * Use this dimension to request original file dimension. 42 55 */ 43 56 public static final Dimension DEFAULT_DIMENSION = new Dimension(-1, -1); … … 56 69 57 70 /** 58 71 * Constructs a new {@code ImageResource} from an image. 72 * @param cacheKey the caching identifier of the image 59 73 * @param img the image 60 74 */ 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); 64 78 } 65 79 66 80 /** 67 81 * Constructs a new {@code ImageResource} from SVG data. 82 * @param cacheKey the caching identifier of the image 68 83 * @param svg SVG data 69 84 */ 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); 73 93 } 74 94 75 95 /** … … 79 99 * @since 8095 80 100 */ 81 101 public ImageResource(ImageResource res, List<ImageOverlay> overlayInfo) { 102 this.cacheKey = res.cacheKey; 82 103 this.svg = res.svg; 104 this.svgSupplier = res.svgSupplier; 83 105 this.baseImage = res.baseImage; 84 106 this.overlayInfo = overlayInfo; 85 107 } … … 165 187 return getImageIconAlreadyScaled(GuiSizesHelper.getDimensionDpiAdjusted(dim), multiResolution, false, resizeMode); 166 188 } 167 189 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 168 203 /** 169 204 * Get an ImageIcon object for the image of this resource. A potential UI scaling is assumed 170 205 * to be already taken care of, so dim is already scaled accordingly. … … 187 222 } else if (resizeMode == null) { 188 223 resizeMode = ImageResizeMode.BOUNDED; 189 224 } 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); 192 228 if (img == null) { 229 if (svgSupplier != null) { 230 svg = svgSupplier.get(); 231 Logging.trace("{0} is not in cache :-(", cacheKey); 232 svgSupplier = null; 233 } 193 234 if (svg != null) { 194 235 img = ImageProvider.createImageFromSvg(svg, dim, resizeMode); 195 236 if (img == null) { … … 221 262 img = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_4BYTE_ABGR); 222 263 disabledIcon.paintIcon(new JPanel(), img.getGraphics(), 0, 0); 223 264 } 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); 225 271 } 226 272 227 273 if (!multiResolution || svg == null) … … 261 307 return getImageIcon(iconSize, true, ImageResizeMode.PADDED); 262 308 } 263 309 310 static String statistics() { 311 return String.format("ImageResource cache: [%s]", imgCache.getStatistics()); 312 } 313 264 314 @Override 265 315 public String toString() { 266 316 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 89 89 * @throws IOException if any I/O error occurs 90 90 */ 91 91 @Test 92 void testReadDefaul Presets() throws SAXException, IOException {92 void testReadDefaultPresets() throws SAXException, IOException { 93 93 String presetfile = "resource://data/defaultpresets.xml"; 94 94 final Collection<TaggingPreset> presets = TaggingPresetReader.readAll(presetfile, true); 95 95 assertTrue(presets.size() > 0, "Default presets are empty"); 96 TaggingPresetsTest.waitForIconLoading(presets); 96 97 } 97 98 }
