001package org.biopax.paxtools.impl;
002
003import org.biopax.paxtools.controller.ModelUtils;
004import org.biopax.paxtools.controller.SimpleEditorMap;
005import org.biopax.paxtools.controller.SimpleMerger;
006import org.biopax.paxtools.model.BioPAXElement;
007import org.biopax.paxtools.model.BioPAXFactory;
008import org.biopax.paxtools.model.BioPAXLevel;
009import org.biopax.paxtools.model.Model;
010import org.biopax.paxtools.util.BPCollections;
011import org.biopax.paxtools.util.ClassFilterSet;
012import org.biopax.paxtools.util.IllegalBioPAXArgumentException;
013
014import java.util.*;
015
016/**
017 * This is the default implementation of the {@link Model}. Use a factory to create a model.
018 */
019public class ModelImpl implements Model
020{
021// ------------------------------ FIELDS ------------------------------
022
023        private static final long serialVersionUID = -2087521863213381434L;
024        protected final Map<String, BioPAXElement> idMap;
025    private final Map<String, String> nameSpacePrefixMap;
026        private BioPAXLevel level;
027        private transient BioPAXFactory factory;
028    private final transient Set<BioPAXElement> exposedObjectSet;
029    private boolean addDependencies = false;
030    private String xmlBase;
031
032// --------------------------- CONSTRUCTORS ---------------------------
033
034    protected ModelImpl() {
035                idMap = BPCollections.I.createMap();
036        nameSpacePrefixMap = new HashMap<String, String>();
037        this.exposedObjectSet = new UnmodifiableImplicitSet(idMap.values());
038        }
039
040    protected ModelImpl(BioPAXLevel level)
041        {
042           this(level.getDefaultFactory());
043        }
044
045        public ModelImpl(BioPAXFactory factory)
046        {
047                this();
048                this.factory = factory;
049                this.level = factory.getLevel();
050    }
051
052// --------------------- GETTER / SETTER METHODS ---------------------
053
054    public synchronized boolean containsID(String id) {
055        return this.idMap.containsKey(id);
056    }
057
058    
059    public synchronized BioPAXElement getByID(String id) {
060        BioPAXElement ret = this.idMap.get(id);
061        if(ret != null) {
062                assert ret.getRDFId().equals(id);
063        }
064        return ret;
065    }
066
067
068    public Map<String, String> getNameSpacePrefixMap()
069        {
070                return nameSpacePrefixMap;
071        }
072
073    
074    private synchronized void setNameSpacePrefixMap(Map<String, String> nameSpacePrefixMap) {
075                this.nameSpacePrefixMap.clear();
076                this.nameSpacePrefixMap.putAll(nameSpacePrefixMap);
077        }
078    
079
080    public void setFactory(BioPAXFactory factory)
081        {
082                this.factory = factory;
083                this.level = factory.getLevel();
084        }
085
086// ------------------------ INTERFACE METHODS ------------------------
087
088// --------------------- Interface Model ---------------------
089
090// --------------------- ACCESORS and MUTATORS---------------------
091
092        public synchronized Set<BioPAXElement> getObjects()
093        {
094                return exposedObjectSet;
095        }
096
097        public synchronized <T extends BioPAXElement> Set<T> getObjects(Class<T> filterBy)
098        {
099                return new ClassFilterSet<BioPAXElement,T>(exposedObjectSet, filterBy);
100        }
101
102        synchronized void synchronizedsetObjects(Set<BioPAXElement> objects) {          
103                idMap.clear();
104        for(BioPAXElement bpe : objects) {
105                add(bpe);
106        }
107    }
108
109    
110        public synchronized void remove(BioPAXElement aBioPAXElement)
111        {               
112                if(this.contains(aBioPAXElement))
113                        this.idMap.remove(aBioPAXElement.getRDFId());
114        }
115                            
116        public synchronized <T extends BioPAXElement> T addNew(Class<T> c, String id)
117        {
118                T paxElement = factory.create(c, id);
119                this.add(paxElement);
120                return paxElement;
121        }
122
123        /**
124         * This method returns true if given element 
125         * is the same object ("==") as the object stored in the model
126         * usually (for self-consistent models) but not necessarily under the element's ID.
127         * 
128         * @param aBioPAXElement BioPAX object (individual)
129         * @return true/false - whether this model contains the object or not
130         */
131        public synchronized boolean contains(BioPAXElement aBioPAXElement)
132        {
133                return this.idMap.get(aBioPAXElement.getRDFId()) == aBioPAXElement;
134        }
135
136// -------------------------- OTHER METHODS --------------------------
137
138        public synchronized void add(BioPAXElement aBioPAXElement)
139        {
140                String rdfId = aBioPAXElement.getRDFId();
141        if(!this.level.hasElement(aBioPAXElement))
142        {
143            throw new IllegalBioPAXArgumentException(
144                "Given object is of wrong level");
145        }
146        
147        if (rdfId == null)
148                {
149                        throw new IllegalBioPAXArgumentException(
150                                "null ID: every object must have an RDF ID");
151                }
152                else if (this.idMap.containsKey(rdfId))
153                {
154                        throw new IllegalBioPAXArgumentException(
155                                "I already have an object with the same ID: " + rdfId +
156                                        ". Try removing it first");
157                }
158                else if (this.contains(aBioPAXElement))
159                {
160                        throw new IllegalBioPAXArgumentException(
161                                "duplicate element:" + aBioPAXElement);
162                }
163                else
164                {
165                        this.idMap.put(rdfId, aBioPAXElement);
166                }
167        }
168
169
170    public BioPAXLevel getLevel()
171        {
172                return level;
173        }
174
175        // used by hibernate
176    synchronized void setLevel(BioPAXLevel level) {
177                this.level = level;
178                this.factory = level.getDefaultFactory();
179        }
180        
181
182    public void setAddDependencies(boolean value) {
183        this.addDependencies = value;
184    }
185
186    
187    public boolean isAddDependencies() {
188        return addDependencies;
189    }
190
191    private class UnmodifiableImplicitSet implements Set<BioPAXElement>
192        {
193                private final Collection<BioPAXElement> elements;
194
195                public UnmodifiableImplicitSet(
196                        Collection<BioPAXElement> elements)
197                {
198
199                        this.elements = elements;
200                }
201
202                public int size()
203                {
204                        return elements.size();
205                }
206
207                public boolean isEmpty()
208                {
209                        return elements.isEmpty();
210                }
211
212                public boolean contains(Object o)
213                {
214                        return elements.contains(o);
215                }
216
217                public Iterator<BioPAXElement> iterator()
218                {
219                        return elements.iterator();
220                }
221
222                public Object[] toArray()
223                {
224                        return elements.toArray();
225                }
226
227                public <T> T[] toArray(T[] a)
228                {
229            return elements.toArray(a);
230                }
231
232                public boolean add(BioPAXElement bioPAXElement)
233                {
234                        throw new UnsupportedOperationException();
235                }
236
237                public boolean remove(Object o)
238                {
239                        throw new UnsupportedOperationException();
240                }
241
242                public boolean containsAll(Collection<?> c)
243                {
244                        return elements.containsAll(c);
245                }
246
247                public boolean addAll(Collection<? extends BioPAXElement> c)
248                {
249                        throw new UnsupportedOperationException();
250                }
251
252                public boolean retainAll(Collection<?> c)
253                {
254                        throw new UnsupportedOperationException();
255                }
256
257                public boolean removeAll(Collection<?> c)
258                {
259                        throw new UnsupportedOperationException();
260                }
261
262                public void clear()
263                {
264                        throw new UnsupportedOperationException();
265                }
266        }
267
268    /**
269     * It does not automatically replace or clean up the old 
270     * element's object properties, therefore, some child 
271     * elements may become "dangling" if they were used by
272     * the replaced element only.
273     * 
274     * Can also clear object properties (- replace with null).
275     */
276        public synchronized void replace(final BioPAXElement existing, final BioPAXElement replacement) 
277        {
278                 ModelUtils.replace(this, Collections.singletonMap(existing, replacement));
279                 remove(existing);
280                 if(replacement != null)
281                         add(replacement);
282        }
283        
284        
285        /**
286         * This is default implementation that uses the 
287         * id-based merging ({@link SimpleMerger#merge(Model, Model...)})
288         * 
289         * NOTE: some applications, such as those dealing with persistence/transactions 
290         * or advanced BioPAX alignment/comparison algorithms (like the Patch), 
291         * may have to implement and use a more specific method instead.
292         * 
293         * @see SimpleMerger
294         * @see Model#merge(Model)
295         */
296        public synchronized void merge(Model source) {
297                SimpleMerger merger = new SimpleMerger(
298                        SimpleEditorMap.get(level));
299                if(source == null)
300                        merger.merge(this, this); //repairs itself
301                else
302                        merger.merge(this, source);
303        }
304
305        
306        /**
307         * 
308         * This implementation "repairs" the model 
309         * without unnecessarily copying objects:
310     * - recursively adds lost "children" (not null object property values
311     *   for which {@link Model#contains(BioPAXElement)} returns False)
312     * - updates object properties (should refer to model's elements)
313         * 
314         */
315        @Override
316        public synchronized void repair() {
317                // updates props and children
318                merge(null);
319        }
320
321        @Override
322        public void setXmlBase(String base) {
323                this.xmlBase = base;
324        }
325
326        @Override
327        public String getXmlBase() {
328                return this.xmlBase;
329        }
330}