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}