001/*
002 * Copyright 2006 - 2013
003 *     Stefan Balev     <stefan.balev@graphstream-project.org>
004 *     Julien Baudry    <julien.baudry@graphstream-project.org>
005 *     Antoine Dutot    <antoine.dutot@graphstream-project.org>
006 *     Yoann Pigné      <yoann.pigne@graphstream-project.org>
007 *     Guilhelm Savin   <guilhelm.savin@graphstream-project.org>
008 * 
009 * This file is part of GraphStream <http://graphstream-project.org>.
010 * 
011 * GraphStream is a library whose purpose is to handle static or dynamic
012 * graph, create them from scratch, file or any source and display them.
013 * 
014 * This program is free software distributed under the terms of two licenses, the
015 * CeCILL-C license that fits European law, and the GNU Lesser General Public
016 * License. You can  use, modify and/ or redistribute the software under the terms
017 * of the CeCILL-C license as circulated by CEA, CNRS and INRIA at the following
018 * URL <http://www.cecill.info> or under the terms of the GNU LGPL as published by
019 * the Free Software Foundation, either version 3 of the License, or (at your
020 * option) any later version.
021 * 
022 * This program is distributed in the hope that it will be useful, but WITHOUT ANY
023 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
024 * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
025 * 
026 * You should have received a copy of the GNU Lesser General Public License
027 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
028 * 
029 * The fact that you are presently reading this means that you have had
030 * knowledge of the CeCILL-C and LGPL licenses and that you accept their terms.
031 */
032package org.graphstream.ui.spriteManager;
033
034import java.util.Arrays;
035import java.util.HashMap;
036import java.util.Iterator;
037
038import org.graphstream.graph.Graph;
039import org.graphstream.stream.AttributeSink;
040import org.graphstream.ui.graphicGraph.stylesheet.Style;
041import org.graphstream.ui.graphicGraph.stylesheet.Value;
042import org.graphstream.ui.graphicGraph.stylesheet.Values;
043import org.graphstream.ui.graphicGraph.stylesheet.StyleConstants.Units;
044
045/**
046 * Set of sprites associated with a graph.
047 * 
048 * <p>
049 * The sprite manager acts as a set of sprite elements that are associated with
050 * a graph. There can be only one sprite manager per graph. The sprite manager
051 * only role is to allow to create, destroy and enumerate sprites of a graph.
052 * </p>
053 * 
054 * <p>
055 * See the {@link Sprite} class for an explanation of what are sprites and how
056 * to use them.
057 * </p>
058 * 
059 * <p>
060 * In case you need to refine the Sprite class, you can change the
061 * {@link SpriteFactory} of this manager so that it creates specific instances
062 * of sprites instead of the default ones. This is mostly useful when all
063 * sprites will pertain to the same subclass. If you need to create several
064 * sprites of distinct subclasses, you can use the
065 * {@link #addSprite(String, Class)} and
066 * {@link #addSprite(String, Class, Values)} methods.
067 * </p>
068 */
069public class SpriteManager implements Iterable<Sprite>, AttributeSink {
070        // Attribute
071
072        /**
073         * The graph to add sprites to.
074         */
075        protected Graph graph;
076
077        /**
078         * The set of sprites.
079         */
080        protected HashMap<String, Sprite> sprites = new HashMap<String, Sprite>();
081
082        /**
083         * Factory to create new sprites.
084         */
085        protected SpriteFactory factory = new SpriteFactory();
086
087        // Attributes
088
089        /**
090         * this acts as a lock when we are adding a sprite since we are also
091         * listener of the graph, and when we receive an "add" event, we
092         * automatically create a sprite. We can want to avoid listening at ourself.
093         */
094        boolean attributeLock = false;
095
096        // Construction
097
098        /**
099         * Create a new manager for sprite and bind it to the given graph. If the
100         * graph already contains attributes describing sprites, the manager is
101         * automatically filled with the existing sprites.
102         * 
103         * @param graph
104         *            The graph to associate with this manager;
105         */
106        public SpriteManager(Graph graph) throws InvalidSpriteIDException {
107                this.graph = graph;
108
109                lookForExistingSprites();
110                graph.addAttributeSink(this);
111        }
112
113        protected void lookForExistingSprites() throws InvalidSpriteIDException {
114                if (graph.getAttributeCount() > 0) {
115                        for (String attr : graph.getAttributeKeySet()) {
116                                if (attr.startsWith("ui.sprite.")) {
117                                        String id = attr.substring(10);
118
119                                        if (id.indexOf('.') < 0) {
120                                                addSprite(id);
121                                        } else {
122                                                String sattr = id.substring(id.indexOf('.') + 1);
123                                                id = id.substring(0, id.indexOf('.'));
124
125                                                Sprite s = getSprite(id);
126
127                                                if (s == null)
128                                                        s = addSprite(id);
129
130                                                s.addAttribute(sattr, graph.getAttribute(attr));
131                                        }
132                                }
133                        }
134                }
135        }
136
137        // Access
138
139        /**
140         * Number of sprites in the manager.
141         * 
142         * @return The sprite count.
143         */
144        public int getSpriteCount() {
145                return sprites.size();
146        }
147
148        /**
149         * True if the manager contains a sprite corresponding to the given
150         * identifier.
151         * 
152         * @param identifier
153         *            The sprite identifier to search for.
154         */
155        public boolean hasSprite(String identifier) {
156                return (sprites.get(identifier) != null);
157        }
158
159        /**
160         * Sprite corresponding to the given identifier or null if no sprite is
161         * associated with the given identifier.
162         * 
163         * @param identifier
164         *            The sprite identifier.
165         */
166        public Sprite getSprite(String identifier) {
167                return sprites.get(identifier);
168        }
169
170        /**
171         * Iterable set of sprites in no particular order.
172         * 
173         * @return The set of sprites.
174         */
175        public Iterable<? extends Sprite> sprites() {
176                return sprites.values();
177        }
178
179        /**
180         * Iterator on the set of sprites.
181         * 
182         * @return An iterator on sprites.
183         */
184        public Iterator<? extends Sprite> spriteIterator() {
185                return sprites.values().iterator();
186        }
187
188        /**
189         * Iterator on the set of sprites.
190         * 
191         * @return An iterator on sprites.
192         */
193        public Iterator<Sprite> iterator() {
194                return sprites.values().iterator();
195        }
196
197        /**
198         * The current sprite factory.
199         * 
200         * @return A Sprite factory.
201         */
202        public SpriteFactory getSpriteFactory() {
203                return factory;
204        }
205
206        // Command
207
208        /**
209         * Detach this manager from its graph. This manager will no more be usable
210         * to create or remove sprites. However sprites not yet removed are still
211         * present as attributes in the graph and binding another sprite manager to
212         * this graph will retrieve all sprites.
213         */
214        public void detach() {
215                graph.removeAttributeSink(this);
216                sprites.clear();
217
218                graph = null;
219        }
220
221        /**
222         * Specify the sprite factory to use. This allows to use specific sprite
223         * classes (descendants of Sprite).
224         * 
225         * @param factory
226         *            The new factory to use.
227         */
228        public void setSpriteFactory(SpriteFactory factory) {
229                this.factory = factory;
230        }
231
232        /**
233         * Reset the sprite factory to defaults.
234         */
235        public void resetSpriteFactory() {
236                factory = new SpriteFactory();
237        }
238
239        /**
240         * Add a sprite with the given identifier. If the sprite already exists,
241         * nothing is done. The sprite identifier cannot actually contain dots. This
242         * character use is reserved by the sprite mechanism.
243         * 
244         * @param identifier
245         *            The identifier of the new sprite to add.
246         * @return The created sprite.
247         * @throws InvalidSpriteIDException
248         *             If the given identifier contains a dot.
249         */
250        public Sprite addSprite(String identifier) throws InvalidSpriteIDException {
251                return addSprite(identifier, (Values) null);
252        }
253
254        /**
255         * Add a sprite with the given identifier and position. If the sprite
256         * already exists, nothing is done, excepted if the position is not null in
257         * which case it is repositioned. If the sprite does not exists, it is added
258         * and if position is not null, it is used as the initial position of the
259         * sprite. The sprite identifier cannot actually contain dots. This
260         * character use is reserved by the sprite mechanism.
261         * 
262         * @param identifier
263         *            The sprite identifier.
264         * @param position
265         *            The sprite position (or null for (0,0,0)).
266         * @return The created sprite.
267         * @throws InvalidSpriteIDException
268         *             If the given identifier contains a dot.
269         */
270        protected Sprite addSprite(String identifier, Values position)
271                        throws InvalidSpriteIDException {
272                if (identifier.indexOf('.') >= 0)
273                        throw new InvalidSpriteIDException(
274                                        "Sprite identifiers cannot contain dots.");
275
276                Sprite sprite = sprites.get(identifier);
277
278                if (sprite == null) {
279                        attributeLock = true;
280                        sprite = factory.newSprite(identifier, this, position);
281                        sprites.put(identifier, sprite);
282                        attributeLock = false;
283                } else {
284                        if (position != null)
285                                sprite.setPosition(position);
286                }
287
288                return sprite;
289        }
290
291        /**
292         * Add a sprite of a given subclass of Sprite with the given identifier. If
293         * the sprite already exists, nothing is done. This method allows to add a
294         * sprite of a chosen subclass of Sprite, without using a
295         * {@link SpriteFactory}. Most often you use a sprite factory when all
296         * sprites will pertain to the same subclass. If some sprites pertain to
297         * distinct subclasses, you can use this method.
298         * 
299         * @param identifier
300         *            The identifier of the new sprite to add.
301         * @param spriteClass
302         *            The class of the new sprite to add.
303         * @return The created sprite.
304         */
305        public <T extends Sprite> T addSprite(String identifier,
306                        Class<T> spriteClass) {
307                return addSprite(identifier, spriteClass, null);
308        }
309
310        /**
311         * Same as {@link #addSprite(String, Class)} but also allows to specify an
312         * initial position.
313         * 
314         * @param identifier
315         *            The identifier of the new sprite to add.
316         * @param spriteClass
317         *            The class of the new sprite to add.
318         * @param position
319         *            The sprite position, or null for position (0, 0, 0).
320         * @return The created sprite.
321         */
322        public <T extends Sprite> T addSprite(String identifier,
323                        Class<T> spriteClass, Values position) {
324                try {
325                        T sprite = spriteClass.newInstance();
326                        sprite.init(identifier, this, position);
327                        return sprite;
328                } catch (InstantiationException e) {
329                        System.err.printf(
330                                        "Error while trying to instantiate class %s : %s",
331                                        spriteClass.getName(), e.getMessage());
332                        e.printStackTrace();
333                } catch (IllegalAccessException e) {
334                        e.printStackTrace();
335                }
336                return null;
337        }
338
339        /**
340         * Remove a sprite knowing its identifier. If no such sprite exists, this
341         * fails silently.
342         * 
343         * @param identifier
344         *            The identifier of the sprite to remove.
345         */
346        public void removeSprite(String identifier) {
347                Sprite sprite = sprites.get(identifier);
348
349                if (sprite != null) {
350                        attributeLock = true;
351                        sprites.remove(identifier);
352                        sprite.removed();
353                        attributeLock = false;
354                }
355        }
356
357        // Utility
358
359        protected static Values getPositionValue(Object value) {
360                if (value instanceof Object[]) {
361                        Object[] values = (Object[]) value;
362
363                        if (values.length == 4) {
364                                if (values[0] instanceof Number && values[1] instanceof Number
365                                                && values[2] instanceof Number
366                                                && values[3] instanceof Style.Units) {
367                                        return new Values((Style.Units) values[3],
368                                                        ((Number) values[0]).floatValue(),
369                                                        ((Number) values[1]).floatValue(),
370                                                        ((Number) values[2]).floatValue());
371                                } else {
372                                        System.err
373                                                        .printf("GraphicGraph : cannot parse values[4] for sprite position.%n");
374                                }
375                        } else if (values.length == 3) {
376                                if (values[0] instanceof Number && values[1] instanceof Number
377                                                && values[2] instanceof Number) {
378                                        return new Values(Units.GU,
379                                                        ((Number) values[0]).floatValue(),
380                                                        ((Number) values[1]).floatValue(),
381                                                        ((Number) values[2]).floatValue());
382                                } else {
383                                        System.err
384                                                        .printf("GraphicGraph : cannot parse values[3] for sprite position.%n");
385                                }
386                        } else if (values.length == 1) {
387                                if (values[0] instanceof Number) {
388                                        return new Values(Units.GU,
389                                                        ((Number) values[0]).floatValue());
390                                } else {
391                                        System.err
392                                                        .printf("GraphicGraph : sprite position percent is not a number.%n");
393                                }
394                        } else {
395                                System.err
396                                                .printf("GraphicGraph : cannot transform value '%s' (length=%d) into a position%n",
397                                                                Arrays.toString(values), values.length);
398                        }
399                } else if (value instanceof Number) {
400                        return new Values(Units.GU, ((Number) value).floatValue());
401                } else if (value instanceof Value) {
402                        return new Values((Value) value);
403                } else if (value instanceof Values) {
404                        return new Values((Values) value);
405                } else {
406                        System.err
407                                        .printf("GraphicGraph : cannot place sprite with posiiton '%s' (instance of %s)%n",
408                                                        value, value.getClass().getName());
409                }
410
411                return null;
412        }
413
414        // GraphAttributesListener
415
416        public void graphAttributeAdded(String graphId, long time,
417                        String attribute, Object value) {
418                if (attributeLock)
419                        return; // We want to avoid listening at ourselves.
420
421                if (attribute.startsWith("ui.sprite.")) {
422                        String spriteId = attribute.substring(10);
423
424                        if (spriteId.indexOf('.') < 0) {
425                                if (getSprite(spriteId) == null) {
426                                        // A sprite has been created by another entity.
427                                        // Synchronise this manager.
428
429                                        Values position = null;
430
431                                        if (value != null)
432                                                position = getPositionValue(value);
433
434                                        try {
435                                                addSprite(spriteId, position);
436                                        } catch (InvalidSpriteIDException e) {
437                                                e.printStackTrace();
438                                                throw new RuntimeException(e);
439                                                // Ho !! Dirty !!
440                                        }
441                                }
442                        }
443                }
444        }
445
446        public void graphAttributeChanged(String graphId, long time,
447                        String attribute, Object oldValue, Object newValue) {
448                if (attributeLock)
449                        return; // We want to avoid listening at ourselves.
450
451                if (attribute.startsWith("ui.sprite.")) {
452                        String spriteId = attribute.substring(10);
453
454                        if (spriteId.indexOf('.') < 0) {
455                                Sprite s = getSprite(spriteId);
456
457                                if (s != null) {
458                                        // The sprite has been moved by another entity.
459                                        // Update its position.
460
461                                        if (newValue != null) {
462                                                Values position = getPositionValue(newValue);
463                                                // System.err.printf(
464                                                // "       %%spriteMan set %s Position(%s) (from %s)%n",
465                                                // spriteId, position, newValue );
466                                                s.setPosition(position);
467                                        } else {
468                                                System.err.printf(
469                                                                "%s changed but newValue == null ! (old=%s)%n",
470                                                                spriteId, oldValue);
471                                        }
472                                } else {
473                                        throw new RuntimeException(
474                                                        "WTF ! sprite changed, but not added...%n");
475                                }
476                        }
477                }
478        }
479
480        public void graphAttributeRemoved(String graphId, long time,
481                        String attribute) {
482                if (attributeLock)
483                        return; // We want to avoid listening at ourselves.
484
485                if (attribute.startsWith("ui.sprite.")) {
486                        String spriteId = attribute.substring(10);
487
488                        if (spriteId.indexOf('.') < 0) {
489                                if (getSprite(spriteId) != null) {
490                                        // A sprite has been removed by another entity.
491                                        // Synchronise this manager.
492
493                                        removeSprite(spriteId);
494                                }
495                        }
496                }
497        }
498
499        // Unused.
500
501        public void edgeAttributeAdded(String graphId, long time, String edgeId,
502                        String attribute, Object value) {
503        }
504
505        public void edgeAttributeChanged(String graphId, long time, String edgeId,
506                        String attribute, Object oldValue, Object newValue) {
507        }
508
509        public void edgeAttributeRemoved(String graphId, long time, String edgeId,
510                        String attribute) {
511        }
512
513        public void nodeAttributeAdded(String graphId, long time, String nodeId,
514                        String attribute, Object value) {
515        }
516
517        public void nodeAttributeChanged(String graphId, long time, String nodeId,
518                        String attribute, Object oldValue, Object newValue) {
519        }
520
521        public void nodeAttributeRemoved(String graphId, long time, String nodeId,
522                        String attribute) {
523        }
524}