/****************************************************************************** * Copyright (c) 2016 Oracle * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Konstantin Komissarchik - initial implementation and ongoing maintenance ******************************************************************************/ package org.eclipse.sapphire; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; /** * An implementation of {@link Map} that is {@link Observable} using listeners. * * @author <a href="mailto:konstantin.komissarchik@oracle.com">Konstantin Komissarchik</a> */ public final class ObservableMap<K,V> extends AbstractMap<K,V> implements Observable { private final Map<K,V> base; private Set<Map.Entry<K,V>> entries; private ListenerContext listeners; public ObservableMap() { this( null ); } public ObservableMap( final Map<K,V> base ) { this.base = ( base == null ? new HashMap<K,V>() : base ); } @Override public void attach( final Listener listener ) { if( listener == null ) { throw new IllegalArgumentException(); } synchronized( this ) { if( this.listeners == null ) { this.listeners = new ListenerContext(); } this.listeners.attach( listener ); } } @Override public void detach( final Listener listener ) { if( listener == null ) { throw new IllegalArgumentException(); } synchronized( this ) { if( this.listeners != null ) { this.listeners.detach( listener ); } } } private void broadcast() { final ListenerContext listeners; synchronized( this ) { listeners = this.listeners; } if( listeners != null ) { listeners.broadcast( new Event() ); } } @Override public int size() { synchronized( this ) { return this.base.size(); } } @Override public boolean isEmpty() { synchronized( this ) { return this.base.isEmpty(); } } @Override public boolean containsKey( final Object key ) { synchronized( this ) { return this.base.containsKey( key ); } } @Override public boolean containsValue( final Object value ) { synchronized( this ) { return this.base.containsValue( value ); } } @Override public V get( final Object key ) { synchronized( this ) { return this.base.get( key ); } } @Override public V put( final K key, final V value ) { final V previous; boolean broadcast = false; synchronized( this ) { if( this.base.containsKey( key ) ) { previous = this.base.put( key, value ); if( previous != value ) { broadcast = true; } } else { this.base.put( key, value ); previous = null; broadcast = true; } } if( broadcast ) { broadcast(); } return previous; } @Override public void putAll( final Map<? extends K,? extends V> m ) { boolean broadcast = false; synchronized( this ) { for( final Map.Entry<? extends K,? extends V> entry : m.entrySet() ) { final K key = entry.getKey(); final V value = entry.getValue(); if( this.base.containsKey( key ) ) { final V previous = this.base.put( key, value ); if( value != previous ) { broadcast = true; } } else { this.base.put( key, value ); broadcast = true; } } } if( broadcast ) { broadcast(); } } @Override public V remove( final Object key ) { final V removed; boolean broadcast = false; synchronized( this ) { if( this.base.containsKey( key ) ) { broadcast = true; } removed = this.base.remove( key ); } if( broadcast ) { broadcast(); } return removed; } @Override public void clear() { boolean broadcast = false; synchronized( this ) { if( ! this.base.isEmpty() ) { broadcast = true; } this.base.clear(); } if( broadcast ) { broadcast(); } } @Override public Set<Map.Entry<K,V>> entrySet() { synchronized( this ) { if( this.entries == null ) { this.entries = new AbstractSet<Map.Entry<K,V>>() { public Iterator<Map.Entry<K,V>> iterator() { synchronized( ObservableMap.this ) { return new Iterator<Map.Entry<K,V>>() { private final Iterator<Entry<K,V>> base = ObservableMap.this.base.entrySet().iterator(); public boolean hasNext() { synchronized( ObservableMap.this ) { return this.base.hasNext(); } } public Map.Entry<K,V> next() { synchronized( ObservableMap.this ) { return this.base.next(); } } public void remove() { synchronized( ObservableMap.this ) { this.base.remove(); broadcast(); } } }; } } public int size() { return ObservableMap.this.size(); } public boolean contains( final Object obj ) { synchronized( ObservableMap.this ) { return ObservableMap.this.base.entrySet().contains( obj ); } } }; } return this.entries; } } }