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