2012-03-23 9 views

Respuesta

13

No estoy al tanto de cualquier estándar o tercera parte, pero es fácil, basta con crear una clase que envuelve otro mapa e implementa la interfaz del mapa:

public class MapListener<K, V> implements Map<K, V> { 

    private final Map<K, V> delegatee; 

    public MapListener(Map<K, V> delegatee) { 
     this.delegatee = delegatee; 
    } 

    // implement all Map methods, with callbacks you need. 

} 
+0

En lugar de 'implementa Map ', creo que es más fácil de usar 'extends Abstract Map '. (Pero estoy de acuerdo en estructurarlo como un contenedor que delega a otro mapa.) – ruakh

+3

Guava's ForwardingMap es básicamente exactamente esto: reenvía todos los métodos al delegado, y luego puede anularlos para probarlos. –

+0

Eso es lo que pensé, solo pescar código reutilizable antes de escribir el mío :) –

4

Sazonar al gusto. Esto es representativo, no normativo. Por supuesto que tiene problemas.

public class ListenerMap extends HashMap { 

    public static final String PROP_PUT = "put"; 
    private PropertyChangeSupport propertySupport; 

    public ListenerMap() { 
     super(); 
     propertySupport = new PropertyChangeSupport(this); 
    } 

    public String getSampleProperty() { 
     return sampleProperty; 
    } 

    @Override 
    public Object put(Object k, Object v) { 
     Object old = super.put(k, v); 
     propertySupport.firePropertyChange(PROP_PUT, old, v); 
     return old; 
    } 

     public void addPropertyChangeListener(PropertyChangeListener listener) { 
     propertySupport.addPropertyChangeListener(listener); 
    } 

    public void removePropertyChangeListener(PropertyChangeListener listener) { 
     propertySupport.removePropertyChangeListener(listener); 
    } 
} 
+0

Extender no es bueno para este requisito, creo que crear un contenedor para cada mapa es mucho mejor. –

+0

@AmirPashazadeh ¿qué quiere decir con "envoltorio para cada mapa"? –

+0

Un contenedor que acepta cualquier implementación de mapa como argumento, por lo que el comportamiento predeterminado del mapa permanece como estaba. –

0

Lo que están pidiendo esencialmente for es un Cache que puede proporcionar notificaciones de eventos. Hay algunos productos como Infinispan que ya proporcionan eso para usted pero sin conocer su caso de uso es difícil de recomendar.

Si desea un simple ObservableMap, debería ser fácil de implementar. Simplemente tiene que crear un patrón Observer. Puede encontrar un ejemplo here.

+0

Infinispan sería genial, pero lo necesito dentro de un applet, así puedo entregar datos a ... ¡infinispan! El hecho de que las llamadas javascript sean tratadas como código sin firmar que no puedo entregar directamente :( –

+0

Infinispan tiene modos incrustados y distribuidos. Puede incrustar Infinispan en su JVM. – uaarkoti

0

Aquí hay un ejemplo de trabajo de un mapa que dispara eventos de cambio de propiedad en put y remove. La aplicación se divide en dos clases:

ListenerModel

Contiene los métodos relacionados con la adición y eliminación de los oyentes de cambio y también un método para la cocción de los cambios de propiedad.

ListenerMap

Extiende ListenerModel y implementes la interfaz java.util.Map por delegación. Dispara solo los cambios de propiedad en el método de poner y quitar. Tendría sentido disparar las propiedades en otros métodos como, por ejemplo, clear(), putAll().

ListenerModel

import java.beans.PropertyChangeListener; 
import java.beans.PropertyChangeSupport; 

public class ListenerModel { 

    private final PropertyChangeSupport changeSupport = new PropertyChangeSupport(this); 

    public void addPropertyChangeListener(PropertyChangeListener listener) { 
     changeSupport.addPropertyChangeListener(listener); 
    } 

    public void removePropertyChangeListener(PropertyChangeListener listener) { 
     changeSupport.removePropertyChangeListener(listener); 
    } 

    protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) { 
     changeSupport.firePropertyChange(propertyName, oldValue, newValue); 
    } 
} 

ListenerMap

import java.util.*; 

public class ListenerMap<K, V> extends ListenerModel implements Map<K, V> { 

    public static final String PROP_PUT = "put"; 

    public static final String REMOVE_PUT = "remove"; 

    private Map<K, V> delegate = new LinkedHashMap<>(); 

    @Override 
    public void clear() { 
     delegate.clear(); 
    } 

    @Override 
    public boolean containsKey(Object key) { 
     return delegate.containsKey(key); 
    } 

    @Override 
    public boolean containsValue(Object value) { 
     return delegate.containsValue(value); 
    } 

    @Override 
    public Set<Entry<K, V>> entrySet() { 
     return delegate.entrySet(); 
    } 

    @Override 
    public V get(Object key) { 
     return delegate.get(key); 
    } 

    @Override 
    public boolean isEmpty() { 
     return delegate.isEmpty(); 
    } 

    @Override 
    public Set<K> keySet() { 
     return delegate.keySet(); 
    } 

    @Override 
    public V put(K key, V value) { 
     V oldValue = delegate.put(key, value); 
     firePropertyChange(PROP_PUT, oldValue == null ? null : new AbstractMap.SimpleEntry<>(key, oldValue), 
       new AbstractMap.SimpleEntry<>(key, value)); 
     return oldValue; 
    } 

    @Override 
    public void putAll(Map<? extends K, ? extends V> m) { 
     delegate.putAll(m); 
    } 

    @Override 
    public V remove(Object key) { 
     V oldValue = delegate.remove(key); 
     firePropertyChange(REMOVE_PUT, oldValue == null ? null : new AbstractMap.SimpleEntry<>(key, oldValue), 
       null); 
     return oldValue; 
    } 

    @Override 
    public int size() { 
     return delegate.size(); 
    } 

    @Override 
    public Collection<V> values() { 
     return delegate.values(); 
    } 
} 

Aquí está una prueba JUnit 4:

import org.junit.Before; 
import org.junit.Test; 

import java.beans.PropertyChangeListener; 
import java.util.Map; 

import static org.hamcrest.core.Is.is; 
import static org.hamcrest.core.IsNull.nullValue; 
import static org.junit.Assert.assertThat; 

/** 
* Created by Gil on 01/07/2017. 
*/ 
public class ListenerMapTest { 

    private ListenerMap<String, String> map; 

    @Before 
    public void setUp() throws Exception { 
     map = new ListenerMap<>(); 
    } 

    @Test 
    public void whenPut_ShouldFireTrigger() throws Exception { 
     boolean[] fired = {false}; 
     Map.Entry<String, String>[] listenEntry = new Map.Entry[1]; 
     boolean[] checkNull = {true}; 
     PropertyChangeListener propertyChangeListener = evt -> { 
      if (ListenerMap.PROP_PUT.equals(evt.getPropertyName())) { 
       if(checkNull[0]) { 
        assertThat(evt.getOldValue(), is(nullValue())); 
       } 
       else { 
        Map.Entry<String, String> oldValue = (Map.Entry<String, String>) evt.getOldValue(); 
        assertThat(oldValue.getKey(), is("k1")); 
        assertThat(oldValue.getValue(), is("v1")); 
       } 
       listenEntry[0] = (Map.Entry<String, String>) evt.getNewValue(); 
       fired[0] = true; 
      } 
     }; 
     map.addPropertyChangeListener(propertyChangeListener); 
     map.put("k1", "v1"); 
     assertThat(fired[0], is(true)); 
     assertThat(listenEntry[0].getKey(), is("k1")); 
     assertThat(listenEntry[0].getValue(), is("v1")); 
     checkNull[0] = false; 
     map.put("k1", "v2"); 
    } 

    @Test 
    public void whenRemove_ShouldNotFire() throws Exception { 
     boolean[] fired = {false}; 
     PropertyChangeListener propertyChangeListener = evt -> { 
      fired[0] = true; 
     }; 
     map.addPropertyChangeListener(propertyChangeListener); 
     map.put("k1", "v1"); 
     assertThat(fired[0], is(true)); 
     fired[0] = false; 
     map.removePropertyChangeListener(propertyChangeListener); 
     map.put("k2", "v2"); 
     assertThat(fired[0], is(false)); 
    } 

} 
Cuestiones relacionadas