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