Thread safety changes in objmap2
[simantics/platform.git] / bundles / org.simantics.objmap2 / src / org / simantics / objmap / graph / impl / Mapping.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2013 Association for Decentralized Information Management
3  * in Industry THTH ry.
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.objmap.graph.impl;
13
14
15 import java.util.AbstractSet;
16 import java.util.ArrayList;
17 import java.util.Collection;
18 import java.util.Iterator;
19 import java.util.List;
20 import java.util.Set;
21
22 import org.simantics.db.ReadGraph;
23 import org.simantics.db.WriteGraph;
24 import org.simantics.db.exception.DatabaseException;
25 import org.simantics.objmap.backward.IBackwardMapping;
26 import org.simantics.objmap.exceptions.MappingException;
27 import org.simantics.objmap.forward.IForwardMapping;
28 import org.simantics.objmap.graph.IMapping;
29 import org.simantics.objmap.graph.IMappingListener;
30 import org.simantics.objmap.graph.schema.ILinkType;
31 import org.simantics.objmap.graph.schema.IMappingSchema;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34
35 import gnu.trove.map.hash.THashMap;
36
37 /**
38  * An implementation of IMapping. The class should not be created
39  * directly but using methods in Mappings.
40  * @see org.simantics.objmap.graph.Mappings
41  * @author Hannu Niemist√∂
42  */
43 public class Mapping<Domain, Range> implements IMapping<Domain, Range> {
44
45         static final Logger LOGGER = LoggerFactory.getLogger(Mapping.class);
46         
47         
48         IMappingSchema<Domain, Range> schema;
49         
50         THashMap<Domain, Link<Domain,Range>> domain = new THashMap<Domain, Link<Domain,Range>>();
51         THashMap<Range, Link<Domain,Range>> range = new THashMap<Range, Link<Domain,Range>>();
52         ArrayList<IMappingListener> listeners = new ArrayList<IMappingListener>();
53
54         ArrayList<Link<Domain,Range>> modifiedDomainLinks = new ArrayList<Link<Domain,Range>>();
55         ArrayList<Link<Domain,Range>> modifiedRangeLinks = new ArrayList<Link<Domain,Range>>();
56
57         boolean disposed = false;
58         
59         boolean listensDomain; 
60         
61         public Mapping(IMappingSchema<Domain, Range> schema, boolean listensDomain) {
62                 this.schema = schema;
63                 this.listensDomain = listensDomain;
64         }
65         
66         private void removeLink(Link<Domain,Range> link) {
67                 if(link.domainModified)
68                         modifiedDomainLinks.remove(link);
69                 if(link.rangeModified)
70                         modifiedRangeLinks.remove(link);
71                 link.removed = true;
72         }
73         
74         private void createDomain(WriteGraph g, Link<Domain,Range> link) throws MappingException {
75             LOGGER.trace("        createDomain for " + link.rangeElement);
76                 ILinkType<Domain,Range> type = schema.linkTypeOfRangeElement(link.rangeElement);
77                 Domain domainElement = type.createDomainElement(g, link.rangeElement);
78                 link.type = type;
79                 link.domainElement = domainElement;
80                 domain.put(domainElement, link);
81                 type.createDomain(g, new RangeToDomain(), domainElement, link.rangeElement);
82                 
83         // TODO Should we do this only if the mapping is listening?
84         domainModified(link);
85         }
86         
87         private void createRange(ReadGraph g, Link<Domain,Range> link) throws MappingException {
88                 ILinkType<Domain,Range> type = schema.linkTypeOfDomainElement(g, link.domainElement);           
89                 Range rangeElement = type.createRangeElement(g, link.domainElement);
90                 
91                 link.type = type;
92                 link.rangeElement = rangeElement;
93                 range.put(rangeElement, link);
94                 type.createRange(g, new DomainToRange(), link.domainElement, rangeElement);
95         }
96         
97         Set<Domain> domainSet = new AbstractSet<Domain>() {
98
99                 public boolean add(Domain e) {
100                         if(domain.containsKey(e))
101                                 return false;
102                         Link<Domain,Range> link = new Link<Domain,Range>(null, e, null);
103                         domain.put(e, link);
104                         modifiedDomainLinks.add(link);
105                         return true;
106                 }
107                 
108                 public boolean contains(Object o) {
109                         return domain.contains(o);
110                 }
111                 
112                 public boolean remove(Object o) {
113                         Link<Domain,Range> link = domain.remove(o);                     
114                         if(link == null)
115                                 return false;
116                         removeLink(link);
117                         if(link.rangeElement != null)
118                                 range.remove(link.rangeElement);
119                         return true;    
120                 }
121                 
122                 @Override
123                 public Iterator<Domain> iterator() {
124                     // FIXME does not implement Iterator.remove correctly
125                         return domain.keySet().iterator();
126                 }
127
128                 @Override
129                 public int size() {
130                         return domain.size();
131                 }
132                 
133         };
134         
135         Set<Range> rangeSet = new AbstractSet<Range>() {
136
137                 public boolean add(Range e) {
138                         if(range.containsKey(e))
139                                 return false;
140                         Link<Domain,Range> link = new Link<Domain,Range>(null, null, e);
141                         range.put(e, link);
142                         modifiedRangeLinks.add(link);
143                         return true;
144                 }
145                 
146                 public boolean contains(Object o) {
147                         return range.contains(o);
148                 }
149                 
150                 public boolean remove(Object o) {
151                         Link<Domain,Range> link = range.remove(o);                      
152                         if(link == null)
153                                 return false;
154                         removeLink(link);
155                         if(link.domainElement != null)
156                                 domain.remove(link.domainElement);
157                         return true;
158                 }
159                 
160                 @Override
161                 public Iterator<Range> iterator() {
162                     // FIXME does not implement Iterator.remove correctly
163                         return range.keySet().iterator();
164                 }
165
166                 @Override
167                 public int size() {
168                         return range.size();
169                 }
170                 
171         };
172         
173         class DomainToRange implements IForwardMapping<Domain, Range> {
174
175                 public DomainToRange() {
176                 }
177
178                 @Override
179                 public Range get(Domain element)  {
180                         Link<Domain,Range> link = domain.get(element);
181                         if (link != null)
182                                 return link.rangeElement;
183                         return null;
184                         
185                 }
186                 
187                 @Override
188                 public Range map(ReadGraph graph, Domain element)
189                                 throws MappingException {
190                         Link<Domain,Range> link = domain.get(element);
191                         if(link == null) {
192                             link = new Link<Domain,Range>(null, element, null);
193                     link.domainModified = true;
194                     modifiedDomainLinks.add(link);
195                             domain.put(element, link);           
196                             createRange(graph, link);   
197                         }
198                         else if(link.type == null) 
199                                 createRange(graph, link);
200             return link.rangeElement;
201                 }
202                 
203                 @Override
204                 public Set<Domain> getDomain() {
205                         return domain.keySet();
206                 }
207                 
208         };
209         
210         class RangeToDomain extends DomainToRange implements IBackwardMapping<Domain, Range> {
211
212                 public RangeToDomain() {
213                         super();
214                 }
215                 
216                 @Override
217                 public Domain inverseGet(Range element) {
218                         
219                         Link<Domain,Range> link = range.get(element);
220                         if(link != null)
221                                 return link.domainElement;
222                         return null;
223                 }
224                 
225                 @Override
226                 public Domain inverseMap(WriteGraph graph, Range element)
227                                 throws MappingException {
228                         Link<Domain,Range> link = range.get(element);
229                         if(link == null) {
230                             link = new Link<Domain,Range>(null, null, element);
231                             link.rangeModified = true;
232                 modifiedRangeLinks.add(link);
233                             range.put(element, link);
234                             createDomain(graph, link);                          
235                         }
236                         else if(link.type == null)
237                                 createDomain(graph, link);
238                         return link.domainElement;
239                 }
240                 
241                 
242                 @Override
243                 public Set<Range> getRange() {
244                         return range.keySet();
245                 }
246         };
247         
248         @Override
249         public Set<Domain> getDomain() {
250                 return domainSet;
251         }
252         
253         @Override
254         public Set<Range> getRange() {
255                 return rangeSet;
256         }
257         
258         
259         @Override
260         public synchronized Collection<Domain> updateDomain(WriteGraph g) throws MappingException {
261             LOGGER.trace("Mapping.updateDomain");
262                 RangeToDomain map = new RangeToDomain();
263                 ArrayList<Domain> updated = new ArrayList<Domain>();
264                 while(!modifiedRangeLinks.isEmpty()) {
265                     LOGGER.trace("    modifiedRangeLinks.size() = " + modifiedRangeLinks.size());
266                     
267                         Link<Domain,Range> link = modifiedRangeLinks.remove(modifiedRangeLinks.size()-1);
268                         link.rangeModified = false;
269                         /*if(link.domainModified) {
270                                 link.domainModified = false;
271                                 modifiedDomainLinks.remove(link);
272                         }*/
273                         
274                         if(link.type == null) {
275                                 createDomain(g, link);
276                         }
277                         else {
278                                 if(link.type.updateDomain(g, map, link.domainElement, link.rangeElement))
279                                         updated.add(link.domainElement);
280                         }
281                 }       
282                 if (listensDomain)
283                         updateRange(g); //FIXME: without this listening would stop. 
284                 return updated;
285         }
286         
287         @Override
288         public synchronized Collection<Range> updateRange(ReadGraph g) throws MappingException {
289             LOGGER.trace("Mapping.updateRange");
290                 DomainToRange map = new DomainToRange();
291                 ArrayList<Range> updated = new ArrayList<Range>();
292                 while(!modifiedDomainLinks.isEmpty()) {             
293                     LOGGER.trace("    modifiedDomainLinks.size() = " + modifiedDomainLinks.size());
294                     
295                         Link<Domain,Range> link = modifiedDomainLinks.remove(modifiedDomainLinks.size()-1);
296                         link.domainModified = false;
297                         /*if(link.rangeModified) {
298                                 link.rangeModified = false;
299                                 modifiedRangeLinks.remove(link);
300                         }*/
301                         
302                         if(link.type == null) {
303                                 createRange(g, link);
304                         }
305                         
306                         if(listensDomain) {
307                             RangeUpdateRequest<Domain,Range> request = new RangeUpdateRequest<Domain,Range>(link, map, this);
308                             boolean changes;
309                             try {
310                                 changes = g.syncRequest(request, request) > 0;
311                 } catch (DatabaseException e) {
312                     throw new MappingException(e);
313                 }
314                             
315                             if (changes)
316                                 updated.add(link.rangeElement);
317                         }
318                         else
319                             if(link.type.updateRange(g, map, link.domainElement, link.rangeElement))
320                                 updated.add(link.rangeElement);
321                 }       
322                 return updated;
323         }
324
325         @Override
326         public Range get(Domain domainElement) {
327                 Link<Domain,Range> link = domain.get(domainElement);
328                 if(link == null)
329                         return null;
330                 return link.rangeElement;
331         }
332
333         @Override
334         public Domain inverseGet(Range rangeElement) {
335                 Link<Domain,Range> link = range.get(rangeElement);
336                 if(link == null)
337                         return null;
338                 return link.domainElement;
339         }
340
341         @Override
342         public Domain inverseMap(WriteGraph g, Range rangeElement) throws MappingException {
343                 getRange().add(rangeElement);
344                 updateDomain(g);
345                 return inverseGet(rangeElement);
346         }
347
348         @Override
349         public Range map(ReadGraph g, Domain domainElement) throws MappingException {
350                 getDomain().add(domainElement);
351                 updateRange(g);
352                 return get(domainElement);
353         }
354
355         void domainModified(Link<Domain,Range> link) {
356             if(!link.domainModified) {          
357                 synchronized(modifiedDomainLinks) {
358                     LOGGER.trace("        domainModified for " + link.rangeElement);
359                 link.domainModified = true;
360                 modifiedDomainLinks.add(link);
361                 if(modifiedDomainLinks.size() == 1) {
362                     for(IMappingListener listener : listeners)
363                         listener.domainModified();
364                 }
365                 }
366         }
367         }
368         
369         @Override
370         public void domainModified(Domain domainElement) {
371                 Link<Domain,Range> link = domain.get(domainElement);
372                 if(link != null)
373                     domainModified(link);
374         }
375
376         void rangeModified(Link<Domain,Range> link) {
377             if(!link.rangeModified) {
378                 synchronized(modifiedRangeLinks) {
379                 link.rangeModified = true;
380                 modifiedRangeLinks.add(link);
381                 if(modifiedRangeLinks.size() == 1) {
382                     for(IMappingListener listener : listeners)
383                         listener.rangeModified();
384                 }
385                 }
386         }
387         }
388         
389         @Override
390         public void rangeModified(Range rangeElement) {
391                 Link<Domain,Range> link = range.get(rangeElement);
392                 if(link != null)
393                     rangeModified(link);
394         }
395
396         @Override
397         public boolean isDomainModified() {
398                 return !modifiedDomainLinks.isEmpty();
399         }
400
401         @Override
402         public boolean isRangeModified() {
403                 return !modifiedRangeLinks.isEmpty();
404         }
405         
406         @Override
407         public Collection<Domain> getDomainModified() {
408                 List<Domain> list = new ArrayList<Domain>(modifiedDomainLinks.size());
409                 for (Link<Domain, Range> link : modifiedDomainLinks)
410                         list.add(link.domainElement);
411                 return list;
412                                 
413         }
414         
415         @Override
416         public Collection<Range> getRangeModified() {
417                 List<Range> list = new ArrayList<Range>(modifiedRangeLinks.size());
418                 for (Link<Domain, Range> link : modifiedRangeLinks)
419                         list.add(link.rangeElement);
420                 return list;
421         }
422
423         @Override
424         public void addMappingListener(IMappingListener listener) {
425                 listeners.add(listener);
426         }
427
428         @Override
429         public void removeMappingListener(IMappingListener listener) {
430                 listeners.remove(listener);             
431         }
432
433         @Override
434         public Collection<Domain> getConflictingDomainElements() {
435                 ArrayList<Domain> result = new ArrayList<Domain>();
436                 if(modifiedDomainLinks.size() < modifiedRangeLinks.size()) {
437                         for(Link<Domain,Range> link : modifiedDomainLinks)
438                                 if(link.rangeModified)
439                                         result.add(link.domainElement);
440                 }
441                 else {
442                         for(Link<Domain,Range> link : modifiedRangeLinks)
443                                 if(link.domainModified)
444                                         result.add(link.domainElement);
445                 }
446                 return result;
447         }
448
449         @Override
450         public Collection<Range> getConflictingRangeElements() {
451                 ArrayList<Range> result = new ArrayList<Range>();
452                 if(modifiedDomainLinks.size() < modifiedRangeLinks.size()) {
453                         for(Link<Domain,Range> link : modifiedDomainLinks)
454                                 if(link.rangeModified)
455                                         result.add(link.rangeElement);
456                 }
457                 else {
458                         for(Link<Domain,Range> link : modifiedRangeLinks)
459                                 if(link.domainModified)
460                                         result.add(link.rangeElement);
461                 }
462                 return result;
463         }
464
465     @Override
466     public void dispose() {
467         disposed = true;
468     }
469     
470     public boolean isDisposed() {
471         return disposed;
472     }
473         
474 }