/*
* This file is part of the OpenSCADA project
* Copyright (C) 2006-2012 TH4 SYSTEMS GmbH (http://th4-systems.com)
*
* OpenSCADA is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version 3
* only, as published by the Free Software Foundation.
*
* OpenSCADA is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License version 3 for more details
* (a copy is included in the LICENSE file that accompanied this code).
*
* You should have received a copy of the GNU Lesser General Public License
* version 3 along with OpenSCADA. If not, see
* <http://opensource.org/licenses/lgpl-3.0.html> for a copy of the LGPLv3 License.
*/
package org.openscada.ui.databinding.observable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.databinding.observable.DisposeEvent;
import org.eclipse.core.databinding.observable.IDisposeListener;
import org.eclipse.core.databinding.observable.IStaleListener;
import org.eclipse.core.databinding.observable.ObservableTracker;
import org.eclipse.core.databinding.observable.StaleEvent;
import org.eclipse.core.databinding.observable.map.IMapChangeListener;
import org.eclipse.core.databinding.observable.map.IObservableMap;
import org.eclipse.core.databinding.observable.map.MapChangeEvent;
import org.eclipse.core.databinding.observable.map.MapDiff;
import org.eclipse.core.databinding.observable.map.ObservableMap;
public class KeyPrefixMapObservable extends ObservableMap
{
private IStaleListener staleListener = new IStaleListener () {
@Override
public void handleStale ( final StaleEvent staleEvent )
{
fireStale ();
}
};
private IObservableMap map;
private final String keyPrefix;
private final Object valueType;
private IMapChangeListener changeListener = new IMapChangeListener () {
@Override
public void handleMapChange ( final MapChangeEvent event )
{
KeyPrefixMapObservable.this.handleMapChange ( event.diff );
}
};
private IDisposeListener disposeListener = new IDisposeListener () {
@Override
public void handleDispose ( final DisposeEvent event )
{
dispose ();
}
};
private final boolean removePrefix;
protected KeyPrefixMapObservable ( final IObservableMap map, final String keyPrefix, final Object valueType, final boolean removePrefix )
{
super ( map.getRealm (), new HashMap<Object, Object> () );
this.map = map;
this.keyPrefix = keyPrefix;
this.valueType = valueType;
this.removePrefix = removePrefix;
map.addMapChangeListener ( this.changeListener );
map.addStaleListener ( this.staleListener );
map.addDisposeListener ( this.disposeListener );
}
private String makeKey ( final Object key )
{
if ( key == null )
{
return null;
}
else if ( !this.removePrefix )
{
return key.toString ();
}
else
{
return key.toString ().substring ( this.keyPrefix.length () );
}
}
private boolean isPrefix ( final Object key )
{
if ( key == null )
{
return this.keyPrefix == null;
}
if ( this.keyPrefix == null )
{
return false;
}
return key.toString ().startsWith ( this.keyPrefix );
}
@SuppressWarnings ( "unchecked" )
private void handleMapChange ( final MapDiff diff )
{
final Set<String> added = new HashSet<String> ();
final Set<String> changed = new HashSet<String> ();
final Set<String> removed = new HashSet<String> ();
final Map<String, Object> newValues = new HashMap<String, Object> ();
final Map<String, Object> oldValues = new HashMap<String, Object> ();
// process adds
for ( final Object key : diff.getAddedKeys () )
{
if ( isPrefix ( key ) )
{
final Object value = diff.getNewValue ( key );
final String localKey = makeKey ( key );
added.add ( localKey );
newValues.put ( localKey, value );
this.wrappedMap.put ( localKey, value );
}
}
// process changes
for ( final Object key : diff.getChangedKeys () )
{
if ( isPrefix ( key ) )
{
final String localKey = makeKey ( key );
changed.add ( localKey );
final Object value = diff.getNewValue ( key );
newValues.put ( localKey, value );
oldValues.put ( localKey, diff.getOldValue ( key ) );
this.wrappedMap.put ( localKey, value );
}
}
// process removes
for ( final Object key : diff.getRemovedKeys () )
{
final String localKey = makeKey ( key );
oldValues.put ( localKey, diff.getOldValue ( key ) );
removed.add ( localKey );
this.wrappedMap.remove ( localKey );
}
fireMapChange ( createMapDiff ( added, changed, removed, newValues, oldValues ) );
}
private MapDiff createMapDiff ( final Set<String> added, final Set<String> changed, final Set<String> removed, final Map<String, ?> newValues, final Map<String, ?> oldValues )
{
return new MapDiff () {
@Override
public Set<?> getAddedKeys ()
{
return added;
}
@Override
public Set<?> getRemovedKeys ()
{
return removed;
}
@Override
public Set<?> getChangedKeys ()
{
return changed;
}
@Override
public Object getOldValue ( final Object key )
{
return oldValues.get ( key );
}
@Override
public Object getNewValue ( final Object key )
{
return newValues.get ( key );
}
};
}
@Override
public Object getValueType ()
{
return this.valueType;
}
@Override
public boolean isStale ()
{
ObservableTracker.getterCalled ( this );
return this.map.isStale ();
}
@Override
public synchronized void dispose ()
{
if ( this.map != null )
{
this.map.removeMapChangeListener ( this.changeListener );
this.map.removeStaleListener ( this.staleListener );
this.map.removeDisposeListener ( this.disposeListener );
this.map = null;
this.changeListener = null;
this.staleListener = null;
this.disposeListener = null;
}
super.dispose ();
}
@Override
public Object put ( final Object key, final Object value )
{
checkRealm ();
final String fullKey = reverseKey ( key );
return this.map.put ( fullKey, value );
}
@Override
public void clear ()
{
checkRealm ();
this.map.clear ();
}
@SuppressWarnings ( "rawtypes" )
@Override
public void putAll ( final Map values )
{
checkRealm ();
final HashMap<String, Object> newValues = new HashMap<String, Object> ();
for ( final Object o : values.entrySet () )
{
final Entry<?, ?> entry = (Map.Entry<?, ?>)o;
final String fullKey = reverseKey ( entry.getKey () );
newValues.put ( fullKey, entry.getValue () );
}
}
@Override
public Object remove ( final Object key )
{
checkRealm ();
return this.map.remove ( reverseKey ( key ) );
}
private String reverseKey ( final Object key )
{
if ( this.removePrefix && this.keyPrefix != null )
{
return this.keyPrefix + key;
}
else if ( key != null )
{
return key.toString ();
}
else
{
return null;
}
}
public static IObservableMap observePrefix ( final IObservableMap map, final String keyPrefix )
{
return new KeyPrefixMapObservable ( map, keyPrefix, null, false );
}
public static IObservableMap observePrefix ( final IObservableMap map, final String keyPrefix, final boolean removePrefix )
{
return new KeyPrefixMapObservable ( map, keyPrefix, null, removePrefix );
}
public static IObservableMap observePrefix ( final IObservableMap map, final Object valueType, final String keyPrefix, final boolean removePrefix )
{
return new KeyPrefixMapObservable ( map, keyPrefix, valueType, removePrefix );
}
}