/* * SessionCollection.java * Eisenkraut * * Copyright (c) 2004-2016 Hanns Holger Rutz. All rights reserved. * * This software is published under the GNU General Public License v3+ * * * For further information, please contact Hanns Holger Rutz at * contact@sciss.de */ package de.sciss.eisenkraut.session; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collections; import java.util.EventListener; import java.util.HashSet; import java.util.List; import java.util.Set; import de.sciss.app.BasicEvent; import de.sciss.app.EventManager; import de.sciss.eisenkraut.util.MapManager; public class SessionCollection extends AbstractSessionObject implements EventManager.Processor { protected final List<SessionObject> collObjects = new ArrayList<SessionObject>(); protected final MapManager.Listener objectListener; protected static final Set<String> EMPTY_SET = new HashSet<String>(1); // --- event handling --- protected EventManager elm = null; // lazy /** * Creates a new empty collection. */ public SessionCollection() { super(); objectListener = new MapManager.Listener() { public void mapChanged(MapManager.Event e) { dispatchObjectMapChange(e); } public void mapOwnerModified(MapManager.Event e) { dispatchObjectMapChange(e); } }; } public void dispose() { clear(null); super.dispose(); } /** * Pauses dispatching of <code>SessionCollection.Event</code>s. * * @see de.sciss.app.EventManager#pause() */ public void pauseDispatcher() { if( elm != null ) elm.pause(); } /** * Resumes dispatching of <code>SessionCollection.Event</code>s. * * @see de.sciss.app.EventManager#resume() */ public void resumeDispatcher() { if( elm != null ) elm.resume(); } /** * Gets the session object at a given index * in the collection. * * @param index index in the collection of all session objects * @return the session object at the given index * * @see List#get( int ) */ public SessionObject get(int index) { return collObjects.get(index); } /** * Gets a list of all session objects in the collection * (i.e. a duplicate of the collection). * * @return a list of all session objects. this is a copy * so that changes do not influence each other. * the elements (session objects) reference of course * the same objects. */ public List<SessionObject> getAll() { return new ArrayList<SessionObject>(collObjects); } /** * Adds a new session object to the tail of the collection. * Fires a <code>SessionCollection.Event</code> * (<code>CHANGED</code>). * * @param source source of the fired event or null * if no event shall be generated * @param so the session object to be added * * @see SessionCollection.Event#COLLECTION_CHANGED * @see SessionCollection.Event#ACTION_ADDED * @see java.util.Collection#add( Object ) */ public void add(Object source, SessionObject so) { collObjects.add(so); so.getMap().addListener(objectListener); if (source != null) { dispatchCollectionChange(source, Collections.singletonList(so), Event.ACTION_ADDED); } } /** * Adds a new session object to the tail of the collection. * Fires a <code>SessionCollection.Event</code> * (<code>CHANGED</code>). * * @param source source of the fired event or null * if no event shall be generated * @param so the session object to be added * * @see SessionCollection.Event#COLLECTION_CHANGED * @see SessionCollection.Event#ACTION_ADDED * @see java.util.Collection#add( Object ) */ public void add(Object source, int idx, SessionObject so) { collObjects.add(idx, so); so.getMap().addListener(objectListener); if (source != null) { dispatchCollectionChange(source, Collections.singletonList(so), Event.ACTION_ADDED); } } /** * Adds a list of session objects to the tail of the collection. * Fires a <code>SessionCollectionEvent</code> * (<code>CHANGED</code>) if the collection changed as a * result of the call. * * @param source source of the fired event or null * if no event shall be generated * @param c the collection of session objects to be added * (may be empty) * @return true if the collection changed as a result of the call. * * @see SessionCollection.Event#COLLECTION_CHANGED * @see SessionCollection.Event#ACTION_ADDED */ public boolean addAll(Object source, List<? extends SessionObject> c) { final boolean result = collObjects.addAll(c); if (result) { for (SessionObject aC : c) { aC.getMap().addListener(objectListener); } if (source != null) dispatchCollectionChange(source, c, Event.ACTION_ADDED); } return result; } /** * Removes a session object from the collection. * Fires a <code>SessionCollectionEvent</code> * (<code>CHANGED</code>) if the collection * contained the session object. * * @param source source of the fired event or null * if no event shall be generated * @param so the session object to be removed * @return true if the collection contained the session object * * @see SessionCollection.Event#COLLECTION_CHANGED * @see SessionCollection.Event#ACTION_REMOVED * @see java.util.Collection#remove( Object ) */ public boolean remove( Object source, SessionObject so ) { final boolean result = collObjects.remove( so ); if( result ) { so.getMap().removeListener( objectListener ); if( source != null ) { dispatchCollectionChange( source, Collections.singletonList( so ), Event.ACTION_REMOVED ); } } return result; } /** * Removes a list of session objects from the collection. * Fires a <code>SessionCollectionEvent</code> * (<code>CHANGED</code>) if the collection changed as a * result of the call. * * @param source source of the fired event or null * if no event shall be generated * @param c the collection of session objects to be removed * (may be empty) * @return true if the collection changed as a result of the call. * * @see SessionCollection.Event#COLLECTION_CHANGED * @see SessionCollection.Event#ACTION_REMOVED */ public boolean removeAll(Object source, List<SessionObject> c) { boolean result = collObjects.removeAll(c); if( result ) { for (Object aC : c) { ((SessionObject) aC).getMap().removeListener(objectListener); } if( source != null ) dispatchCollectionChange( source, c, Event.ACTION_REMOVED ); } return result; } /** * Tests if the collection contains a session object. * * @param so the session object to look up * @return <code>true</code> if the collection contains the * session object * @see java.util.Collection#contains( Object ) */ public boolean contains( SessionObject so ) { return collObjects.contains( so ); } /** * Queries the index of a session object in the collection. * * @param so the session object to look up in the collection * @return the index in the collection or -1 if the session object was not * in the collection * * @see List#indexOf( Object ) */ public int indexOf( SessionObject so ) { return collObjects.indexOf( so ); } /** * Tests if the collection is empty. * * @return <code>true</code> if the collection is empty * * @see java.util.Collection#isEmpty() */ public boolean isEmpty() { return collObjects.isEmpty(); } /** * Gets the size of the session object collection. * * @return number of session objects in the collection * @see java.util.Collection#size() */ public int size() { return collObjects.size(); } /** * Removes all session objects from the collection. * Fires a <code>SessionCollectionEvent</code> * (<code>CHANGED</code>) if the collection * was not empty. * * @param source source of the fired event or null * if no event shall be generated * * @see SessionCollection.Event#COLLECTION_CHANGED * @see SessionCollection.Event#ACTION_REMOVED * @see java.util.Collection#clear() */ public void clear(Object source) { if (!isEmpty()) { final List<SessionObject> c = (source == null) ? null : getAll(); for (SessionObject collObject : collObjects) { collObject.getMap().removeListener(objectListener); } collObjects.clear(); if (source != null) dispatchCollectionChange(source, c, Event.ACTION_REMOVED); } } // --- create a unique name for a new session object --- /** * Creates a unique new logical name for * a session object. This method formats the * given message format with the given arguments * and looks in the given collection if an object * exists with that name. If not, the formatted string * is returned. Otherwise, <code>args[0]</code> is * incremented by 1 and the procedure is repeated until * a unique name has been found. * * @param ptrn the message format used to create * versions of a name * @param args argument array for the message format. * <code>args[0]</code> <strong>MUST</strong> * be a <code>Number</code> object and will * be replaced by this method, if the initial * name already existed. * @param theseNot a list of session objects whose names * are forbidden to be returned by this method. * * @return a synthesized name for a new session object which * is guaranteed to be not used by any of the session objects * in the given collection <code>theseNot</code>. * <code>args[0]</code> contains the next index of * iterative calling of this method. */ public static String createUniqueName(MessageFormat ptrn, Object[] args, List<SessionObject> theseNot) { final StringBuffer strBuf = new StringBuffer(); int i = ((Number) args[0]).intValue(); String name; do { strBuf.setLength(0); name = ptrn.format(args, strBuf, null).toString(); args[0] = ++i; } while (findByName(theseNot, name) != null); return name; } /** * Looks up a session object by its name. * The search is case insensitive because * the name might be used for data storage and * the underlying file system might not distinguish * between upper and lower case file names! * * @param name the name of the session object to find. * @return the session object or null if no session object by that * name exists in the current collection of all session objects. * * @see java.lang.String#equalsIgnoreCase( String ) */ public SessionObject findByName( String name ) { return findByName( collObjects, name ); } public static SessionObject findByName(List<SessionObject> coll, String name) { for (SessionObject so : coll) { if (so.getName().equalsIgnoreCase(name)) return so; } return null; } // --- listener registration --- /** * Registers a <code>Listener</code> * which will be informed about changes of * the session object collection. * * @param listener the <code>Listener</code> to register * * @see de.sciss.app.EventManager#addListener( Object ) */ public void addListener( SessionCollection.Listener listener ) // , Set keySet, int mode ) { synchronized( this ) { if( elm == null ) { elm = new EventManager( this ); } elm.addListener( listener ); } } /** * Unregisters a <code>Listener</code> * from receiving changes of * the session object collection. * * @param listener the <code>Listener</code> to unregister * @see de.sciss.app.EventManager#removeListener( Object ) */ public void removeListener( SessionCollection.Listener listener ) { if( elm != null ) elm.removeListener( listener ); } /** * This is called by the EventManager * if new events are to be processed. */ public void processEvent( BasicEvent e ) { SessionCollection.Listener listener; int i; for( i = 0; i < elm.countListeners(); i++ ) { listener = (SessionCollection.Listener) elm.getListener( i ); switch( e.getID() ) { case SessionCollection.Event.COLLECTION_CHANGED: listener.sessionCollectionChanged( (SessionCollection.Event) e ); break; case SessionCollection.Event.MAP_CHANGED: listener.sessionObjectMapChanged( (SessionCollection.Event) e ); break; case SessionCollection.Event.OBJECT_CHANGED: listener.sessionObjectChanged( (SessionCollection.Event) e ); break; default: assert false : e.getID(); } } // for( i = 0; i < elm.countListeners(); i++ ) } // utility function to create and dispatch a SessionObjectCollectionEvent protected void dispatchCollectionChange( Object source, List<? extends SessionObject> affected, int type ) { if( elm != null ) { final Event e2 = new Event( source, System.currentTimeMillis(), affected, type ); elm.dispatchEvent( e2 ); } } // utility function to create and dispatch a SessionObjectCollectionEvent protected void dispatchObjectMapChange( MapManager.Event e ) { if( elm != null ) { final Event e2 = new Event( e.getSource(), System.currentTimeMillis(), e ); elm.dispatchEvent( e2 ); } } public void debugDump() { System.err.println( "Dumping "+this.getClass().getName() ); for( int i = 0; i < collObjects.size(); i++ ) { System.err.println( "object "+i+" = "+collObjects.get( i ).toString() ); } // elm.debugDump(); } // ---------------- SessionObject interface ---------------- /** * This simply returns <code>null</code>! */ public Class<?> getDefaultEditor() { return null; } // -------------------------- inner Event class -------------------------- // XXX TO-DO : Event should have a getDocumentCollection method // XXX TO-DO : Event should have indices of all elements @SuppressWarnings("serial") public class Event extends BasicEvent { // --- ID values --- /** * returned by getID() : the collection was changed by * adding or removing elements */ public static final int COLLECTION_CHANGED = 0; /** * returned by getID() : the collection elements have * been modified, e.g. resized */ public static final int MAP_CHANGED = 1; /** * returned by getID() : the collection elements have * been modified, e.g. resized */ public static final int OBJECT_CHANGED = 2; public static final int ACTION_ADDED = 0; public static final int ACTION_REMOVED = 1; public static final int ACTION_CHANGED = 2; private final List<Object> affectedColl; private final int affectedType; private final Set<?> affectedSet; private final Object affectedParam; /** * Constructs a new <code>SessionObjectCollectionEvent</code>. * * @param source who originated the action / event * @param when system time when the event occurred */ protected Event(Object source, long when, List<? extends SessionObject> affectedColl, int type) { super(source, COLLECTION_CHANGED, when); this.affectedColl = new ArrayList<Object>(affectedColl); this.affectedType = type; this.affectedParam = null; this.affectedSet = EMPTY_SET; } protected Event(Event superEvent, List<Object> affectedColl) { super(superEvent.getSource(), superEvent.getID(), superEvent.getWhen()); this.affectedColl = new ArrayList<Object>(affectedColl); this.affectedType = superEvent.getModificationType(); this.affectedParam = getModificationParam(); this.affectedSet = new HashSet<Object>(superEvent.affectedSet); } protected Event( Object source, long when, MapManager.Event e ) { super( source, e.getID() == MapManager.Event.MAP_CHANGED ? MAP_CHANGED : OBJECT_CHANGED, when ); this.affectedColl = new ArrayList<Object>(1); this.affectedColl.add(e.getOwner()); if( getID() == MAP_CHANGED ) { this.affectedType = ACTION_CHANGED; this.affectedSet = e.getPropertyNames(); this.affectedParam = null; } else { this.affectedType = e.getOwnerModType(); this.affectedParam = e.getOwnerModParam(); this.affectedSet = EMPTY_SET; } } public List<Object> getCollection() { return new ArrayList<Object>( affectedColl ); } public boolean collectionContains( SessionObject so ) { return affectedColl.contains( so ); } public boolean collectionContainsAny(List<Object> coll) { for (Object aColl : coll) { if (affectedColl.contains(aColl)) return true; } return false; } public boolean setContains(String key) { return (affectedSet.contains(key)); } public boolean setContainsAny(List<Object> coll) { for (Object aColl : coll) { if (affectedSet.contains(aColl)) return true; } return false; } public int getModificationType() { return affectedType; } public Object getModificationParam() { return affectedParam; } public boolean incorporate(BasicEvent oldEvent) { return false; // XXX for now } } // -------------------------- inner Listener interface -------------------------- public interface Listener extends EventListener { /** * Invoked when the collection was changed by * adding or removing elements * * @param e the event describing * the collection change */ public void sessionCollectionChanged(SessionCollection.Event e); public void sessionObjectMapChanged(SessionCollection.Event e); public void sessionObjectChanged(SessionCollection.Event e); } }