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.Main; 030import org.openstreetmap.josm.data.cache.BufferedImageCacheEntry; 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.Pair; 038 039/** 040 * Panel for cache content management. 041 * 042 * @author Wiktor Niesiobędzki 043 * 044 */ 045public class CacheContentsPanel extends JPanel { 046 047 /** 048 * Creates cache content panel 049 */ 050 public CacheContentsPanel() { 051 super(new GridBagLayout()); 052 Main.worker.submit(() -> { 053 addToPanel(TMSLayer.getCache(), "TMS"); 054 addToPanel(WMSLayer.getCache(), "WMS"); 055 addToPanel(WMTSLayer.getCache(), "WMTS"); 056 }); 057 } 058 059 private void addToPanel(final CacheAccess<String, BufferedImageCacheEntry> cache, final String name) { 060 final Long cacheSize = getCacheSize(cache); 061 final TableModel tableModel = getTableModel(cache); 062 063 GuiHelper.runInEDT(() -> { 064 add(new JLabel(tr("{0} cache, total cache size: {1} bytes", name, cacheSize)), 065 GBC.eol().insets(5, 5, 0, 0)); 066 add(new JScrollPane(getTableForCache(cache, tableModel)), 067 GBC.eol().fill(GBC.BOTH)); 068 }); 069 } 070 071 private static Long getCacheSize(CacheAccess<String, BufferedImageCacheEntry> cache) { 072 ICacheStats stats = cache.getStatistics(); 073 for (IStats cacheStats: stats.getAuxiliaryCacheStats()) { 074 for (IStatElement<?> statElement: cacheStats.getStatElements()) { 075 if ("Data File Length".equals(statElement.getName())) { 076 Object val = statElement.getData(); 077 if (val instanceof Long) { 078 return (Long) val; 079 } 080 } 081 } 082 } 083 return Long.valueOf(-1); 084 } 085 086 public static String[][] getCacheStats(CacheAccess<String, BufferedImageCacheEntry> cache) { 087 Set<String> keySet = cache.getCacheControl().getKeySet(); 088 Map<String, int[]> temp = new ConcurrentHashMap<>(); // use int[] as a Object reference to int, gives better performance 089 for (String key: keySet) { 090 String[] keyParts = key.split(":", 2); 091 if (keyParts.length == 2) { 092 int[] counter = temp.get(keyParts[0]); 093 if (counter == null) { 094 temp.put(keyParts[0], new int[]{1}); 095 } else { 096 counter[0]++; 097 } 098 } else { 099 Main.warn("Could not parse the key: {0}. No colon found", key); 100 } 101 } 102 103 List<Pair<String, Integer>> sortedStats = new ArrayList<>(); 104 for (Entry<String, int[]> e: temp.entrySet()) { 105 sortedStats.add(new Pair<>(e.getKey(), e.getValue()[0])); 106 } 107 sortedStats.sort(Comparator.comparing(o -> o.b, Comparator.reverseOrder())); 108 String[][] ret = new String[sortedStats.size()][3]; 109 int index = 0; 110 for (Pair<String, Integer> e: sortedStats) { 111 ret[index] = new String[]{e.a, e.b.toString(), tr("Clear")}; 112 index++; 113 } 114 return ret; 115 } 116 117 private static JTable getTableForCache(final CacheAccess<String, BufferedImageCacheEntry> cache, final TableModel tableModel) { 118 final JTable ret = new JTable(tableModel); 119 120 ButtonColumn buttonColumn = new ButtonColumn( 121 new AbstractAction() { 122 @Override 123 public void actionPerformed(ActionEvent e) { 124 int row = ret.convertRowIndexToModel(ret.getEditingRow()); 125 tableModel.setValueAt("0", row, 1); 126 cache.remove(ret.getValueAt(row, 0).toString() + ':'); 127 } 128 }); 129 TableColumn tableColumn = ret.getColumnModel().getColumn(2); 130 tableColumn.setCellRenderer(buttonColumn); 131 tableColumn.setCellEditor(buttonColumn); 132 return ret; 133 } 134 135 private static DefaultTableModel getTableModel(final CacheAccess<String, BufferedImageCacheEntry> cache) { 136 return new DefaultTableModel( 137 getCacheStats(cache), 138 new String[]{tr("Cache name"), tr("Object Count"), tr("Clear")}) { 139 @Override 140 public boolean isCellEditable(int row, int column) { 141 return column == 2; 142 } 143 }; 144 } 145}