1 /*******************************************************************************
\r
2 * Copyright (c) 2007, 2013 Association for Decentralized Information Management
\r
3 * in Industry THTH ry.
\r
4 * All rights reserved. This program and the accompanying materials
\r
5 * are made available under the terms of the Eclipse Public License v1.0
\r
6 * which accompanies this distribution, and is available at
\r
7 * http://www.eclipse.org/legal/epl-v10.html
\r
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.objmap.graph.impl;
\r
15 import gnu.trove.map.hash.THashMap;
\r
17 import java.util.AbstractSet;
\r
18 import java.util.ArrayList;
\r
19 import java.util.Collection;
\r
20 import java.util.Iterator;
\r
21 import java.util.List;
\r
22 import java.util.Set;
\r
24 import org.apache.log4j.Logger;
\r
25 import org.simantics.db.ReadGraph;
\r
26 import org.simantics.db.WriteGraph;
\r
27 import org.simantics.db.exception.DatabaseException;
\r
29 import org.simantics.objmap.backward.IBackwardMapping;
\r
30 import org.simantics.objmap.exceptions.MappingException;
\r
31 import org.simantics.objmap.forward.IForwardMapping;
\r
32 import org.simantics.objmap.graph.IMapping;
\r
33 import org.simantics.objmap.graph.IMappingListener;
\r
34 import org.simantics.objmap.graph.schema.ILinkType;
\r
35 import org.simantics.objmap.graph.schema.IMappingSchema;
\r
38 * An implementation of IMapping. The class should not be created
\r
39 * directly but using methods in Mappings.
\r
40 * @see org.simantics.objmap.graph.Mappings
\r
41 * @author Hannu Niemistö
\r
43 public class Mapping<Domain, Range> implements IMapping<Domain, Range> {
\r
45 static Logger LOGGER = Logger.getLogger("org.simantics.objmap");
\r
48 IMappingSchema<Domain, Range> schema;
\r
50 THashMap<Domain, Link<Domain,Range>> domain = new THashMap<Domain, Link<Domain,Range>>();
\r
51 THashMap<Range, Link<Domain,Range>> range = new THashMap<Range, Link<Domain,Range>>();
\r
52 ArrayList<IMappingListener> listeners = new ArrayList<IMappingListener>();
\r
54 ArrayList<Link<Domain,Range>> modifiedDomainLinks = new ArrayList<Link<Domain,Range>>();
\r
55 ArrayList<Link<Domain,Range>> modifiedRangeLinks = new ArrayList<Link<Domain,Range>>();
\r
57 boolean disposed = false;
\r
59 boolean listensDomain;
\r
61 public Mapping(IMappingSchema<Domain, Range> schema, boolean listensDomain) {
\r
62 this.schema = schema;
\r
63 this.listensDomain = listensDomain;
\r
66 private void removeLink(Link<Domain,Range> link) {
\r
67 if(link.domainModified)
\r
68 modifiedDomainLinks.remove(link);
\r
69 if(link.rangeModified)
\r
70 modifiedRangeLinks.remove(link);
\r
71 link.removed = true;
\r
74 private void createDomain(WriteGraph g, Link<Domain,Range> link) throws MappingException {
\r
75 LOGGER.info(" createDomain for " + link.rangeElement);
\r
76 ILinkType<Domain,Range> type = schema.linkTypeOfRangeElement(link.rangeElement);
\r
77 Domain domainElement = type.createDomainElement(g, link.rangeElement);
\r
79 link.domainElement = domainElement;
\r
80 domain.put(domainElement, link);
\r
81 type.createDomain(g, new RangeToDomain(g), domainElement, link.rangeElement);
\r
83 // TODO Should we do this only if the mapping is listening?
\r
84 domainModified(link);
\r
87 private void createRange(ReadGraph g, Link<Domain,Range> link) throws MappingException {
\r
88 ILinkType<Domain,Range> type = schema.linkTypeOfDomainElement(g, link.domainElement);
\r
89 Range rangeElement = type.createRangeElement(g, link.domainElement);
\r
92 link.rangeElement = rangeElement;
\r
93 range.put(rangeElement, link);
\r
94 type.createRange(g, new DomainToRange(g), link.domainElement, rangeElement);
\r
97 Set<Domain> domainSet = new AbstractSet<Domain>() {
\r
99 public boolean add(Domain e) {
\r
100 if(domain.containsKey(e))
\r
102 Link<Domain,Range> link = new Link<Domain,Range>(null, e, null);
\r
103 domain.put(e, link);
\r
104 modifiedDomainLinks.add(link);
\r
108 public boolean contains(Object o) {
\r
109 return domain.contains(o);
\r
112 public boolean remove(Object o) {
\r
113 Link<Domain,Range> link = domain.remove(o);
\r
117 if(link.rangeElement != null)
\r
118 range.remove(link.rangeElement);
\r
123 public Iterator<Domain> iterator() {
\r
124 // FIXME does not implement Iterator.remove correctly
\r
125 return domain.keySet().iterator();
\r
129 public int size() {
\r
130 return domain.size();
\r
135 Set<Range> rangeSet = new AbstractSet<Range>() {
\r
137 public boolean add(Range e) {
\r
138 if(range.containsKey(e))
\r
140 Link<Domain,Range> link = new Link<Domain,Range>(null, null, e);
\r
141 range.put(e, link);
\r
142 modifiedRangeLinks.add(link);
\r
146 public boolean contains(Object o) {
\r
147 return range.contains(o);
\r
150 public boolean remove(Object o) {
\r
151 Link<Domain,Range> link = range.remove(o);
\r
155 if(link.domainElement != null)
\r
156 domain.remove(link.domainElement);
\r
161 public Iterator<Range> iterator() {
\r
162 // FIXME does not implement Iterator.remove correctly
\r
163 return range.keySet().iterator();
\r
167 public int size() {
\r
168 return range.size();
\r
173 class DomainToRange implements IForwardMapping<Domain, Range> {
\r
177 public DomainToRange(ReadGraph g) {
\r
182 public Range get(Domain element) {
\r
183 Link<Domain,Range> link = domain.get(element);
\r
185 return link.rangeElement;
\r
191 public Range map(ReadGraph graph, Domain element)
\r
192 throws MappingException {
\r
193 Link<Domain,Range> link = domain.get(element);
\r
195 link = new Link<Domain,Range>(null, element, null);
\r
196 link.domainModified = true;
\r
197 modifiedDomainLinks.add(link);
\r
198 domain.put(element, link);
\r
199 createRange(g, link);
\r
201 else if(link.type == null)
\r
202 createRange(g, link);
\r
203 return link.rangeElement;
\r
207 public Set<Domain> getDomain() {
\r
208 return domain.keySet();
\r
213 class RangeToDomain extends DomainToRange implements IBackwardMapping<Domain, Range> {
\r
217 public RangeToDomain(WriteGraph g) {
\r
222 public Domain inverseGet(Range element) {
\r
224 Link<Domain,Range> link = range.get(element);
\r
226 return link.domainElement;
\r
231 public Domain inverseMap(WriteGraph graph, Range element)
\r
232 throws MappingException {
\r
233 Link<Domain,Range> link = range.get(element);
\r
235 link = new Link<Domain,Range>(null, null, element);
\r
236 link.rangeModified = true;
\r
237 modifiedRangeLinks.add(link);
\r
238 range.put(element, link);
\r
239 createDomain(g, link);
\r
241 else if(link.type == null)
\r
242 createDomain(g, link);
\r
243 return link.domainElement;
\r
248 public Set<Range> getRange() {
\r
249 return range.keySet();
\r
254 public Set<Domain> getDomain() {
\r
259 public Set<Range> getRange() {
\r
265 public synchronized Collection<Domain> updateDomain(WriteGraph g) throws MappingException {
\r
266 LOGGER.info("Mapping.updateDomain");
\r
267 RangeToDomain map = new RangeToDomain(g);
\r
268 ArrayList<Domain> updated = new ArrayList<Domain>();
\r
269 while(!modifiedRangeLinks.isEmpty()) {
\r
270 LOGGER.info(" modifiedRangeLinks.size() = " + modifiedRangeLinks.size());
\r
272 Link<Domain,Range> link = modifiedRangeLinks.remove(modifiedRangeLinks.size()-1);
\r
273 link.rangeModified = false;
\r
274 /*if(link.domainModified) {
\r
275 link.domainModified = false;
\r
276 modifiedDomainLinks.remove(link);
\r
279 if(link.type == null) {
\r
280 createDomain(g, link);
\r
283 if(link.type.updateDomain(g, map, link.domainElement, link.rangeElement))
\r
284 updated.add(link.domainElement);
\r
287 updateRange(g); //FIXME: without this listening would stop.
\r
292 public synchronized Collection<Range> updateRange(ReadGraph g) throws MappingException {
\r
293 LOGGER.info("Mapping.updateRange");
\r
294 DomainToRange map = new DomainToRange(g);
\r
295 ArrayList<Range> updated = new ArrayList<Range>();
\r
296 while(!modifiedDomainLinks.isEmpty()) {
\r
297 LOGGER.info(" modifiedDomainLinks.size() = " + modifiedDomainLinks.size());
\r
299 Link<Domain,Range> link = modifiedDomainLinks.remove(modifiedDomainLinks.size()-1);
\r
300 link.domainModified = false;
\r
301 /*if(link.rangeModified) {
\r
302 link.rangeModified = false;
\r
303 modifiedRangeLinks.remove(link);
\r
306 if(link.type == null) {
\r
307 createRange(g, link);
\r
310 if(listensDomain) {
\r
311 RangeUpdateRequest<Domain,Range> request = new RangeUpdateRequest<Domain,Range>(link, map, this);
\r
313 g.syncRequest(request, request);
\r
314 } catch (DatabaseException e) {
\r
315 throw new MappingException(e);
\r
317 // TODO check if really modified
\r
318 updated.add(link.rangeElement);
\r
321 if(link.type.updateRange(g, map, link.domainElement, link.rangeElement))
\r
322 updated.add(link.rangeElement);
\r
328 public Range get(Domain domainElement) {
\r
329 Link<Domain,Range> link = domain.get(domainElement);
\r
332 return link.rangeElement;
\r
336 public Domain inverseGet(Range rangeElement) {
\r
337 Link<Domain,Range> link = range.get(rangeElement);
\r
340 return link.domainElement;
\r
344 public Domain inverseMap(WriteGraph g, Range rangeElement) throws MappingException {
\r
345 getRange().add(rangeElement);
\r
347 return inverseGet(rangeElement);
\r
351 public Range map(ReadGraph g, Domain domainElement) throws MappingException {
\r
352 getDomain().add(domainElement);
\r
354 return get(domainElement);
\r
357 void domainModified(Link<Domain,Range> link) {
\r
358 if(!link.domainModified) {
\r
359 synchronized(modifiedDomainLinks) {
\r
360 LOGGER.info(" domainModified for " + link.rangeElement);
\r
361 link.domainModified = true;
\r
362 modifiedDomainLinks.add(link);
\r
363 if(modifiedDomainLinks.size() == 1) {
\r
364 for(IMappingListener listener : listeners)
\r
365 listener.domainModified();
\r
372 public void domainModified(Domain domainElement) {
\r
373 Link<Domain,Range> link = domain.get(domainElement);
\r
375 domainModified(link);
\r
378 void rangeModified(Link<Domain,Range> link) {
\r
379 if(!link.rangeModified) {
\r
380 synchronized(modifiedRangeLinks) {
\r
381 link.rangeModified = true;
\r
382 modifiedRangeLinks.add(link);
\r
383 if(modifiedRangeLinks.size() == 1) {
\r
384 for(IMappingListener listener : listeners)
\r
385 listener.rangeModified();
\r
392 public void rangeModified(Range rangeElement) {
\r
393 Link<Domain,Range> link = range.get(rangeElement);
\r
395 rangeModified(link);
\r
399 public boolean isDomainModified() {
\r
400 return !modifiedDomainLinks.isEmpty();
\r
404 public boolean isRangeModified() {
\r
405 return !modifiedRangeLinks.isEmpty();
\r
409 public Collection<Domain> getDomainModified() {
\r
410 List<Domain> list = new ArrayList<Domain>(modifiedDomainLinks.size());
\r
411 for (Link<Domain, Range> link : modifiedDomainLinks)
\r
412 list.add(link.domainElement);
\r
418 public Collection<Range> getRangeModified() {
\r
419 List<Range> list = new ArrayList<Range>(modifiedRangeLinks.size());
\r
420 for (Link<Domain, Range> link : modifiedRangeLinks)
\r
421 list.add(link.rangeElement);
\r
426 public void addMappingListener(IMappingListener listener) {
\r
427 listeners.add(listener);
\r
431 public void removeMappingListener(IMappingListener listener) {
\r
432 listeners.remove(listener);
\r
436 public Collection<Domain> getConflictingDomainElements() {
\r
437 ArrayList<Domain> result = new ArrayList<Domain>();
\r
438 if(modifiedDomainLinks.size() < modifiedRangeLinks.size()) {
\r
439 for(Link<Domain,Range> link : modifiedDomainLinks)
\r
440 if(link.rangeModified)
\r
441 result.add(link.domainElement);
\r
444 for(Link<Domain,Range> link : modifiedRangeLinks)
\r
445 if(link.domainModified)
\r
446 result.add(link.domainElement);
\r
452 public Collection<Range> getConflictingRangeElements() {
\r
453 ArrayList<Range> result = new ArrayList<Range>();
\r
454 if(modifiedDomainLinks.size() < modifiedRangeLinks.size()) {
\r
455 for(Link<Domain,Range> link : modifiedDomainLinks)
\r
456 if(link.rangeModified)
\r
457 result.add(link.rangeElement);
\r
460 for(Link<Domain,Range> link : modifiedRangeLinks)
\r
461 if(link.domainModified)
\r
462 result.add(link.rangeElement);
\r
468 public void dispose() {
\r
472 public boolean isDisposed() {
\r