001package org.biopax.paxtools.util;
002
003import org.apache.commons.logging.Log;
004import org.apache.commons.logging.LogFactory;
005import org.biopax.paxtools.model.BioPAXElement;
006
007import java.util.*;
008
009
010/**
011 * A thread-safe set of BioPAX objects that also prevents adding several elements 
012 * having the same URI. It also allows to quickly get a BioPAX 
013 * object by URI. This set is used internally by all multiple cardinality
014 * BioPAX object property and inverse property implementations (since v4.2, 2013).
015 *
016 * @author rodche
017 */
018public class BiopaxSafeSet<E extends BioPAXElement> extends AbstractSet<E>
019{
020        private final static Log LOG = LogFactory.getLog(BiopaxSafeSet.class);
021        
022        //initial map is to be reset to a modifiable instance on first write
023        private final static Map empty = Collections.unmodifiableMap(Collections.emptyMap());
024
025        private Map<String,E> map;
026        
027        public BiopaxSafeSet()
028        {
029                map = empty;
030        }
031
032        public Iterator<E> iterator() {
033                synchronized (map) {
034                        return map.values().iterator();
035                }
036        }
037
038        public int size()
039        {
040                synchronized (map) {
041                        return map.size();
042                }
043        }
044        
045        @Override
046        public boolean add(E bpe)
047        {
048                synchronized (map) {    
049                        if(map.isEmpty())
050                        {       //new real map instead of initial fake (empty) one
051                                this.map = BPCollections.I.createMap();
052                        }
053                }
054                        
055                String uri = bpe.getRDFId();
056                
057                synchronized (map) { //sync on the new map instance             
058                        if (!map.containsKey(uri)) {
059                                map.put(uri, bpe);
060                                return true;
061                        } else {
062                                // do not throw an ex., because duplicate attempts occur naturally
063                                // (e.g., same PE on both left and right sides of a reaction
064                                // causes same participant/participantOf is touched twice)
065                                LOG.debug("ignored duplicate:" + uri);
066                                return false;
067                        }
068                }
069        }
070        
071        
072        @Override
073        public boolean contains(Object o) {
074                if(map==empty)
075                        return false;
076                
077                synchronized (map) {//to sync due to two operations
078                        return super.contains(o) 
079                                && ( get(((E)o).getRDFId()) == o );
080                }
081        }
082        
083        
084        /**
085         * Gets a BioPAX element by URI.
086         * 
087         * @param uri absolute URI of a BioPAX individual
088         * @return BioPAX object or null
089         */
090        public E get(String uri) {
091                
092                if(map==empty)
093                        return null;
094                
095                synchronized (map) {
096                        return map.get(uri);
097                }
098        }
099}