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}