/**************************************************************************** * Copyright (C) 2012 ecsec GmbH. * All rights reserved. * Contact: ecsec GmbH (info@ecsec.de) * * This file is part of the Open eCard App. * * GNU General Public License Usage * This file may be used under the terms of the GNU General Public * License version 3.0 as published by the Free Software Foundation * and appearing in the file LICENSE.GPL included in the packaging of * this file. Please review the following information to ensure the * GNU General Public License version 3.0 requirements will be met: * http://www.gnu.org/copyleft/gpl.html. * * Other Usage * Alternatively, this file may be used in accordance with the terms * and conditions contained in a signed written agreement between * you and ecsec GmbH. * ***************************************************************************/ package org.openecard.common.util; import java.util.Collection; import java.util.Date; import java.util.Iterator; import java.util.Map; import java.util.Set; /** * * @author Tobias Wich <tobias.wich@ecsec.de> */ public class SelfCleaningMap<K extends Comparable, V> implements Map<K, V> { private static final long rerun = 30 * 1000; private final long kill; private final RemoveActionFactory<V> actionFactory; private Map<K,Entry> _map; public <M extends Map> SelfCleaningMap(Class<M> c) throws InstantiationException, IllegalAccessException { this(c, 15 * 60); }; public <M extends Map> SelfCleaningMap(Class<M> c, RemoveActionFactory<V> actionFactory) throws InstantiationException, IllegalAccessException { this(c, actionFactory, 15 * 60); }; /** * * @param c * @param lifetime in minutes * @throws InstantiationException * @throws IllegalAccessException */ public <M extends Map> SelfCleaningMap(Class<M> c, int lifetime) throws InstantiationException, IllegalAccessException { this(c, null, lifetime); }; public <M extends Map> SelfCleaningMap(Class<M> c, RemoveActionFactory<V> actionFactory, int lifetime) throws InstantiationException, IllegalAccessException { this.kill = lifetime * 1000; this.actionFactory = actionFactory; this._map = c.newInstance(); }; private boolean hasAction() { return this.actionFactory != null; } private RemoveAction<V> getAction(V v) { return actionFactory.create(v); } private class Entry { public final V v; public final Date d; public Entry(V v) { this.v = v; this.d = new Date(); } public Entry(V v, Date d) { this.v = v; this.d = d; } }; private Cleaner cleaner = null; private class Cleaner extends Thread { @Override public void run() { synchronized (this) { while (!_map.isEmpty()) { // only run as long as there are entries in the map try { wait(rerun); // block 30 seconds long now = System.currentTimeMillis(); Iterator<Map.Entry<K, Entry>> i = _map.entrySet().iterator(); while (i.hasNext()) { Map.Entry<K, Entry> e = i.next(); long old = e.getValue().d.getTime(); if ((now - old) > kill) { // remove entry if (hasAction()) { getAction(e.getValue().v).perform(); } i.remove(); // notify all listening objects V v = e.getValue().v; synchronized (v) { v.notifyAll(); } } } } catch (InterruptedException ex) { // nobody cares } } // done delete this thread from outer class cleaner = null; } } }; @Override public synchronized int size() { return _map.size(); } @Override public synchronized boolean isEmpty() { return _map.isEmpty(); } @Override public synchronized void putAll(Map m) { Iterator<Map.Entry> i = m.entrySet().iterator(); while (i.hasNext()) { Map.Entry next = i.next(); SelfCleaningMap.Entry e = new SelfCleaningMap.Entry(next.getValue()); _map.put((K) next.getKey(), e); } } @Override public synchronized void clear() { Iterator<Map.Entry<K, Entry>> i = _map.entrySet().iterator(); while (i.hasNext()) { Map.Entry<K, Entry> e = i.next(); // remove entry if (hasAction()) { getAction(e.getValue().v).perform(); } i.remove(); // notify all listening objects V v = e.getValue().v; synchronized (v) { v.notifyAll(); } } } @Override public Set keySet() { throw new UnsupportedOperationException("It is not safe to request lists of objects which may get deleted in another thread."); } @Override public Collection values() { throw new UnsupportedOperationException("It is not safe to request lists of objects which may get deleted in another thread."); } @Override public Set entrySet() { throw new UnsupportedOperationException("It is not safe to request lists of objects which may get deleted in another thread."); } @Override public synchronized boolean containsKey(Object key) { boolean result = _map.containsKey((K) key); if (result == true) { // update access time get((K) key); } return result; } @Override public synchronized boolean containsValue(Object value) { Iterator<Map.Entry<K,Entry>> i = _map.entrySet().iterator(); while (i.hasNext()) { Map.Entry<K,Entry> next = i.next(); V v = next.getValue().v; if ((value == null && v == null) || (value != null && value.equals(v))) { // update access time next.getValue().d.setTime(System.currentTimeMillis()); return true; } } return false; // none found } @Override public synchronized V get(Object key) { Entry e = _map.get((K) key); if (e != null) { // update access time e.d.setTime(System.currentTimeMillis()); return e.v; } return null; } @Override public synchronized V put(K key, V value) { // start killer thread if non is running if (cleaner == null) { cleaner = new Cleaner(); cleaner.start(); } Entry result = _map.put(key, new Entry(value)); return (result == null) ? null : result.v; } @Override public synchronized V remove(Object key) { Entry e = _map.remove((K) key); if (hasAction()) { getAction(e.v).perform(); } if (e != null) { synchronized (e.v) { e.v.notifyAll(); } return e.v; } return null; } }