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.graph.implementations;
033
034import java.util.ArrayList;
035import java.util.Collection;
036import java.util.Collections;
037import java.util.HashMap;
038import java.util.Iterator;
039import java.util.Map;
040
041import org.graphstream.graph.CompoundAttribute;
042import org.graphstream.graph.Element;
043import org.graphstream.graph.NullAttributeException;
044
045/**
046 * A base implementation of an element.
047 * 
048 * <p>
049 * This class is the Base class for {@link org.graphstream.graph.Node},
050 * {@link org.graphstream.graph.Edge} and {@link org.graphstream.graph.Graph}.
051 * An element is made of an unique and arbitrary identifier that identifies it,
052 * and a set of attributes.
053 * </p>
054 * 
055 * @since 20040910
056 */
057public abstract class AbstractElement implements Element {
058        public static enum AttributeChangeEvent {
059                ADD, CHANGE, REMOVE
060        };
061
062        // Attribute
063
064        // protected static Set<String> emptySet = new HashSet<String>();
065
066        /**
067         * Tag of this element.
068         */
069        protected final String id;
070
071        /**
072         * The index of this element.
073         */
074        private int index;
075
076        /**
077         * Attributes map. This map is created only when needed. It contains pairs
078         * (key,value) where the key is the attribute name and the value an Object.
079         */
080        protected HashMap<String, Object> attributes = null;
081
082        /**
083         * Vector used when removing attributes to avoid recursive removing.
084         */
085        protected ArrayList<String> attributesBeingRemoved = null;
086
087        // Construction
088
089        /**
090         * New element.
091         * 
092         * @param id
093         *            The unique identifier of this element.
094         */
095        public AbstractElement(String id) {
096                assert id != null : "Graph elements cannot have a null identifier";
097                this.id = id;
098        }
099
100        // Access
101
102        public String getId() {
103                return id;
104        }
105
106        public int getIndex() {
107                return index;
108        }
109
110        /**
111         * Used by subclasses to change the index of an element
112         * 
113         * @param index
114         *            the new index
115         */
116        protected void setIndex(int index) {
117                this.index = index;
118        }
119
120        // XXX UGLY. how to create events in the abstract element ?
121        // XXX The various methods that add and remove attributes will propagate an
122        // event
123        // XXX sometimes this is in response to another event and the
124        // sourceId/timeId is given
125        // XXX sometimes this comes from a direct call to
126        // add/change/removeAttribute() methods
127        // XXX in which case we need to generate a new event (sourceId/timeId) using
128        // the graph
129        // XXX id and a new time. These methods allow access to this.
130        // protected abstract String myGraphId(); // XXX
131
132        // protected abstract long newEvent(); // XXX
133
134        protected abstract boolean nullAttributesAreErrors(); // XXX
135
136        /**
137         * Called for each change in the attribute set. This method must be
138         * implemented by sub-elements in order to send events to the graph
139         * listeners.
140         * 
141         * @param sourceId
142         *            The source of the change.
143         * @param timeId
144         *            The source time of the change, for synchronization.
145         * @param attribute
146         *            The attribute name that changed.
147         * @param event
148         *            The type of event among ADD, CHANGE and REMOVE.
149         * @param oldValue
150         *            The old value of the attribute, null if the attribute was
151         *            added.
152         * @param newValue
153         *            The new value of the attribute, null if the attribute is about
154         *            to be removed.
155         */
156        protected abstract void attributeChanged(AttributeChangeEvent event,
157                        String attribute, Object oldValue, Object newValue);
158
159        /**
160         * @complexity O(log(n)) with n being the number of attributes of this
161         *             element.
162         */
163        // public Object getAttribute( String key )
164        @SuppressWarnings("all")
165        public <T> T getAttribute(String key) {
166                if (attributes != null) {
167                        T value = (T) attributes.get(key);
168
169                        if (value != null)
170                                return value;
171                }
172
173                if (nullAttributesAreErrors())
174                        throw new NullAttributeException(key);
175
176                return null;
177        }
178
179        /**
180         * @complexity O(log(n*m)) with n being the number of attributes of this
181         *             element and m the number of keys given.
182         */
183        // public Object getFirstAttributeOf( String ... keys )
184        @SuppressWarnings("all")
185        public <T> T getFirstAttributeOf(String... keys) {
186                Object o = null;
187
188                if (attributes != null) {
189                        for (String key : keys) {
190                                o = attributes.get(key);
191
192                                if (o != null)
193                                        return (T) o;
194                        }
195                }
196
197                if (o == null && nullAttributesAreErrors())
198                        throw new NullAttributeException();
199
200                return (T) o;
201        }
202
203        /**
204         * @complexity O(log(n)) with n being the number of attributes of this
205         *             element.
206         */
207        // public Object getAttribute( String key, Class<?> clazz )
208        @SuppressWarnings("all")
209        public <T> T getAttribute(String key, Class<T> clazz) {
210                if (attributes != null) {
211                        Object o = attributes.get(key);
212
213                        if (o != null && clazz.isInstance(o))
214                                return (T) o;
215                }
216
217                if (nullAttributesAreErrors())
218                        throw new NullAttributeException(key);
219
220                return null;
221        }
222
223        /**
224         * @complexity O(log(n*m)) with n being the number of attributes of this
225         *             element and m the number of keys given.
226         */
227        // public Object getFirstAttributeOf( Class<?> clazz, String ... keys )
228        @SuppressWarnings("all")
229        public <T> T getFirstAttributeOf(Class<T> clazz, String... keys) {
230                Object o = null;
231
232                if (attributes == null)
233                        return null;
234
235                for (String key : keys) {
236                        o = attributes.get(key);
237
238                        if (o != null && clazz.isInstance(o))
239                                return (T) o;
240                }
241
242                if (nullAttributesAreErrors())
243                        throw new NullAttributeException();
244
245                return null;
246        }
247
248        /**
249         * @complexity O(log(n)) with n being the number of attributes of this
250         *             element.
251         */
252        public CharSequence getLabel(String key) {
253                if (attributes != null) {
254                        Object o = attributes.get(key);
255
256                        if (o != null && o instanceof CharSequence)
257                                return (CharSequence) o;
258                }
259
260                if (nullAttributesAreErrors())
261                        throw new NullAttributeException(key);
262
263                return null;
264        }
265
266        /**
267         * @complexity O(log(n)) with n being the number of attributes of this
268         *             element.
269         */
270        public double getNumber(String key) {
271                if (attributes != null) {
272                        Object o = attributes.get(key);
273
274                        if (o != null) {
275                                if (o instanceof Number)
276                                        return ((Number) o).doubleValue();
277
278                                if (o instanceof String) {
279                                        try {
280                                                return Double.parseDouble((String) o);
281                                        } catch (NumberFormatException e) {
282                                        }
283                                } else if (o instanceof CharSequence) {
284                                        try {
285                                                return Double
286                                                                .parseDouble(((CharSequence) o).toString());
287                                        } catch (NumberFormatException e) {
288                                        }
289                                }
290                        }
291                }
292
293                if (nullAttributesAreErrors())
294                        throw new NullAttributeException(key);
295
296                return Double.NaN;
297        }
298
299        /**
300         * @complexity O(log(n)) with n being the number of attributes of this
301         *             element.
302         */
303        @SuppressWarnings("unchecked")
304        public ArrayList<? extends Number> getVector(String key) {
305                if (attributes != null) {
306                        Object o = attributes.get(key);
307
308                        if (o != null && o instanceof ArrayList)
309                                return ((ArrayList<? extends Number>) o);
310                }
311
312                if (nullAttributesAreErrors())
313                        throw new NullAttributeException(key);
314
315                return null;
316        }
317
318        /**
319         * @complexity O(log(n)) with n being the number of attributes of this
320         *             element.
321         */
322        public Object[] getArray(String key) {
323                if (attributes != null) {
324                        Object o = attributes.get(key);
325
326                        if (o != null && o instanceof Object[])
327                                return ((Object[]) o);
328                }
329
330                if (nullAttributesAreErrors())
331                        throw new NullAttributeException(key);
332
333                return null;
334        }
335
336        /**
337         * @complexity O(log(n)) with n being the number of attributes of this
338         *             element.
339         */
340        public HashMap<?, ?> getHash(String key) {
341                if (attributes != null) {
342                        Object o = attributes.get(key);
343
344                        if (o != null) {
345                                if (o instanceof HashMap<?, ?>)
346                                        return ((HashMap<?, ?>) o);
347                                if (o instanceof CompoundAttribute)
348                                        return ((CompoundAttribute) o).toHashMap();
349                        }
350                }
351
352                if (nullAttributesAreErrors())
353                        throw new NullAttributeException(key);
354
355                return null;
356        }
357
358        /**
359         * @complexity O(log(n)) with n being the number of attributes of this
360         *             element.
361         */
362        public boolean hasAttribute(String key) {
363                if (attributes != null)
364                        return attributes.containsKey(key);
365
366                return false;
367        }
368
369        /**
370         * @complexity O(log(n)) with n being the number of attributes of this
371         *             element.
372         */
373        public boolean hasAttribute(String key, Class<?> clazz) {
374                if (attributes != null) {
375                        Object o = attributes.get(key);
376
377                        if (o != null)
378                                return (clazz.isInstance(o));
379                }
380
381                return false;
382        }
383
384        /**
385         * @complexity O(log(n)) with n being the number of attributes of this
386         *             element.
387         */
388        public boolean hasLabel(String key) {
389                if (attributes != null) {
390                        Object o = attributes.get(key);
391
392                        if (o != null)
393                                return (o instanceof CharSequence);
394                }
395
396                return false;
397        }
398
399        /**
400         * @complexity O(log(n)) with n being the number of attributes of this
401         *             element.
402         */
403        public boolean hasNumber(String key) {
404                if (attributes != null) {
405                        Object o = attributes.get(key);
406
407                        if (o != null)
408                                return (o instanceof Number);
409                }
410
411                return false;
412        }
413
414        /**
415         * @complexity O(log(n)) with n being the number of attributes of this
416         *             element.
417         */
418        public boolean hasVector(String key) {
419                if (attributes != null) {
420                        Object o = attributes.get(key);
421
422                        if (o != null && o instanceof ArrayList<?>)
423                                return true;
424                }
425
426                return false;
427        }
428
429        /**
430         * @complexity O(log(n)) with n being the number of attributes of this
431         *             element.
432         */
433        public boolean hasArray(String key) {
434                if (attributes != null) {
435                        Object o = attributes.get(key);
436
437                        if (o != null && o instanceof Object[])
438                                return true;
439                }
440
441                return false;
442        }
443
444        /**
445         * @complexity O(log(n)) with n being the number of attributes of this
446         *             element.
447         */
448        public boolean hasHash(String key) {
449                if (attributes != null) {
450                        Object o = attributes.get(key);
451
452                        if (o != null
453                                        && (o instanceof HashMap<?, ?> || o instanceof CompoundAttribute))
454                                return true;
455                }
456
457                return false;
458        }
459
460        public Iterator<String> getAttributeKeyIterator() {
461                if (attributes != null)
462                        return attributes.keySet().iterator();
463
464                return null;
465        }
466
467        public Iterable<String> getEachAttributeKey() {
468                return getAttributeKeySet();
469        }
470
471        public Collection<String> getAttributeKeySet() {
472                if (attributes != null)
473                        return (Collection<String>) Collections
474                                        .unmodifiableCollection(attributes.keySet());
475
476                return Collections.emptySet();
477        }
478
479        // public Map<String,Object> getAttributeMap()
480        // {
481        // if( attributes != null )
482        // {
483        // if( constMap == null )
484        // constMap = new ConstMap<String,Object>( attributes );
485        //
486        // return constMap;
487        // }
488        //
489        // return null;
490        // }
491
492        /**
493         * Override the Object method
494         */
495        @Override
496        public String toString() {
497                return id;
498        }
499
500        public int getAttributeCount() {
501                if (attributes != null)
502                        return attributes.size();
503
504                return 0;
505        }
506
507        // Command
508
509        public void clearAttributes() {
510                if (attributes != null) {
511                        for (Map.Entry<String, Object> entry : attributes.entrySet())
512                                attributeChanged(AttributeChangeEvent.REMOVE, entry.getKey(),
513                                                entry.getValue(), null);
514
515                        attributes.clear();
516                }
517        }
518
519        protected void clearAttributesWithNoEvent() {
520                if (attributes != null)
521                        attributes.clear();
522        }
523
524        /**
525         * @complexity O(log(n)) with n being the number of attributes of this
526         *             element.
527         */
528        public void addAttribute(String attribute, Object... values) {
529                if (attributes == null)
530                        attributes = new HashMap<String, Object>(1);
531
532                Object oldValue;
533                Object value;
534
535                if (values.length == 0)
536                        value = true;
537                else if (values.length == 1)
538                        value = values[0];
539                else
540                        value = values;
541
542                AttributeChangeEvent event = AttributeChangeEvent.ADD;
543
544                if (attributes.containsKey(attribute)) // In case the value is null,
545                        event = AttributeChangeEvent.CHANGE; // but the attribute exists.
546
547                oldValue = attributes.put(attribute, value);
548                attributeChanged(event, attribute, oldValue, value);
549        }
550
551        /**
552         * @complexity O(log(n)) with n being the number of attributes of this
553         *             element.
554         */
555        public void changeAttribute(String attribute, Object... values) {
556                addAttribute(attribute, values);
557        }
558
559        /**
560         * @complexity O(log(n)) with n being the number of attributes of this
561         *             element.
562         */
563        public void setAttribute(String attribute, Object... values) {
564                addAttribute(attribute, values);
565        }
566
567        /**
568         * @complexity O(log(n)) with n being the number of attributes of this
569         *             element.
570         */
571        public void addAttributes(Map<String, Object> attributes) {
572                if (this.attributes == null)
573                        this.attributes = new HashMap<String, Object>(attributes.size());
574
575                Iterator<String> i = attributes.keySet().iterator();
576                Iterator<Object> j = attributes.values().iterator();
577
578                while (i.hasNext() && j.hasNext())
579                        addAttribute(i.next(), j.next());
580        }
581
582        /**
583         * @complexity O(log(n)) with n being the number of attributes of this
584         *             element.
585         */
586        public void removeAttribute(String attribute) {
587                if (attributes != null) {
588                        //
589                        // 'attributesBeingRemoved' is created only if this is required.
590                        //
591                        if (attributesBeingRemoved == null)
592                                attributesBeingRemoved = new ArrayList<String>();
593
594                        //
595                        // Avoid recursive calls when synchronizing graphs.
596                        //
597                        if (attributes.containsKey(attribute)
598                                        && !attributesBeingRemoved.contains(attribute)) {
599                                attributesBeingRemoved.add(attribute);
600
601                                attributeChanged(AttributeChangeEvent.REMOVE, attribute,
602                                                attributes.get(attribute), null);
603
604                                attributesBeingRemoved
605                                                .remove(attributesBeingRemoved.size() - 1);
606                                attributes.remove(attribute);
607                        }
608                }
609        }
610}