]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.modeling/src/org/simantics/modeling/services/CaseInsensitiveComponentNamingStrategy.java
Fixed all line endings of the repository
[simantics/platform.git] / bundles / org.simantics.modeling / src / org / simantics / modeling / services / CaseInsensitiveComponentNamingStrategy.java
index e6e7432ba25d09d97675f4f2de6f62a223c8ef15..ff1c17bd36c72da78f14561218d0d858a772a00d 100644 (file)
-/*******************************************************************************\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;
+
+/**
+ * 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 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()) {
+                            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<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;
+
+                            System.out.println("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!
+                                    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<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) {
+        System.out.println("[" + 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;
+    }
+
+}