+/*******************************************************************************\r
+ * Copyright (c) 2007, 2013 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ * VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.objmap.graph.impl;\r
+\r
+\r
+import gnu.trove.map.hash.THashMap;\r
+\r
+import java.util.AbstractSet;\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
+import java.util.Iterator;\r
+import java.util.List;\r
+import java.util.Set;\r
+\r
+import org.apache.log4j.Logger;\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.WriteGraph;\r
+import org.simantics.db.exception.DatabaseException;\r
+\r
+import org.simantics.objmap.backward.IBackwardMapping;\r
+import org.simantics.objmap.exceptions.MappingException;\r
+import org.simantics.objmap.forward.IForwardMapping;\r
+import org.simantics.objmap.graph.IMapping;\r
+import org.simantics.objmap.graph.IMappingListener;\r
+import org.simantics.objmap.graph.schema.ILinkType;\r
+import org.simantics.objmap.graph.schema.IMappingSchema;\r
+\r
+/**\r
+ * An implementation of IMapping. The class should not be created\r
+ * directly but using methods in Mappings.\r
+ * @see org.simantics.objmap.graph.Mappings\r
+ * @author Hannu Niemistö\r
+ */\r
+public class Mapping<Domain, Range> implements IMapping<Domain, Range> {\r
+\r
+ static Logger LOGGER = Logger.getLogger("org.simantics.objmap");\r
+ \r
+ \r
+ IMappingSchema<Domain, Range> schema;\r
+ \r
+ THashMap<Domain, Link<Domain,Range>> domain = new THashMap<Domain, Link<Domain,Range>>();\r
+ THashMap<Range, Link<Domain,Range>> range = new THashMap<Range, Link<Domain,Range>>();\r
+ ArrayList<IMappingListener> listeners = new ArrayList<IMappingListener>();\r
+\r
+ ArrayList<Link<Domain,Range>> modifiedDomainLinks = new ArrayList<Link<Domain,Range>>();\r
+ ArrayList<Link<Domain,Range>> modifiedRangeLinks = new ArrayList<Link<Domain,Range>>();\r
+\r
+ boolean disposed = false;\r
+ \r
+ boolean listensDomain; \r
+ \r
+ public Mapping(IMappingSchema<Domain, Range> schema, boolean listensDomain) {\r
+ this.schema = schema;\r
+ this.listensDomain = listensDomain;\r
+ }\r
+ \r
+ private void removeLink(Link<Domain,Range> link) {\r
+ if(link.domainModified)\r
+ modifiedDomainLinks.remove(link);\r
+ if(link.rangeModified)\r
+ modifiedRangeLinks.remove(link);\r
+ link.removed = true;\r
+ }\r
+ \r
+ private void createDomain(WriteGraph g, Link<Domain,Range> link) throws MappingException {\r
+ LOGGER.info(" createDomain for " + link.rangeElement);\r
+ ILinkType<Domain,Range> type = schema.linkTypeOfRangeElement(link.rangeElement);\r
+ Domain domainElement = type.createDomainElement(g, link.rangeElement);\r
+ link.type = type;\r
+ link.domainElement = domainElement;\r
+ domain.put(domainElement, link);\r
+ type.createDomain(g, new RangeToDomain(g), domainElement, link.rangeElement);\r
+ \r
+ // TODO Should we do this only if the mapping is listening?\r
+ domainModified(link);\r
+ }\r
+ \r
+ private void createRange(ReadGraph g, Link<Domain,Range> link) throws MappingException {\r
+ ILinkType<Domain,Range> type = schema.linkTypeOfDomainElement(g, link.domainElement); \r
+ Range rangeElement = type.createRangeElement(g, link.domainElement);\r
+ \r
+ link.type = type;\r
+ link.rangeElement = rangeElement;\r
+ range.put(rangeElement, link);\r
+ type.createRange(g, new DomainToRange(g), link.domainElement, rangeElement);\r
+ }\r
+ \r
+ Set<Domain> domainSet = new AbstractSet<Domain>() {\r
+\r
+ public boolean add(Domain e) {\r
+ if(domain.containsKey(e))\r
+ return false;\r
+ Link<Domain,Range> link = new Link<Domain,Range>(null, e, null);\r
+ domain.put(e, link);\r
+ modifiedDomainLinks.add(link);\r
+ return true;\r
+ }\r
+ \r
+ public boolean contains(Object o) {\r
+ return domain.contains(o);\r
+ }\r
+ \r
+ public boolean remove(Object o) {\r
+ Link<Domain,Range> link = domain.remove(o); \r
+ if(link == null)\r
+ return false;\r
+ removeLink(link);\r
+ if(link.rangeElement != null)\r
+ range.remove(link.rangeElement);\r
+ return true; \r
+ }\r
+ \r
+ @Override\r
+ public Iterator<Domain> iterator() {\r
+ // FIXME does not implement Iterator.remove correctly\r
+ return domain.keySet().iterator();\r
+ }\r
+\r
+ @Override\r
+ public int size() {\r
+ return domain.size();\r
+ }\r
+ \r
+ };\r
+ \r
+ Set<Range> rangeSet = new AbstractSet<Range>() {\r
+\r
+ public boolean add(Range e) {\r
+ if(range.containsKey(e))\r
+ return false;\r
+ Link<Domain,Range> link = new Link<Domain,Range>(null, null, e);\r
+ range.put(e, link);\r
+ modifiedRangeLinks.add(link);\r
+ return true;\r
+ }\r
+ \r
+ public boolean contains(Object o) {\r
+ return range.contains(o);\r
+ }\r
+ \r
+ public boolean remove(Object o) {\r
+ Link<Domain,Range> link = range.remove(o); \r
+ if(link == null)\r
+ return false;\r
+ removeLink(link);\r
+ if(link.domainElement != null)\r
+ domain.remove(link.domainElement);\r
+ return true;\r
+ }\r
+ \r
+ @Override\r
+ public Iterator<Range> iterator() {\r
+ // FIXME does not implement Iterator.remove correctly\r
+ return range.keySet().iterator();\r
+ }\r
+\r
+ @Override\r
+ public int size() {\r
+ return range.size();\r
+ }\r
+ \r
+ };\r
+ \r
+ class DomainToRange implements IForwardMapping<Domain, Range> {\r
+\r
+ ReadGraph g;\r
+ \r
+ public DomainToRange(ReadGraph g) {\r
+ this.g = g;\r
+ }\r
+\r
+ @Override\r
+ public Range get(Domain element) {\r
+ Link<Domain,Range> link = domain.get(element);\r
+ if (link != null)\r
+ return link.rangeElement;\r
+ return null;\r
+ \r
+ }\r
+ \r
+ @Override\r
+ public Range map(ReadGraph graph, Domain element)\r
+ throws MappingException {\r
+ Link<Domain,Range> link = domain.get(element);\r
+ if(link == null) {\r
+ link = new Link<Domain,Range>(null, element, null);\r
+ link.domainModified = true;\r
+ modifiedDomainLinks.add(link);\r
+ domain.put(element, link); \r
+ createRange(g, link); \r
+ }\r
+ else if(link.type == null) \r
+ createRange(g, link);\r
+ return link.rangeElement;\r
+ }\r
+ \r
+ @Override\r
+ public Set<Domain> getDomain() {\r
+ return domain.keySet();\r
+ }\r
+ \r
+ };\r
+ \r
+ class RangeToDomain extends DomainToRange implements IBackwardMapping<Domain, Range> {\r
+\r
+ WriteGraph g;\r
+ \r
+ public RangeToDomain(WriteGraph g) {\r
+ super(g);\r
+ this.g = g;\r
+ }\r
+ @Override\r
+ public Domain inverseGet(Range element) {\r
+ \r
+ Link<Domain,Range> link = range.get(element);\r
+ if(link != null)\r
+ return link.domainElement;\r
+ return null;\r
+ }\r
+ \r
+ @Override\r
+ public Domain inverseMap(WriteGraph graph, Range element)\r
+ throws MappingException {\r
+ Link<Domain,Range> link = range.get(element);\r
+ if(link == null) {\r
+ link = new Link<Domain,Range>(null, null, element);\r
+ link.rangeModified = true;\r
+ modifiedRangeLinks.add(link);\r
+ range.put(element, link);\r
+ createDomain(g, link); \r
+ }\r
+ else if(link.type == null)\r
+ createDomain(g, link);\r
+ return link.domainElement;\r
+ }\r
+ \r
+ \r
+ @Override\r
+ public Set<Range> getRange() {\r
+ return range.keySet();\r
+ }\r
+ };\r
+ \r
+ @Override\r
+ public Set<Domain> getDomain() {\r
+ return domainSet;\r
+ }\r
+ \r
+ @Override\r
+ public Set<Range> getRange() {\r
+ return rangeSet;\r
+ }\r
+ \r
+ \r
+ @Override\r
+ public synchronized Collection<Domain> updateDomain(WriteGraph g) throws MappingException {\r
+ LOGGER.info("Mapping.updateDomain");\r
+ RangeToDomain map = new RangeToDomain(g);\r
+ ArrayList<Domain> updated = new ArrayList<Domain>();\r
+ while(!modifiedRangeLinks.isEmpty()) {\r
+ LOGGER.info(" modifiedRangeLinks.size() = " + modifiedRangeLinks.size());\r
+ \r
+ Link<Domain,Range> link = modifiedRangeLinks.remove(modifiedRangeLinks.size()-1);\r
+ link.rangeModified = false;\r
+ /*if(link.domainModified) {\r
+ link.domainModified = false;\r
+ modifiedDomainLinks.remove(link);\r
+ }*/\r
+ \r
+ if(link.type == null) {\r
+ createDomain(g, link);\r
+ }\r
+ \r
+ if(link.type.updateDomain(g, map, link.domainElement, link.rangeElement))\r
+ updated.add(link.domainElement);\r
+ } \r
+ if (listensDomain)\r
+ updateRange(g); //FIXME: without this listening would stop. \r
+ return updated;\r
+ }\r
+ \r
+ @Override\r
+ public synchronized Collection<Range> updateRange(ReadGraph g) throws MappingException {\r
+ LOGGER.info("Mapping.updateRange");\r
+ DomainToRange map = new DomainToRange(g);\r
+ ArrayList<Range> updated = new ArrayList<Range>();\r
+ while(!modifiedDomainLinks.isEmpty()) { \r
+ LOGGER.info(" modifiedDomainLinks.size() = " + modifiedDomainLinks.size());\r
+ \r
+ Link<Domain,Range> link = modifiedDomainLinks.remove(modifiedDomainLinks.size()-1);\r
+ link.domainModified = false;\r
+ /*if(link.rangeModified) {\r
+ link.rangeModified = false;\r
+ modifiedRangeLinks.remove(link);\r
+ }*/\r
+ \r
+ if(link.type == null) {\r
+ createRange(g, link);\r
+ }\r
+ \r
+ if(listensDomain) {\r
+ RangeUpdateRequest<Domain,Range> request = new RangeUpdateRequest<Domain,Range>(link, map, this);\r
+ try {\r
+ g.syncRequest(request, request);\r
+ } catch (DatabaseException e) {\r
+ throw new MappingException(e);\r
+ }\r
+ // TODO check if really modified\r
+ updated.add(link.rangeElement);\r
+ }\r
+ else\r
+ if(link.type.updateRange(g, map, link.domainElement, link.rangeElement))\r
+ updated.add(link.rangeElement);\r
+ } \r
+ return updated;\r
+ }\r
+\r
+ @Override\r
+ public Range get(Domain domainElement) {\r
+ Link<Domain,Range> link = domain.get(domainElement);\r
+ if(link == null)\r
+ return null;\r
+ return link.rangeElement;\r
+ }\r
+\r
+ @Override\r
+ public Domain inverseGet(Range rangeElement) {\r
+ Link<Domain,Range> link = range.get(rangeElement);\r
+ if(link == null)\r
+ return null;\r
+ return link.domainElement;\r
+ }\r
+\r
+ @Override\r
+ public Domain inverseMap(WriteGraph g, Range rangeElement) throws MappingException {\r
+ getRange().add(rangeElement);\r
+ updateDomain(g);\r
+ return inverseGet(rangeElement);\r
+ }\r
+\r
+ @Override\r
+ public Range map(ReadGraph g, Domain domainElement) throws MappingException {\r
+ getDomain().add(domainElement);\r
+ updateRange(g);\r
+ return get(domainElement);\r
+ }\r
+\r
+ void domainModified(Link<Domain,Range> link) {\r
+ if(!link.domainModified) { \r
+ synchronized(modifiedDomainLinks) {\r
+ LOGGER.info(" domainModified for " + link.rangeElement);\r
+ link.domainModified = true;\r
+ modifiedDomainLinks.add(link);\r
+ if(modifiedDomainLinks.size() == 1) {\r
+ for(IMappingListener listener : listeners)\r
+ listener.domainModified();\r
+ }\r
+ }\r
+ }\r
+ }\r
+ \r
+ @Override\r
+ public void domainModified(Domain domainElement) {\r
+ Link<Domain,Range> link = domain.get(domainElement);\r
+ if(link != null)\r
+ domainModified(link);\r
+ }\r
+\r
+ void rangeModified(Link<Domain,Range> link) {\r
+ if(!link.rangeModified) {\r
+ synchronized(modifiedRangeLinks) {\r
+ link.rangeModified = true;\r
+ modifiedRangeLinks.add(link);\r
+ if(modifiedRangeLinks.size() == 1) {\r
+ for(IMappingListener listener : listeners)\r
+ listener.rangeModified();\r
+ }\r
+ }\r
+ }\r
+ }\r
+ \r
+ @Override\r
+ public void rangeModified(Range rangeElement) {\r
+ Link<Domain,Range> link = range.get(rangeElement);\r
+ if(link != null)\r
+ rangeModified(link);\r
+ }\r
+\r
+ @Override\r
+ public boolean isDomainModified() {\r
+ return !modifiedDomainLinks.isEmpty();\r
+ }\r
+\r
+ @Override\r
+ public boolean isRangeModified() {\r
+ return !modifiedRangeLinks.isEmpty();\r
+ }\r
+ \r
+ @Override\r
+ public Collection<Domain> getDomainModified() {\r
+ List<Domain> list = new ArrayList<Domain>(modifiedDomainLinks.size());\r
+ for (Link<Domain, Range> link : modifiedDomainLinks)\r
+ list.add(link.domainElement);\r
+ return list;\r
+ \r
+ }\r
+ \r
+ @Override\r
+ public Collection<Range> getRangeModified() {\r
+ List<Range> list = new ArrayList<Range>(modifiedRangeLinks.size());\r
+ for (Link<Domain, Range> link : modifiedRangeLinks)\r
+ list.add(link.rangeElement);\r
+ return list;\r
+ }\r
+\r
+ @Override\r
+ public void addMappingListener(IMappingListener listener) {\r
+ listeners.add(listener);\r
+ }\r
+\r
+ @Override\r
+ public void removeMappingListener(IMappingListener listener) {\r
+ listeners.remove(listener); \r
+ }\r
+\r
+ @Override\r
+ public Collection<Domain> getConflictingDomainElements() {\r
+ ArrayList<Domain> result = new ArrayList<Domain>();\r
+ if(modifiedDomainLinks.size() < modifiedRangeLinks.size()) {\r
+ for(Link<Domain,Range> link : modifiedDomainLinks)\r
+ if(link.rangeModified)\r
+ result.add(link.domainElement);\r
+ }\r
+ else {\r
+ for(Link<Domain,Range> link : modifiedRangeLinks)\r
+ if(link.domainModified)\r
+ result.add(link.domainElement);\r
+ }\r
+ return result;\r
+ }\r
+\r
+ @Override\r
+ public Collection<Range> getConflictingRangeElements() {\r
+ ArrayList<Range> result = new ArrayList<Range>();\r
+ if(modifiedDomainLinks.size() < modifiedRangeLinks.size()) {\r
+ for(Link<Domain,Range> link : modifiedDomainLinks)\r
+ if(link.rangeModified)\r
+ result.add(link.rangeElement);\r
+ }\r
+ else {\r
+ for(Link<Domain,Range> link : modifiedRangeLinks)\r
+ if(link.domainModified)\r
+ result.add(link.rangeElement);\r
+ }\r
+ return result;\r
+ }\r
+\r
+ @Override\r
+ public void dispose() {\r
+ disposed = true;\r
+ }\r
+ \r
+ public boolean isDisposed() {\r
+ return disposed;\r
+ }\r
+ \r
+}\r