/******************************************************************************* * Copyright (c) 2007, 2010 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 *******************************************************************************/ /* * * @author Toni Kalajainen */ package org.simantics.utils.datastructures.context; import java.lang.reflect.Array; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import org.simantics.utils.datastructures.disposable.AbstractDisposable; import org.simantics.utils.strings.EString; import org.simantics.utils.threads.Executable; import org.simantics.utils.threads.IThreadWorkQueue; import org.simantics.utils.threads.SyncListenerList; import org.simantics.utils.threads.ThreadUtils; /** * * TODO Optimize getItemsByClass queries (Create optimizing structures after 1st query) * TODO Optimize getSingleItem query * * @param */ public class Context extends AbstractDisposable implements IContext { protected Set set = new HashSet(); @SuppressWarnings({ "rawtypes" }) protected SyncListenerList listeners = new SyncListenerList(IContextListener.class); final Class clazz; private E[] snapshotArray; private Map, Collection> classQueryResultCache = new HashMap, Collection>(); public Context(Class clazz) { this.clazz = clazz; snapshotArray = createArray(clazz, 0); } @Override public void add(E item) { assertNotDisposed(); Executable executables[]; synchronized(this) { if (set.contains(item)) throw new IllegalArgumentException("Context already contains item "+item); set.add(item); snapshotArray = createSnapshot(set); executables = listeners.getExecutables(itemAdded, this, item); Iterator> i = classQueryResultCache.keySet().iterator(); while (i.hasNext()) { Class c = i.next(); if (c.isInstance(item)) i.remove(); } } ThreadUtils.multiSyncExec(executables); } @Override public boolean remove(E item) { assertNotDisposed(); Executable executables[]; synchronized(this) { if (!set.remove(item)) return false; snapshotArray = createSnapshot(set); executables = listeners.getExecutables(itemRemoved, this, item); Iterator> i = classQueryResultCache.keySet().iterator(); while (i.hasNext()) { Class c = i.next(); if (c.isInstance(item)) i.remove(); } } ThreadUtils.multiSyncExec(executables); return true; } @Override public synchronized boolean contains(E item) { assertNotDisposed(); return set.contains(item); } @Override public void clear() { assertNotDisposed(); classQueryResultCache.clear(); ArrayList executables = new ArrayList(); synchronized(this) { if (set.isEmpty()) return; for (E item : snapshotArray) listeners.addExecutables(executables, itemRemoved, this, item); set.clear(); this.snapshotArray = createSnapshot(set); } ThreadUtils.multiSyncExec(executables); } @SuppressWarnings("unchecked") @Override public synchronized Collection getItemsByClass(Class clazz) { assertNotDisposed(); Collection result = (Collection) classQueryResultCache.get(clazz); if (result!=null) return result; result = new ArrayList(); for (E i : set) if (clazz.isAssignableFrom(i.getClass())) result.add((R)i); classQueryResultCache.put(clazz, result); return result; } @SuppressWarnings("unchecked") @Override public synchronized boolean containsItemByClass(Class clazz) { assertNotDisposed(); Collection result = (Collection) classQueryResultCache.get(clazz); if (result!=null) return !result.isEmpty(); for (E i : set) if (clazz.isAssignableFrom(i.getClass())) return true; return false; } @SuppressWarnings({ "unchecked" }) private R[] createArray(Class clazz, int length) { return (R[]) Array.newInstance(clazz, length); } E[] createSnapshot(Set set) { E[] result = createArray(clazz, set.size()); int index = 0; for (E i : set) result[index++] = i; return result; } public synchronized R getSingleItem(Class clazz) { assertNotDisposed(); Collection result = getItemsByClass(clazz); if (result.size()==1) return result.iterator().next(); throw new RuntimeException("one "+clazz.getName()+" expected in Context, got "+result.size()); } @Override public E[] toArray() { assertNotDisposed(); return snapshotArray; } @Override public void addContextListener(IContextListener listener) { assertNotDisposed(); listeners.add(listener); } @Override public void removeContextListener(IContextListener listener) { assertNotDisposed(); listeners.remove(listener); } @Override public void addContextListener(IThreadWorkQueue thread, IContextListener listener) { assertNotDisposed(); listeners.add(thread, listener); } @Override public void removeContextListener(IThreadWorkQueue thread, IContextListener listener) { assertNotDisposed(); listeners.remove(thread, listener); } private static Method itemAdded = SyncListenerList.getMethod(IContextListener.class, "itemAdded"); private static Method itemRemoved = SyncListenerList.getMethod(IContextListener.class, "itemRemoved"); // private void fireItemAdded(E item) // { // listeners.fireEventSync(itemAdded, this, item); // } // // private void fireItemRemoved(E item) // { // listeners.fireEventSync(itemRemoved, this, item); // } @SuppressWarnings("unchecked") @Override public R getAtMostOneItemOfClass(Class clazz) { assertNotDisposed(); int count = 0; R r = null; for (E i : set) { if (clazz.isAssignableFrom(i.getClass())) { ++count; r = (R) i; } } if (count==0) return null; if (count>1) throw new RuntimeException("one "+clazz.getName()+" expected in Context, got "+count); return r; } @Override protected void doDispose() { } @Override public String toString() { String s = EString.implode(snapshotArray, "\n"); return s != null ? s : ""; } }