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