001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.preferences.imagery; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.GridBagLayout; 007import java.awt.event.ActionEvent; 008import java.util.ArrayList; 009import java.util.Comparator; 010import java.util.List; 011import java.util.Map; 012import java.util.Map.Entry; 013import java.util.Set; 014import java.util.concurrent.ConcurrentHashMap; 015 016import javax.swing.AbstractAction; 017import javax.swing.JLabel; 018import javax.swing.JPanel; 019import javax.swing.JScrollPane; 020import javax.swing.JTable; 021import javax.swing.table.DefaultTableModel; 022import javax.swing.table.TableColumn; 023import javax.swing.table.TableModel; 024 025import org.apache.commons.jcs.access.CacheAccess; 026import org.apache.commons.jcs.engine.stats.behavior.ICacheStats; 027import org.apache.commons.jcs.engine.stats.behavior.IStatElement; 028import org.apache.commons.jcs.engine.stats.behavior.IStats; 029import org.openstreetmap.josm.data.cache.BufferedImageCacheEntry; 030import org.openstreetmap.josm.gui.MainApplication; 031import org.openstreetmap.josm.gui.layer.TMSLayer; 032import org.openstreetmap.josm.gui.layer.WMSLayer; 033import org.openstreetmap.josm.gui.layer.WMTSLayer; 034import org.openstreetmap.josm.gui.util.GuiHelper; 035import org.openstreetmap.josm.gui.widgets.ButtonColumn; 036import org.openstreetmap.josm.tools.GBC; 037import org.openstreetmap.josm.tools.Logging; 038import org.openstreetmap.josm.tools.Pair; 039 040/** 041 * Panel for cache content management. 042 * 043 * @author Wiktor Niesiobędzki 044 * 045 */ 046public class CacheContentsPanel extends JPanel { 047 048 /** 049 * Creates cache content panel 050 */ 051 public CacheContentsPanel() { 052 super(new GridBagLayout()); 053 MainApplication.worker.submit(() -> { 054 addToPanel(TMSLayer.getCache(), "TMS"); 055 addToPanel(WMSLayer.getCache(), "WMS"); 056 addToPanel(WMTSLayer.getCache(), "WMTS"); 057 }); 058 } 059 060 private void addToPanel(final CacheAccess<String, BufferedImageCacheEntry> cache, final String name) { 061 final Long cacheSize = getCacheSize(cache); 062 final TableModel tableModel = getTableModel(cache); 063 064 GuiHelper.runInEDT(() -> { 065 add(new JLabel(tr("{0} cache, total cache size: {1} bytes", name, cacheSize)), 066 GBC.eol().insets(5, 5, 0, 0)); 067 add(new JScrollPane(getTableForCache(cache, tableModel)), 068 GBC.eol().fill(GBC.BOTH)); 069 }); 070 } 071 072 private static Long getCacheSize(CacheAccess<String, BufferedImageCacheEntry> cache) { 073 ICacheStats stats = cache.getStatistics(); 074 for (IStats cacheStats: stats.getAuxiliaryCacheStats()) { 075 for (IStatElement<?> statElement: cacheStats.getStatElements()) { 076 if ("Data File Length".equals(statElement.getName())) { 077 Object val = statElement.getData(); 078 if (val instanceof Long) { 079 return (Long) val; 080 } 081 } 082 } 083 } 084 return Long.valueOf(-1); 085 } 086 087 /** 088 * Returns the cache stats. 089 * @param cache imagery cache 090 * @return the cache stats 091 */ 092 public static String[][] getCacheStats(CacheAccess<String, BufferedImageCacheEntry> cache) { 093 Set<String> keySet = cache.getCacheControl().getKeySet(); 094 Map<String, int[]> temp = new ConcurrentHashMap<>(); // use int[] as a Object reference to int, gives better performance 095 for (String key: keySet) { 096 String[] keyParts = key.split(":", 2); 097 if (keyParts.length == 2) { 098 int[] counter = temp.get(keyParts[0]); 099 if (counter == null) { 100 temp.put(keyParts[0], new int[]{1}); 101 } else { 102 counter[0]++; 103 } 104 } else { 105 Logging.warn("Could not parse the key: {0}. No colon found", key); 106 } 107 } 108 109 List<Pair<String, Integer>> sortedStats = new ArrayList<>(); 110 for (Entry<String, int[]> e: temp.entrySet()) { 111 sortedStats.add(new Pair<>(e.getKey(), e.getValue()[0])); 112 } 113 sortedStats.sort(Comparator.comparing(o -> o.b, Comparator.reverseOrder())); 114 String[][] ret = new String[sortedStats.size()][3]; 115 int index = 0; 116 for (Pair<String, Integer> e: sortedStats) { 117 ret[index] = new String[]{e.a, e.b.toString(), tr("Clear")}; 118 index++; 119 } 120 return ret; 121 } 122 123 private static JTable getTableForCache(final CacheAccess<String, BufferedImageCacheEntry> cache, final TableModel tableModel) { 124 final JTable ret = new JTable(tableModel); 125 126 ButtonColumn buttonColumn = new ButtonColumn( 127 new AbstractAction() { 128 @Override 129 public void actionPerformed(ActionEvent e) { 130 int row = ret.convertRowIndexToModel(ret.getEditingRow()); 131 tableModel.setValueAt("0", row, 1); 132 cache.remove(ret.getValueAt(row, 0).toString() + ':'); 133 } 134 }); 135 TableColumn tableColumn = ret.getColumnModel().getColumn(2); 136 tableColumn.setCellRenderer(buttonColumn); 137 tableColumn.setCellEditor(buttonColumn); 138 return ret; 139 } 140 141 private static DefaultTableModel getTableModel(final CacheAccess<String, BufferedImageCacheEntry> cache) { 142 return new DefaultTableModel( 143 getCacheStats(cache), 144 new String[]{tr("Cache name"), tr("Object Count"), tr("Clear")}) { 145 @Override 146 public boolean isCellEditable(int row, int column) { 147 return column == 2; 148 } 149 }; 150 } 151}