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 public static String[][] getCacheStats(CacheAccess<String, BufferedImageCacheEntry> cache) { 088 Set<String> keySet = cache.getCacheControl().getKeySet(); 089 Map<String, int[]> temp = new ConcurrentHashMap<>(); // use int[] as a Object reference to int, gives better performance 090 for (String key: keySet) { 091 String[] keyParts = key.split(":", 2); 092 if (keyParts.length == 2) { 093 int[] counter = temp.get(keyParts[0]); 094 if (counter == null) { 095 temp.put(keyParts[0], new int[]{1}); 096 } else { 097 counter[0]++; 098 } 099 } else { 100 Logging.warn("Could not parse the key: {0}. No colon found", key); 101 } 102 } 103 104 List<Pair<String, Integer>> sortedStats = new ArrayList<>(); 105 for (Entry<String, int[]> e: temp.entrySet()) { 106 sortedStats.add(new Pair<>(e.getKey(), e.getValue()[0])); 107 } 108 sortedStats.sort(Comparator.comparing(o -> o.b, Comparator.reverseOrder())); 109 String[][] ret = new String[sortedStats.size()][3]; 110 int index = 0; 111 for (Pair<String, Integer> e: sortedStats) { 112 ret[index] = new String[]{e.a, e.b.toString(), tr("Clear")}; 113 index++; 114 } 115 return ret; 116 } 117 118 private static JTable getTableForCache(final CacheAccess<String, BufferedImageCacheEntry> cache, final TableModel tableModel) { 119 final JTable ret = new JTable(tableModel); 120 121 ButtonColumn buttonColumn = new ButtonColumn( 122 new AbstractAction() { 123 @Override 124 public void actionPerformed(ActionEvent e) { 125 int row = ret.convertRowIndexToModel(ret.getEditingRow()); 126 tableModel.setValueAt("0", row, 1); 127 cache.remove(ret.getValueAt(row, 0).toString() + ':'); 128 } 129 }); 130 TableColumn tableColumn = ret.getColumnModel().getColumn(2); 131 tableColumn.setCellRenderer(buttonColumn); 132 tableColumn.setCellEditor(buttonColumn); 133 return ret; 134 } 135 136 private static DefaultTableModel getTableModel(final CacheAccess<String, BufferedImageCacheEntry> cache) { 137 return new DefaultTableModel( 138 getCacheStats(cache), 139 new String[]{tr("Cache name"), tr("Object Count"), tr("Clear")}) { 140 @Override 141 public boolean isCellEditable(int row, int column) { 142 return column == 2; 143 } 144 }; 145 } 146}