/******************************************************************************* * Copyright (c) 2007, 2015 Association for Decentralized Information Management * in Industry THTH ry. * 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: * VTT Technical Research Centre of Finland - initial API and implementation * Semantum Oy - added getHintsUnsafe *******************************************************************************/ /* * * @author Toni Kalajainen */ package org.simantics.utils.datastructures.hints; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.eclipse.collections.impl.factory.Maps; /** * * @author Toni Kalajainen */ public class HintContext extends AbstractHintObservable implements IHintContext, Cloneable { protected Map hints = Maps.mutable.empty(); @Override public void clearWithoutNotification() { synchronized (this) { hints.clear(); } } @Override public synchronized boolean containsHint(Key key) { return hints.get(key) != null; } @SuppressWarnings("unchecked") @Override public E getHint(Key key) { if (key == null) throw new IllegalArgumentException("key is null"); synchronized (this) { return (E) hints.get(key); } } @SuppressWarnings("unchecked") @Override public E removeHint(Key key) { if (key == null) throw new IllegalArgumentException("key is null"); Runnable notification; Object oldValue = null; synchronized(this) { oldValue = hints.remove(key); if (oldValue==null) return null; notification = createFireKeyRemovedRunnable(this, key, oldValue); } notification.run(); return (E) oldValue; } /** * Set a set of hints * @param hints */ public void removeHints(Collection keys) { List notifications = new ArrayList(hints.size()); synchronized (this) { // Remove first for (Key key : keys) { Object oldValue = this.hints.remove(key); if (oldValue == null) continue; Runnable notification = createFireKeyRemovedRunnable(this, key, oldValue); notifications.add( notification ); } } // Notify then for (Runnable r : notifications) r.run(); } @Override public void setHint(Key key, Object value) { if (key == null) throw new IllegalArgumentException("key is null"); if (value == null) throw new IllegalArgumentException("value is null"); if (!key.isValueAccepted(value)) throw new RuntimeException("Value \""+value+"\" is not accepted with key "+key.getClass().getName()); Runnable notification; synchronized(this) { Object oldValue = hints.put(key, value); notification = createFireKeyChangedRunnable(this, key, oldValue, value); } notification.run(); } /** * Set a set of hints * @param hints */ @Override public void setHints(Map hints) { List notifications = new ArrayList(hints.size()); synchronized (this) { // Insert first for (Entry e : hints.entrySet()) { Key key = e.getKey(); Object value = e.getValue(); if (value == null) throw new IllegalArgumentException("a value is null for key " + e.getKey()); Object oldValue = this.hints.put(key, value); Runnable notification = createFireKeyChangedRunnable(this, key, oldValue, value); notifications.add( notification ); } } // Notify then for (Runnable r : notifications) r.run(); } public Object setHintWithoutNotification(Key key, Object value) { if (key == null) throw new IllegalArgumentException("key is null"); if (value == null) throw new IllegalArgumentException("value is null"); if (!key.isValueAccepted(value)) throw new RuntimeException("Value \""+value+"\" is not accepted with key "+key.getClass().getName()); synchronized(this) { return hints.put(key, value); } } /** * Removes the specified hint without sending notifications about changes. * * @param * @param key * @return removed hint value */ @SuppressWarnings("unchecked") public E removeHintWithoutNotification(Key key) { if (key == null) throw new IllegalArgumentException("key is null"); Object oldValue = null; synchronized(this) { oldValue = hints.remove(key); } return (E) oldValue; } /** * Replace the current hints with the specified set of hints. Hints * that were previously included but are not contained by the new map will * not be preserved by this operation. * * @param hints the new hints to set */ public void replaceHintsWithoutNotification(Map hints) { Map copy = new HashMap(hints); synchronized (this) { this.hints = copy; } } /** * Replace the current set of hints with the new specified set of hints. * Notifications are sent for removed and changed hints. * * @param hints */ public void replaceHints(Map newHints) { List notifications = new ArrayList(Math.max(this.hints.size(), newHints.size())); synchronized (this) { // Calculate removed keys Set removedKeys = new HashSet(this.hints.keySet()); removedKeys.removeAll(newHints.keySet()); // Remove keys for (Key key : removedKeys) { Object oldValue = this.hints.remove(key); if (oldValue == null) continue; Runnable notification = createFireKeyRemovedRunnable(this, key, oldValue); notifications.add( notification ); } // Replace/set existing/new hints for (Entry e : newHints.entrySet()) { Key key = e.getKey(); Object value = e.getValue(); if (value == null) throw new IllegalArgumentException("a value is null for key " + e.getKey()); Object oldValue = this.hints.put(key, value); if (value.equals(oldValue)) continue; Runnable notification = createFireKeyChangedRunnable(this, key, oldValue, value); notifications.add( notification ); } } // Notify then for (Runnable r : notifications) r.run(); } /** * Set a set of hints without notifying any generic hint or key-specific * listeners. This method will only replace the possible previous values of * the hints included in the specified map. Hints not included in the map * will be preserved as such. * * @param hints the new hints to set */ public void setHintsWithoutNotification(Map hints) { synchronized (this) { for (Entry e : hints.entrySet()) { Key key = e.getKey(); Object value = e.getValue(); if (value == null) throw new IllegalArgumentException("a value is null for key " + e.getKey()); this.hints.put(key, value); } } } /** * Compares two object for equality. *

* Some times it is annoying to compare two objects if their * value may be null. * * @param o1 obj1 * @param o2 obj2 * @return true if equal or both null */ public static boolean objectEquals(Object o1, Object o2) { if (o1==o2) return true; if (o1==null && o2==null) return true; if (o1==null || o2==null) return false; return o1.equals(o2); } @Override public synchronized Map getHints() { return new HashMap(hints); } @Override public Map getHintsUnsafe() { return hints; } @SuppressWarnings("unchecked") @Override public synchronized Map getHintsOfClass(Class clazz) { Map result = new HashMap(); for (Entry e : hints.entrySet()) { Key key = e.getKey(); if (clazz.isAssignableFrom(key.getClass())) result.put((E)key, e.getValue()); } return result; } public synchronized int size() { return hints.size(); } @Override public Object clone() { try { return super.clone(); } catch (CloneNotSupportedException e) { throw new Error(e); } } }