--- /dev/null
+/*******************************************************************************\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 java.util.ArrayList;\r
+import java.util.Comparator;\r
+import java.util.Formatter;\r
+import java.util.List;\r
+import java.util.Set;\r
+import java.util.TreeSet;\r
+\r
+import org.simantics.databoard.Bindings;\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.Resource;\r
+import org.simantics.db.common.Indexing;\r
+import org.simantics.db.common.procedure.adapter.TransientCacheAsyncListener;\r
+import org.simantics.db.common.request.PossibleIndexRoot;\r
+import org.simantics.db.common.request.UnaryRead;\r
+import org.simantics.db.common.utils.NameUtils;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.db.layer0.genericrelation.IndexQueries;\r
+import org.simantics.db.service.GraphChangeListenerSupport;\r
+import org.simantics.layer0.Layer0;\r
+import org.simantics.scl.runtime.function.Function;\r
+import org.simantics.scl.runtime.function.Function1;\r
+import org.simantics.scl.runtime.tuple.Tuple3;\r
+import org.simantics.scl.runtime.tuple.Tuple4;\r
+import org.simantics.structural.stubs.StructuralResource2;\r
+\r
+import gnu.trove.set.hash.THashSet;\r
+\r
+/**\r
+ * A component naming strategy implementation for structural models based on an\r
+ * SCL function that lists used names in a model.\r
+ * \r
+ * <p>\r
+ * The type of the function is expected to be:\r
+ * <code>ReadGraph => Resource -> String -> Integer -> List<Map<String,Object>></code>\r
+ * \r
+ * @author Tuukka Lehtonen\r
+ * \r
+ * @see ComponentNamingStrategy\r
+ */\r
+public class CaseInsensitiveComponentFunctionNamingStrategy extends ComponentNamingStrategyBase {\r
+\r
+ protected static final boolean DEBUG_INDEX_SEARCH = false | DEBUG_ALL;\r
+\r
+ @SuppressWarnings("rawtypes")\r
+ private final Function index;\r
+\r
+ /**\r
+ * A filter that is applied to all user-provided name propositions before\r
+ * processing them.\r
+ */\r
+ private Function1<String, String> propositionPreFilter;\r
+\r
+ /**\r
+ * Construct an index-based naming strategy with "%s %d" as the generated\r
+ * name format. See {@link Formatter} for the format specification.\r
+ * \r
+ * @param index the index function for looking up used names. The function\r
+ * must be of type\r
+ * <code>ReadGraph => Resource -> String -> Integer -> List<Map<String,Object>></code>\r
+ */\r
+ @SuppressWarnings("rawtypes")\r
+ public CaseInsensitiveComponentFunctionNamingStrategy(Function index) {\r
+ this("%s %d", index);\r
+ }\r
+\r
+ /**\r
+ * Construct an index-based naming strategy with the specified format as the\r
+ * generated name format. See {@link Formatter} for the format\r
+ * specification.\r
+ * \r
+ * @param generatedNameFormat the format to use for generated names, see\r
+ * {@link Formatter}\r
+ * @param index the index function for looking up used names. The function\r
+ * must be of type\r
+ * <code>ReadGraph => Resource -> String -> Integer -> List<Map<String,Object>></code>\r
+ */\r
+ @SuppressWarnings("rawtypes")\r
+ public CaseInsensitiveComponentFunctionNamingStrategy(String generatedNameFormat, Function index) {\r
+ super(generatedNameFormat);\r
+ this.index = index;\r
+ }\r
+\r
+ /**\r
+ * Construct an index-based naming strategy with the specified format as the\r
+ * generated name format. See {@link Formatter} for the format\r
+ * specification.\r
+ * \r
+ * @param generatedNameFormat the format to use for generated names, see\r
+ * {@link Formatter}\r
+ * @param index the index function for looking up used names. The function\r
+ * must be of type\r
+ * <code>ReadGraph => Resource -> String -> Integer -> List<Map<String,Object>></code>\r
+ * @param an optional function to \r
+ */\r
+ @SuppressWarnings("rawtypes")\r
+ public CaseInsensitiveComponentFunctionNamingStrategy(String generatedNameFormat, Function index, Function1<String, String> propositionPreFilter) {\r
+ super(generatedNameFormat);\r
+ this.index = index;\r
+ this.propositionPreFilter = propositionPreFilter;\r
+ }\r
+\r
+ CaseInsensitiveComponentNamingStrategy2 fallbackStrategy = null;\r
+\r
+ @Override\r
+ public String validateInstanceName(ReadGraph graph,\r
+ Resource configurationRoot, Resource component, String proposition, boolean acceptProposition)\r
+ throws NamingException, DatabaseException {\r
+\r
+ String lowercaseProposition = proposition.toLowerCase();\r
+ \r
+ Layer0 L0 = Layer0.getInstance(graph);\r
+ String name = graph.getPossibleRelatedValue(component, L0.HasName, Bindings.STRING);\r
+ if (name != null) {\r
+ \r
+ String lowercaseName = name.toLowerCase();\r
+ if(lowercaseName.startsWith(lowercaseProposition)) {\r
+ \r
+ Resource indexRoot = graph.syncRequest(new PossibleIndexRoot(configurationRoot));\r
+ if (indexRoot != null) {\r
+ \r
+ synchronized (this) {\r
+ \r
+ String search = "Name:" + lowercaseName + "*";\r
+ @SuppressWarnings("unchecked")\r
+ List<Resource> components = (List<Resource>) index.apply(graph, indexRoot, search, Integer.MAX_VALUE);\r
+\r
+ Set<Resource> rs = new THashSet<Resource>();\r
+ for (Resource componentResult : components) {\r
+ if (DEBUG_INDEX_SEARCH)\r
+ System.out.println(getClass().getSimpleName() + ": found " + componentResult);\r
+ String n = graph.getPossibleRelatedValue(componentResult, L0.HasName, Bindings.STRING);\r
+ if (n != null && n.toLowerCase().equals(lowercaseName))\r
+ rs.add(componentResult);\r
+ }\r
+ \r
+ Cache c = getCache(graph, indexRoot);\r
+ \r
+ if(rs.size() == 0) { \r
+ if(!c.getRequested().contains(name)) {\r
+ c.addRequested(name);\r
+ return name;\r
+ }\r
+ } else if(rs.size() == 1) {\r
+ if(component.equals(rs.iterator().next())) {\r
+ return name;\r
+ }\r
+ }\r
+\r
+ }\r
+ \r
+ }\r
+ \r
+ }\r
+ \r
+ }\r
+\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
+ static class ComponentsRequest extends UnaryRead<Tuple4, Set<String>>{\r
+\r
+ public ComponentsRequest(Tuple4 parameter) {\r
+ super(parameter);\r
+ }\r
+\r
+ @Override\r
+ public Set<String> perform(ReadGraph graph) throws DatabaseException {\r
+ \r
+ Resource indexRoot = (Resource)parameter.get(0);\r
+ Function index = (Function)parameter.get(1);\r
+ String search = (String)parameter.get(2);\r
+ Comparator<Object> comparator = (Comparator<Object>)parameter.get(3);\r
+ \r
+ List<Resource> components = (List<Resource>) index.apply(graph, indexRoot, search, Integer.MAX_VALUE);\r
+ Set<String> reserved = new TreeSet<String>(comparator);\r
+\r
+ Layer0 L0 = Layer0.getInstance(graph);\r
+ for (Resource componentResult : components) {\r
+ if (DEBUG_INDEX_SEARCH)\r
+ System.out.println(getClass().getSimpleName() + ": found " + componentResult);\r
+ String name = graph.getPossibleRelatedValue(componentResult, L0.HasName, Bindings.STRING);\r
+ if (name != null)\r
+ reserved.add(name);\r
+ }\r
+ \r
+ System.err.println("found " + reserved.size() + " components");\r
+ \r
+ return reserved;\r
+\r
+ }\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
+ Resource indexRoot = graph.syncRequest(new PossibleIndexRoot(configurationRoot));\r
+ if (indexRoot == null) {\r
+ System.err.println("Could not find index root from configuration root '" + NameUtils.getSafeName(graph, configurationRoot, true) + "'");\r
+ if(fallbackStrategy == null)\r
+ fallbackStrategy = \r
+ new CaseInsensitiveComponentNamingStrategy2(graph.getService(GraphChangeListenerSupport.class), generatedNameFormat);\r
+ return fallbackStrategy.validateInstanceName(graph, configurationRoot, container, componentType, proposition, acceptProposition);\r
+ }\r
+\r
+ if (propositionPreFilter != null)\r
+ proposition = propositionPreFilter.apply(proposition);\r
+\r
+ synchronized (this) {\r
+ \r
+ String search = "Name:" + proposition + "*";\r
+ \r
+ Set<String> reserved = graph.syncRequest(new ComponentsRequest(new Tuple4(indexRoot, index, search, getComparator())), TransientCacheAsyncListener.instance());\r
+\r
+ Cache cache = getCache(graph, indexRoot);\r
+\r
+ findStartsWithMatches(cache.getRequested(), proposition, reserved);\r
+\r
+ String result = findFreshName(reserved, proposition, acceptProposition);\r
+ cache.addRequested(result);\r
+\r
+ if (DEBUG_INDEX_SEARCH)\r
+ System.out.println(getClass().getSimpleName() + ": validated instance name " + result);\r
+\r
+ return result;\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public List<String> validateInstanceNames(\r
+ ReadGraph graph,\r
+ Resource configurationRoot,\r
+ List<String> propositions,\r
+ boolean acceptProposition,\r
+ Set<String> externallyReserved) throws NamingException, DatabaseException\r
+ {\r
+ Resource indexRoot = graph.syncRequest(new PossibleIndexRoot(configurationRoot));\r
+ if (indexRoot == null)\r
+ throw new NamingException("Could not find index root from configuration root '" + NameUtils.getSafeName(graph, configurationRoot, true) + "'");\r
+\r
+ Layer0 L0 = Layer0.getInstance(graph);\r
+\r
+ synchronized (this) {\r
+ List<String> result = new ArrayList<String>(propositions.size());\r
+ Set<String> reserved = new TreeSet<String>(getComparator());\r
+\r
+ for (String proposition : propositions) {\r
+ if (propositionPreFilter != null)\r
+ proposition = propositionPreFilter.apply(proposition);\r
+\r
+ String search = "Name:" + IndexQueries.escape( proposition ) + "*";\r
+ @SuppressWarnings("unchecked")\r
+ List<Resource> components = (List<Resource>) index.apply(graph, indexRoot, search, Integer.MAX_VALUE);\r
+\r
+ if (DEBUG_INDEX_SEARCH)\r
+ System.out.println(getClass().getSimpleName() + ": found " + components.size()\r
+ + " index results for index root " + indexRoot + " & configurationRoot " + configurationRoot\r
+ + " & proposition '" + proposition + "':");\r
+\r
+ reserved.clear();\r
+ for (Resource componentResult : components) {\r
+ if (DEBUG_INDEX_SEARCH)\r
+ System.out.println(getClass().getSimpleName() + ": found " + componentResult);\r
+ String name = graph.getPossibleRelatedValue(componentResult, L0.HasName, Bindings.STRING);\r
+ if (name != null)\r
+ reserved.add(name);\r
+ }\r
+\r
+ if (externallyReserved != null)\r
+ reserved.addAll(externallyReserved);\r
+ reserved.addAll(result);\r
+ String name = findFreshName(reserved, proposition, acceptProposition);\r
+\r
+ result.add(name);\r
+\r
+ if (DEBUG_INDEX_SEARCH)\r
+ System.out.println(getClass().getSimpleName() + ": validated instance name " + proposition + " -> " + name);\r
+ }\r
+\r
+ if (DEBUG_INDEX_SEARCH)\r
+ System.out.println(getClass().getSimpleName() + ": validated instance names " + propositions + " -> " + result);\r
+\r
+ return result;\r
+ }\r
+ }\r
+\r
+ private Cache getCache(ReadGraph graph, Resource root) throws DatabaseException {\r
+ Cache cache = Indexing.getCache(root, Cache.class);\r
+ if(cache != null) return cache;\r
+ synchronized (this) {\r
+ cache = Indexing.getCache(root, Cache.class);\r
+ if(cache != null) return cache;\r
+ return Indexing.createCache(root, new Cache(caseInsensitive)); \r
+ }\r
+ }\r
+\r
+ static class Cache {\r
+ \r
+ private final Set<String> requested;\r
+\r
+ Cache(boolean caseInsensitive) {\r
+ this.requested = new TreeSet<String>(getComparator(caseInsensitive));\r
+ }\r
+\r
+ public Set<String> getRequested() {\r
+ return requested;\r
+ }\r
+\r
+ public void addRequested(String name) {\r
+ requested.add(name);\r
+ }\r
+\r
+ @Override\r
+ public String toString() {\r
+ return getClass().getSimpleName() + ": " + requested;\r
+ }\r
+\r
+ }\r
+\r
+}\r