X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;f=bundles%2Forg.simantics.modeling%2Fsrc%2Forg%2Fsimantics%2Fmodeling%2Fservices%2FCaseInsensitiveComponentNamingStrategy2.java;h=a8a8a7319899fb3ef02cb576fa291ef22ed28eea;hb=0935b78fb5a162719de5dab404b7161de2e7e717;hp=b2d4e2e69af8808f8035da62870e93801fe12d56;hpb=969bd23cab98a79ca9101af33334000879fb60c5;p=simantics%2Fplatform.git diff --git a/bundles/org.simantics.modeling/src/org/simantics/modeling/services/CaseInsensitiveComponentNamingStrategy2.java b/bundles/org.simantics.modeling/src/org/simantics/modeling/services/CaseInsensitiveComponentNamingStrategy2.java index b2d4e2e69..a8a8a7319 100644 --- a/bundles/org.simantics.modeling/src/org/simantics/modeling/services/CaseInsensitiveComponentNamingStrategy2.java +++ b/bundles/org.simantics.modeling/src/org/simantics/modeling/services/CaseInsensitiveComponentNamingStrategy2.java @@ -1,534 +1,537 @@ -/******************************************************************************* - * Copyright (c) 2007, 2010 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.modeling.services; - -import gnu.trove.map.hash.THashMap; - -import java.lang.ref.SoftReference; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Formatter; -import java.util.HashSet; -import java.util.IdentityHashMap; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.ConcurrentSkipListMap; -import java.util.concurrent.ConcurrentSkipListSet; - -import org.simantics.Simantics; -import org.simantics.databoard.Bindings; -import org.simantics.db.AsyncReadGraph; -import org.simantics.db.ReadGraph; -import org.simantics.db.Resource; -import org.simantics.db.Session; -import org.simantics.db.common.request.AsyncReadRequest; -import org.simantics.db.common.utils.NameUtils; -import org.simantics.db.event.ChangeEvent; -import org.simantics.db.event.ChangeListener; -import org.simantics.db.exception.DatabaseException; -import org.simantics.db.procedure.AsyncMultiProcedure; -import org.simantics.db.procedure.AsyncProcedure; -import org.simantics.db.service.GraphChangeListenerSupport; -import org.simantics.layer0.Layer0; -import org.simantics.modeling.ComponentUtils; -import org.simantics.project.IProject; -import org.simantics.structural.stubs.StructuralResource2; -import org.simantics.utils.datastructures.Pair; -import org.simantics.utils.ui.ErrorLogger; - -/** - * A first-hand component naming strategy implementation for structural models. - * - * This version is somewhat optimized for case-insensitivity by using custom - * comparators in maps and sets. It uses a soft-referenced cache of - * used/requested names per a single root of a configuration's component - * hierarchy. - * - * @author Tuukka Lehtonen - * - * @see ComponentNamingStrategy - */ -public class CaseInsensitiveComponentNamingStrategy2 extends ComponentNamingStrategyBase implements ChangeListener { - - private static final boolean DEBUG_ALL = false; - private static final boolean DEBUG_GRAPH_UPDATES = false | DEBUG_ALL; - private static final boolean DEBUG_CACHE_INITIALIZATION = false | DEBUG_ALL; - private static final boolean DEBUG_CACHE_INITIALIZATION_BROWSE = false | DEBUG_ALL; - private static final boolean DEBUG_CACHE_UPDATES = false | DEBUG_ALL; - - static class Cache { - private final Resource root; - private final Set reserved; - - // Having cache as soft references should somewhat address the problem - // of the amount of requested names growing too large. - private final Set requested; - - // Only internally used data - private final ConcurrentMap r2s; - private final ConcurrentMap> s2r; - - public Cache(Resource root, Set reserved, ConcurrentMap r2s, ConcurrentMap> s2r, boolean caseInsensitive) { - assert root != null; - assert reserved != null; - assert r2s != null; - assert s2r != null; - this.root = root; - this.reserved = reserved; - this.r2s = r2s; - this.s2r = s2r; - this.requested = new ConcurrentSkipListSet(getComparator(caseInsensitive)); - } - - @Override - protected void finalize() throws Throwable { - if (DEBUG_CACHE_UPDATES) - debug("FINALIZE"); - super.finalize(); - } - - public Resource getRoot() { - return root; - } - - public Set getReserved() { - // This prevents the map from being retrieved during a cache update. - synchronized (this) { - return reserved; - } - } - - public Set getRequested() { - // This prevents the map from being retrieved during a cache update. - synchronized (this) { - return requested; - } - } - - public void addRequested(String name) { - requested.add(name); - } - - public void replaceEntries(Collection> entries) { - if (entries.isEmpty()) - return; - - if (DEBUG_CACHE_UPDATES) - debug(" updating " + entries.size() +" cache entries"); - - synchronized (this) { - for (Pair entry : entries) { - Resource component = entry.first; - String newName = entry.second; - - assert component != null; - assert newName != null; - - String oldName = r2s.get(component); - if (oldName == null) { - // This must be an uncached new component. - - // Validate cache. - Set existingEntries = getMapSet(s2r, newName); - if (!existingEntries.isEmpty()) { - System.out.println("WARNING: Somebody is screwing the model up with duplicate name: " + newName); - // TODO: generate issue or message - } - - Object prev = r2s.putIfAbsent(component, newName); - assert prev == null; - addToMapSet(s2r, newName, component); - - reserved.add(newName); - requested.remove(newName); - - if (DEBUG_CACHE_UPDATES) - debug("\tnew component name: " + newName); - } else { - // This must be a change to an existing cached component. - - // Validate cache - Set existingEntries = getMapSet(s2r, newName); - if (!existingEntries.isEmpty()) { - // Currently changesets can contain multiple entries for a same change. - // This picks out one of such cases where the value of a resource has been - // set multiple times to the same value. - if (existingEntries.contains(component)) - continue; - - System.out.println("WARNING: Somebody is screwing the model up with duplicate name: " + newName); - // TODO: generate issue or message - } - - Set resourcesWithOldName = removeFromMapSet(s2r, oldName, component); - addToMapSet(s2r, newName, component); - boolean updated = r2s.replace(component, oldName, newName); - assert updated; - if (resourcesWithOldName.isEmpty()) { - reserved.remove(oldName); - } - reserved.add(newName); - requested.remove(newName); - - if (DEBUG_CACHE_UPDATES) - debug("\tcomponent name changed: " + oldName + " -> " + newName); - } - } - - if (DEBUG_CACHE_UPDATES) { - debug("reserved names after update: " + reserved); - debug("requested names after update: " + requested); - } - } - } - - private void debug(String string) { - CaseInsensitiveComponentNamingStrategy2.debug(this, string); - } - } - - static class CacheFactory { - final ReadGraph graph; - final Resource root; - final Layer0 b; - final StructuralResource2 sr; - final boolean caseInsensitive; - - final Set reserved; - final ConcurrentMap r2s = new ConcurrentSkipListMap(); - final ConcurrentMap> s2r; - - CacheFactory(ReadGraph graph, Resource root, boolean caseInsensitive) { - this.graph = graph; - this.root = root; - this.b = Layer0.getInstance(graph); - this.sr = StructuralResource2.getInstance(graph); - this.caseInsensitive = caseInsensitive; - - this.reserved = new ConcurrentSkipListSet(getComparator(caseInsensitive)); - this.s2r = new ConcurrentSkipListMap>(getComparator(caseInsensitive)); - } - - private void debug(String string) { - CaseInsensitiveComponentNamingStrategy2.debug(this, string); - } - - public Cache create() throws DatabaseException { - if (DEBUG_CACHE_INITIALIZATION_BROWSE) - debug("browsing all components from root " + root); - - graph.syncRequest(new AsyncReadRequest() { - @Override - public void run(AsyncReadGraph graph) { - browseComposite(graph, root); - } - }); - - if (DEBUG_CACHE_INITIALIZATION_BROWSE) - debug("browsing completed, results:\n\treserved: " + reserved + "\n\tr2s: " + r2s + "\n\ts2r: " + s2r); - - return new Cache(root, reserved, r2s, s2r, caseInsensitive); - } - - static abstract class MultiProc implements AsyncMultiProcedure { - @Override - public void finished(AsyncReadGraph graph) { - } - @Override - public void exception(AsyncReadGraph graph, Throwable t) { - ErrorLogger.defaultLogError(t); - } - } - - static abstract class AsyncProc implements AsyncProcedure { - @Override - public void exception(AsyncReadGraph graph, Throwable t) { - ErrorLogger.defaultLogError(t); - } - } - - private void browseComposite(AsyncReadGraph graph, Resource composite) { - if (DEBUG_CACHE_INITIALIZATION_BROWSE) - debug("browsing composite " + composite); - graph.forEachObject(composite, b.ConsistsOf, new MultiProc() { - @Override - public void execute(AsyncReadGraph graph, Resource component) { - browseComponent(graph, component); - } - - private void browseComponent(AsyncReadGraph graph, final Resource component) { - if (DEBUG_CACHE_INITIALIZATION_BROWSE) - debug("browsing component " + component); - reserveName(graph, component); - graph.forIsInstanceOf(component, sr.Composite, new AsyncProc() { - @Override - public void execute(AsyncReadGraph graph, Boolean result) { - if (result) - browseComposite(graph, component); - } - }); - } - - private void reserveName(AsyncReadGraph graph, final Resource component) { - graph.forPossibleRelatedValue(component, b.HasName, new AsyncProc() { - @Override - public void execute(AsyncReadGraph graph, String componentName) { - if (componentName != null) { - if (DEBUG_CACHE_INITIALIZATION_BROWSE) - debug("reserving name of component " + component + " '" + componentName + "'"); - Set components = addToMapSet(s2r, componentName, component); - if (components.size() > 1) { - // Found duplicate names in the model !! - // TODO: generate issue! - System.err.println("WARNING: found multiple components with same name '" + componentName + "': " + components); - System.err.println("TODO: generate issue"); - } else { - String prevName = r2s.putIfAbsent(component, componentName); - if (prevName == null) - reserved.add(componentName); - } - } - } - }); - } - }); - } - } - - private SoftReference>> mapRef = - new SoftReference>>(new THashMap>()); - - private final GraphChangeListenerSupport changeSupport; - private Resource inverseOfHasName; - - public CaseInsensitiveComponentNamingStrategy2() { - this(Simantics.getSession().getService(GraphChangeListenerSupport.class), "%s_%d"); - } - - public CaseInsensitiveComponentNamingStrategy2(GraphChangeListenerSupport changeSupport) { - this(changeSupport, "%s %d"); - } - - /** - * @param changeSupport - * @param generatedNameFormat the format to use for generated names, see - * {@link Formatter} - */ - public CaseInsensitiveComponentNamingStrategy2(GraphChangeListenerSupport changeSupport, String generatedNameFormat) { - super(generatedNameFormat); - this.changeSupport = changeSupport; - changeSupport.addListener(this); - } - - public void dispose() { - changeSupport.removeListener(this); - } - - static class CacheUpdateBundle { - IdentityHashMap>> updates = new IdentityHashMap>>(); - - public boolean isEmpty() { - return updates.isEmpty(); - } - - public void add(Cache cache, Resource component, String newName) { - assert cache != null; - assert component != null; - assert newName != null; - - Collection> collection = updates.get(cache); - if (collection == null) { - collection = new ArrayList>(); - updates.put(cache, collection); - } - - collection.add(Pair.make(component, newName)); - } - - public void commitAll() { - for (Map.Entry>> entry : updates.entrySet()) { - Cache cache = entry.getKey(); - cache.replaceEntries(entry.getValue()); - } - } - - @Override - public String toString() { - return getClass().getSimpleName() + " [" + updates.size() + " changed caches]"; - } - } - - @Override - public void graphChanged(ChangeEvent e) throws DatabaseException { - Collection changedValues = e.getChanges().changedValues(); - if (DEBUG_GRAPH_UPDATES) - debug("graph updated with " + changedValues.size() + " value changes"); - - ReadGraph graph = e.getGraph(); - Layer0 b = Layer0.getInstance(graph); - - // Cache inverse of Has Name relation. - if (inverseOfHasName == null) { - inverseOfHasName = graph.getInverse(b.HasName); - } - - CacheUpdateBundle bundle = new CacheUpdateBundle(); - - for (Resource value : changedValues) { - //System.out.println("VALUE CHANGE: " + GraphUtils.getReadableName(graph, value)); - for (Resource nameOfComponent : graph.getObjects(value, inverseOfHasName)) { - if (DEBUG_GRAPH_UPDATES) - debug("\tNAME CHANGE: " + NameUtils.getSafeName(graph, value)); - Resource root = ComponentUtils.tryGetComponentConfigurationRoot(graph, nameOfComponent); - Cache cache = peekCache(graph, root); - if (cache != null) { - String newName = graph.getPossibleValue(value, Bindings.STRING); - if (newName != null) { - if (DEBUG_GRAPH_UPDATES) - debug("\t\tqueued cache update"); - bundle.add(cache, nameOfComponent, newName); - } - } - } - } - - if (!bundle.isEmpty()) { - if (DEBUG_GRAPH_UPDATES) - debug("committing " + bundle); - bundle.commitAll(); - } - } - - private Cache getCache(ReadGraph graph, Resource configurationRoot) throws DatabaseException { - Cache cache = null; - THashMap> map = mapRef.get(); - - if (map != null) { - SoftReference cacheRef = map.get(configurationRoot); - if (cacheRef != null) { - cache = cacheRef.get(); - if (cache != null) - // Cache hit! - return cache; - } - } else { - // Cache miss, rebuild cache index - map = new THashMap>(); - mapRef = new SoftReference>>(map); - } - - // Cache miss, rebuild local cache - if (DEBUG_CACHE_INITIALIZATION) - debug("Constructing new cache for root " + NameUtils.getSafeName(graph, configurationRoot) + " (" + configurationRoot + ")"); - cache = new CacheFactory(graph, configurationRoot, caseInsensitive).create(); - if (DEBUG_CACHE_INITIALIZATION) - debug("\tInitialized with reservations: " + cache.getReserved()); - map.put(configurationRoot, new SoftReference(cache)); - return cache; - } - - private Cache peekCache(ReadGraph graph, Resource configurationRoot) { - THashMap> map = mapRef.get(); - if (map == null) - return null; - SoftReference cacheRef = map.get(configurationRoot); - if (cacheRef == null) - return null; - Cache cache = cacheRef.get(); - if (cache == null) - return null; - // Cache hit! - return cache; - } - - @Override - public String validateInstanceName(ReadGraph graph, - Resource configurationRoot, Resource component, String proposition, boolean acceptProposition) - throws NamingException, DatabaseException { - - Layer0 L0 = Layer0.getInstance(graph); - StructuralResource2 STR = StructuralResource2.getInstance(graph); - Resource container = graph.getSingleObject(component, L0.PartOf); - Resource componentType = graph.getSingleType(component, STR.Component); - return validateInstanceName(graph, configurationRoot, container, componentType, proposition, acceptProposition); - - } - - @Override - public String validateInstanceName(ReadGraph graph, Resource configurationRoot, Resource container, - Resource componentType, String proposition, boolean acceptProposition) throws NamingException, DatabaseException { - Cache cache = getCache(graph, configurationRoot); - synchronized (cache) { - String result = findFreshName(cache.getReserved(), cache.getRequested(), proposition, acceptProposition); - cache.addRequested(result); - return result; - } - } - - private void debug(String string) { - debug(this, string); - } - - private static void debug(Object obj, String string) { - System.out.println("[" + obj.getClass().getSimpleName() + "(" + System.identityHashCode(obj) + ")] " + string); - } - - private static Set addToMapSet(ConcurrentMap> map, K key, V value) { - Set set = map.get(key); - if (set == null) { - set = new HashSet(1); - map.putIfAbsent(key, set); - } - set.add(value); - return set; - } - - private static Set getMapSet(ConcurrentMap> map, K key) { - Set set = map.get(key); - if (set == null) - return Collections.emptySet(); - return set; - } - - private static Set removeFromMapSet(ConcurrentMap> map, K key, V value) { - Set set = map.get(key); - if (set == null) - return Collections.emptySet(); - if (set.remove(value)) { - if (set.isEmpty()) { - map.remove(key); - return Collections.emptySet(); - } - } - return set; - } - - public static CaseInsensitiveComponentNamingStrategy2 install(IProject project) { - - Session session = project.getSession(); - - GraphChangeListenerSupport changeSupport = session.peekService(GraphChangeListenerSupport.class); - if (changeSupport != null) { - CaseInsensitiveComponentNamingStrategy2 namingStrategy = new CaseInsensitiveComponentNamingStrategy2(changeSupport, "%s%02d"); - project.setHint(ComponentNamingStrategy.PROJECT_KEY, namingStrategy); - return namingStrategy; - } else { - System.out.println("WARNING: No GraphChangeListenerSupport in session " + session +", can't initialize all services."); - } - - return null; - - } - -} +/******************************************************************************* + * Copyright (c) 2007, 2010 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.modeling.services; + +import gnu.trove.map.hash.THashMap; + +import java.lang.ref.SoftReference; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Formatter; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ConcurrentSkipListMap; +import java.util.concurrent.ConcurrentSkipListSet; + +import org.simantics.Simantics; +import org.simantics.databoard.Bindings; +import org.simantics.db.AsyncReadGraph; +import org.simantics.db.ReadGraph; +import org.simantics.db.Resource; +import org.simantics.db.Session; +import org.simantics.db.common.request.AsyncReadRequest; +import org.simantics.db.common.utils.NameUtils; +import org.simantics.db.event.ChangeEvent; +import org.simantics.db.event.ChangeListener; +import org.simantics.db.exception.DatabaseException; +import org.simantics.db.procedure.AsyncMultiProcedure; +import org.simantics.db.procedure.AsyncProcedure; +import org.simantics.db.service.GraphChangeListenerSupport; +import org.simantics.layer0.Layer0; +import org.simantics.modeling.ComponentUtils; +import org.simantics.project.IProject; +import org.simantics.structural.stubs.StructuralResource2; +import org.simantics.utils.datastructures.Pair; +import org.simantics.utils.ui.ErrorLogger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A first-hand component naming strategy implementation for structural models. + * + * This version is somewhat optimized for case-insensitivity by using custom + * comparators in maps and sets. It uses a soft-referenced cache of + * used/requested names per a single root of a configuration's component + * hierarchy. + * + * @author Tuukka Lehtonen + * + * @see ComponentNamingStrategy + */ +public class CaseInsensitiveComponentNamingStrategy2 extends ComponentNamingStrategyBase implements ChangeListener { + + private static final Logger LOGGER = LoggerFactory.getLogger(CaseInsensitiveComponentNamingStrategy2.class); + private static final boolean DEBUG_ALL = false; + private static final boolean DEBUG_GRAPH_UPDATES = false | DEBUG_ALL; + private static final boolean DEBUG_CACHE_INITIALIZATION = false | DEBUG_ALL; + private static final boolean DEBUG_CACHE_INITIALIZATION_BROWSE = false | DEBUG_ALL; + private static final boolean DEBUG_CACHE_UPDATES = false | DEBUG_ALL; + + static class Cache { + private final Resource root; + private final Set reserved; + + // Having cache as soft references should somewhat address the problem + // of the amount of requested names growing too large. + private final Set requested; + + // Only internally used data + private final ConcurrentMap r2s; + private final ConcurrentMap> s2r; + + public Cache(Resource root, Set reserved, ConcurrentMap r2s, ConcurrentMap> s2r, boolean caseInsensitive) { + assert root != null; + assert reserved != null; + assert r2s != null; + assert s2r != null; + this.root = root; + this.reserved = reserved; + this.r2s = r2s; + this.s2r = s2r; + this.requested = new ConcurrentSkipListSet(getComparator(caseInsensitive)); + } + + @Override + protected void finalize() throws Throwable { + if (DEBUG_CACHE_UPDATES) + debug("FINALIZE"); + super.finalize(); + } + + public Resource getRoot() { + return root; + } + + public Set getReserved() { + // This prevents the map from being retrieved during a cache update. + synchronized (this) { + return reserved; + } + } + + public Set getRequested() { + // This prevents the map from being retrieved during a cache update. + synchronized (this) { + return requested; + } + } + + public void addRequested(String name) { + requested.add(name); + } + + public void replaceEntries(Collection> entries) { + if (entries.isEmpty()) + return; + + if (DEBUG_CACHE_UPDATES) + debug(" updating " + entries.size() +" cache entries"); + + synchronized (this) { + for (Pair entry : entries) { + Resource component = entry.first; + String newName = entry.second; + + assert component != null; + assert newName != null; + + String oldName = r2s.get(component); + if (oldName == null) { + // This must be an uncached new component. + + // Validate cache. + Set existingEntries = getMapSet(s2r, newName); + if (!existingEntries.isEmpty()) { + System.out.println("WARNING: Somebody is screwing the model up with duplicate name: " + newName); + // TODO: generate issue or message + } + + Object prev = r2s.putIfAbsent(component, newName); + assert prev == null; + addToMapSet(s2r, newName, component); + + reserved.add(newName); + requested.remove(newName); + + if (DEBUG_CACHE_UPDATES) + debug("\tnew component name: " + newName); + } else { + // This must be a change to an existing cached component. + + // Validate cache + Set existingEntries = getMapSet(s2r, newName); + if (!existingEntries.isEmpty()) { + // Currently changesets can contain multiple entries for a same change. + // This picks out one of such cases where the value of a resource has been + // set multiple times to the same value. + if (existingEntries.contains(component)) + continue; + + System.out.println("WARNING: Somebody is screwing the model up with duplicate name: " + newName); + // TODO: generate issue or message + } + + Set resourcesWithOldName = removeFromMapSet(s2r, oldName, component); + addToMapSet(s2r, newName, component); + boolean updated = r2s.replace(component, oldName, newName); + assert updated; + if (resourcesWithOldName.isEmpty()) { + reserved.remove(oldName); + } + reserved.add(newName); + requested.remove(newName); + + if (DEBUG_CACHE_UPDATES) + debug("\tcomponent name changed: " + oldName + " -> " + newName); + } + } + + if (DEBUG_CACHE_UPDATES) { + debug("reserved names after update: " + reserved); + debug("requested names after update: " + requested); + } + } + } + + private void debug(String string) { + CaseInsensitiveComponentNamingStrategy2.debug(this, string); + } + } + + static class CacheFactory { + final ReadGraph graph; + final Resource root; + final Layer0 b; + final StructuralResource2 sr; + final boolean caseInsensitive; + + final Set reserved; + final ConcurrentMap r2s = new ConcurrentSkipListMap(); + final ConcurrentMap> s2r; + + CacheFactory(ReadGraph graph, Resource root, boolean caseInsensitive) { + this.graph = graph; + this.root = root; + this.b = Layer0.getInstance(graph); + this.sr = StructuralResource2.getInstance(graph); + this.caseInsensitive = caseInsensitive; + + this.reserved = new ConcurrentSkipListSet(getComparator(caseInsensitive)); + this.s2r = new ConcurrentSkipListMap>(getComparator(caseInsensitive)); + } + + private void debug(String string) { + CaseInsensitiveComponentNamingStrategy2.debug(this, string); + } + + public Cache create() throws DatabaseException { + if (DEBUG_CACHE_INITIALIZATION_BROWSE) + debug("browsing all components from root " + root); + + graph.syncRequest(new AsyncReadRequest() { + @Override + public void run(AsyncReadGraph graph) { + browseComposite(graph, root); + } + }); + + if (DEBUG_CACHE_INITIALIZATION_BROWSE) + debug("browsing completed, results:\n\treserved: " + reserved + "\n\tr2s: " + r2s + "\n\ts2r: " + s2r); + + return new Cache(root, reserved, r2s, s2r, caseInsensitive); + } + + static abstract class MultiProc implements AsyncMultiProcedure { + @Override + public void finished(AsyncReadGraph graph) { + } + @Override + public void exception(AsyncReadGraph graph, Throwable t) { + ErrorLogger.defaultLogError(t); + } + } + + static abstract class AsyncProc implements AsyncProcedure { + @Override + public void exception(AsyncReadGraph graph, Throwable t) { + ErrorLogger.defaultLogError(t); + } + } + + private void browseComposite(AsyncReadGraph graph, Resource composite) { + if (DEBUG_CACHE_INITIALIZATION_BROWSE) + debug("browsing composite " + composite); + graph.forEachObject(composite, b.ConsistsOf, new MultiProc() { + @Override + public void execute(AsyncReadGraph graph, Resource component) { + browseComponent(graph, component); + } + + private void browseComponent(AsyncReadGraph graph, final Resource component) { + if (DEBUG_CACHE_INITIALIZATION_BROWSE) + debug("browsing component " + component); + reserveName(graph, component); + graph.forIsInstanceOf(component, sr.Composite, new AsyncProc() { + @Override + public void execute(AsyncReadGraph graph, Boolean result) { + if (result) + browseComposite(graph, component); + } + }); + } + + private void reserveName(AsyncReadGraph graph, final Resource component) { + graph.forPossibleRelatedValue(component, b.HasName, new AsyncProc() { + @Override + public void execute(AsyncReadGraph graph, String componentName) { + if (componentName != null) { + if (DEBUG_CACHE_INITIALIZATION_BROWSE) + debug("reserving name of component " + component + " '" + componentName + "'"); + Set components = addToMapSet(s2r, componentName, component); + if (components.size() > 1) { + // Found duplicate names in the model !! + // TODO: generate issue! + LOGGER.warn("WARNING: found multiple components with same name '" + componentName + "': " + components); + LOGGER.warn("TODO: generate issue"); + } else { + String prevName = r2s.putIfAbsent(component, componentName); + if (prevName == null) + reserved.add(componentName); + } + } + } + }); + } + }); + } + } + + private SoftReference>> mapRef = + new SoftReference>>(new THashMap>()); + + private final GraphChangeListenerSupport changeSupport; + private Resource inverseOfHasName; + + public CaseInsensitiveComponentNamingStrategy2() { + this(Simantics.getSession().getService(GraphChangeListenerSupport.class), "%s_%d"); + } + + public CaseInsensitiveComponentNamingStrategy2(GraphChangeListenerSupport changeSupport) { + this(changeSupport, "%s %d"); + } + + /** + * @param changeSupport + * @param generatedNameFormat the format to use for generated names, see + * {@link Formatter} + */ + public CaseInsensitiveComponentNamingStrategy2(GraphChangeListenerSupport changeSupport, String generatedNameFormat) { + super(generatedNameFormat); + this.changeSupport = changeSupport; + changeSupport.addListener(this); + } + + public void dispose() { + changeSupport.removeListener(this); + } + + static class CacheUpdateBundle { + IdentityHashMap>> updates = new IdentityHashMap>>(); + + public boolean isEmpty() { + return updates.isEmpty(); + } + + public void add(Cache cache, Resource component, String newName) { + assert cache != null; + assert component != null; + assert newName != null; + + Collection> collection = updates.get(cache); + if (collection == null) { + collection = new ArrayList>(); + updates.put(cache, collection); + } + + collection.add(Pair.make(component, newName)); + } + + public void commitAll() { + for (Map.Entry>> entry : updates.entrySet()) { + Cache cache = entry.getKey(); + cache.replaceEntries(entry.getValue()); + } + } + + @Override + public String toString() { + return getClass().getSimpleName() + " [" + updates.size() + " changed caches]"; + } + } + + @Override + public void graphChanged(ChangeEvent e) throws DatabaseException { + Collection changedValues = e.getChanges().changedValues(); + if (DEBUG_GRAPH_UPDATES) + debug("graph updated with " + changedValues.size() + " value changes"); + + ReadGraph graph = e.getGraph(); + Layer0 b = Layer0.getInstance(graph); + + // Cache inverse of Has Name relation. + if (inverseOfHasName == null) { + inverseOfHasName = graph.getInverse(b.HasName); + } + + CacheUpdateBundle bundle = new CacheUpdateBundle(); + + for (Resource value : changedValues) { + //System.out.println("VALUE CHANGE: " + GraphUtils.getReadableName(graph, value)); + for (Resource nameOfComponent : graph.getObjects(value, inverseOfHasName)) { + if (DEBUG_GRAPH_UPDATES) + debug("\tNAME CHANGE: " + NameUtils.getSafeName(graph, value)); + Resource root = ComponentUtils.tryGetComponentConfigurationRoot(graph, nameOfComponent); + Cache cache = peekCache(graph, root); + if (cache != null) { + String newName = graph.getPossibleValue(value, Bindings.STRING); + if (newName != null) { + if (DEBUG_GRAPH_UPDATES) + debug("\t\tqueued cache update"); + bundle.add(cache, nameOfComponent, newName); + } + } + } + } + + if (!bundle.isEmpty()) { + if (DEBUG_GRAPH_UPDATES) + debug("committing " + bundle); + bundle.commitAll(); + } + } + + private Cache getCache(ReadGraph graph, Resource configurationRoot) throws DatabaseException { + Cache cache = null; + THashMap> map = mapRef.get(); + + if (map != null) { + SoftReference cacheRef = map.get(configurationRoot); + if (cacheRef != null) { + cache = cacheRef.get(); + if (cache != null) + // Cache hit! + return cache; + } + } else { + // Cache miss, rebuild cache index + map = new THashMap>(); + mapRef = new SoftReference>>(map); + } + + // Cache miss, rebuild local cache + if (DEBUG_CACHE_INITIALIZATION) + debug("Constructing new cache for root " + NameUtils.getSafeName(graph, configurationRoot) + " (" + configurationRoot + ")"); + cache = new CacheFactory(graph, configurationRoot, caseInsensitive).create(); + if (DEBUG_CACHE_INITIALIZATION) + debug("\tInitialized with reservations: " + cache.getReserved()); + map.put(configurationRoot, new SoftReference(cache)); + return cache; + } + + private Cache peekCache(ReadGraph graph, Resource configurationRoot) { + THashMap> map = mapRef.get(); + if (map == null) + return null; + SoftReference cacheRef = map.get(configurationRoot); + if (cacheRef == null) + return null; + Cache cache = cacheRef.get(); + if (cache == null) + return null; + // Cache hit! + return cache; + } + + @Override + public String validateInstanceName(ReadGraph graph, + Resource configurationRoot, Resource component, String proposition, boolean acceptProposition) + throws NamingException, DatabaseException { + + Layer0 L0 = Layer0.getInstance(graph); + StructuralResource2 STR = StructuralResource2.getInstance(graph); + Resource container = graph.getSingleObject(component, L0.PartOf); + Resource componentType = graph.getSingleType(component, STR.Component); + return validateInstanceName(graph, configurationRoot, container, componentType, proposition, acceptProposition); + + } + + @Override + public String validateInstanceName(ReadGraph graph, Resource configurationRoot, Resource container, + Resource componentType, String proposition, boolean acceptProposition) throws NamingException, DatabaseException { + Cache cache = getCache(graph, configurationRoot); + synchronized (cache) { + String result = findFreshName(cache.getReserved(), cache.getRequested(), proposition, acceptProposition); + cache.addRequested(result); + return result; + } + } + + private void debug(String string) { + debug(this, string); + } + + private static void debug(Object obj, String string) { + System.out.println("[" + obj.getClass().getSimpleName() + "(" + System.identityHashCode(obj) + ")] " + string); + } + + private static Set addToMapSet(ConcurrentMap> map, K key, V value) { + Set set = map.get(key); + if (set == null) { + set = new HashSet(1); + map.putIfAbsent(key, set); + } + set.add(value); + return set; + } + + private static Set getMapSet(ConcurrentMap> map, K key) { + Set set = map.get(key); + if (set == null) + return Collections.emptySet(); + return set; + } + + private static Set removeFromMapSet(ConcurrentMap> map, K key, V value) { + Set set = map.get(key); + if (set == null) + return Collections.emptySet(); + if (set.remove(value)) { + if (set.isEmpty()) { + map.remove(key); + return Collections.emptySet(); + } + } + return set; + } + + public static CaseInsensitiveComponentNamingStrategy2 install(IProject project) { + + Session session = project.getSession(); + + GraphChangeListenerSupport changeSupport = session.peekService(GraphChangeListenerSupport.class); + if (changeSupport != null) { + CaseInsensitiveComponentNamingStrategy2 namingStrategy = new CaseInsensitiveComponentNamingStrategy2(changeSupport, "%s%02d"); + project.setHint(ComponentNamingStrategy.PROJECT_KEY, namingStrategy); + return namingStrategy; + } else { + System.out.println("WARNING: No GraphChangeListenerSupport in session " + session +", can't initialize all services."); + } + + return null; + + } + +}