1 /*******************************************************************************
\r
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
\r
3 * in Industry THTH ry.
\r
4 * All rights reserved. This program and the accompanying materials
\r
5 * are made available under the terms of the Eclipse Public License v1.0
\r
6 * which accompanies this distribution, and is available at
\r
7 * http://www.eclipse.org/legal/epl-v10.html
\r
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.modeling.services;
\r
14 import java.util.ArrayList;
\r
15 import java.util.Comparator;
\r
16 import java.util.Formatter;
\r
17 import java.util.List;
\r
18 import java.util.Set;
\r
19 import java.util.TreeSet;
\r
21 import org.simantics.databoard.Bindings;
\r
22 import org.simantics.db.ReadGraph;
\r
23 import org.simantics.db.Resource;
\r
24 import org.simantics.db.common.Indexing;
\r
25 import org.simantics.db.common.procedure.adapter.TransientCacheAsyncListener;
\r
26 import org.simantics.db.common.request.PossibleIndexRoot;
\r
27 import org.simantics.db.common.request.UnaryRead;
\r
28 import org.simantics.db.common.utils.NameUtils;
\r
29 import org.simantics.db.exception.DatabaseException;
\r
30 import org.simantics.db.layer0.genericrelation.IndexQueries;
\r
31 import org.simantics.db.service.GraphChangeListenerSupport;
\r
32 import org.simantics.layer0.Layer0;
\r
33 import org.simantics.scl.runtime.function.Function;
\r
34 import org.simantics.scl.runtime.function.Function1;
\r
35 import org.simantics.scl.runtime.tuple.Tuple3;
\r
36 import org.simantics.scl.runtime.tuple.Tuple4;
\r
37 import org.simantics.structural.stubs.StructuralResource2;
\r
39 import gnu.trove.set.hash.THashSet;
\r
42 * A component naming strategy implementation for structural models based on an
\r
43 * SCL function that lists used names in a model.
\r
46 * The type of the function is expected to be:
\r
47 * <code>ReadGraph => Resource -> String -> Integer -> List<Map<String,Object>></code>
\r
49 * @author Tuukka Lehtonen
\r
51 * @see ComponentNamingStrategy
\r
53 public class CaseInsensitiveComponentFunctionNamingStrategy extends ComponentNamingStrategyBase {
\r
55 protected static final boolean DEBUG_INDEX_SEARCH = false | DEBUG_ALL;
\r
57 @SuppressWarnings("rawtypes")
\r
58 private final Function index;
\r
61 * A filter that is applied to all user-provided name propositions before
\r
64 private Function1<String, String> propositionPreFilter;
\r
67 * Construct an index-based naming strategy with "%s %d" as the generated
\r
68 * name format. See {@link Formatter} for the format specification.
\r
70 * @param index the index function for looking up used names. The function
\r
72 * <code>ReadGraph => Resource -> String -> Integer -> List<Map<String,Object>></code>
\r
74 @SuppressWarnings("rawtypes")
\r
75 public CaseInsensitiveComponentFunctionNamingStrategy(Function index) {
\r
76 this("%s %d", index);
\r
80 * Construct an index-based naming strategy with the specified format as the
\r
81 * generated name format. See {@link Formatter} for the format
\r
84 * @param generatedNameFormat the format to use for generated names, see
\r
86 * @param index the index function for looking up used names. The function
\r
88 * <code>ReadGraph => Resource -> String -> Integer -> List<Map<String,Object>></code>
\r
90 @SuppressWarnings("rawtypes")
\r
91 public CaseInsensitiveComponentFunctionNamingStrategy(String generatedNameFormat, Function index) {
\r
92 super(generatedNameFormat);
\r
97 * Construct an index-based naming strategy with the specified format as the
\r
98 * generated name format. See {@link Formatter} for the format
\r
101 * @param generatedNameFormat the format to use for generated names, see
\r
102 * {@link Formatter}
\r
103 * @param index the index function for looking up used names. The function
\r
105 * <code>ReadGraph => Resource -> String -> Integer -> List<Map<String,Object>></code>
\r
106 * @param an optional function to
\r
108 @SuppressWarnings("rawtypes")
\r
109 public CaseInsensitiveComponentFunctionNamingStrategy(String generatedNameFormat, Function index, Function1<String, String> propositionPreFilter) {
\r
110 super(generatedNameFormat);
\r
111 this.index = index;
\r
112 this.propositionPreFilter = propositionPreFilter;
\r
115 CaseInsensitiveComponentNamingStrategy2 fallbackStrategy = null;
\r
118 public String validateInstanceName(ReadGraph graph,
\r
119 Resource configurationRoot, Resource component, String proposition, boolean acceptProposition)
\r
120 throws NamingException, DatabaseException {
\r
122 String lowercaseProposition = proposition.toLowerCase();
\r
124 Layer0 L0 = Layer0.getInstance(graph);
\r
125 String name = graph.getPossibleRelatedValue(component, L0.HasName, Bindings.STRING);
\r
126 if (name != null) {
\r
128 String lowercaseName = name.toLowerCase();
\r
129 if(lowercaseName.startsWith(lowercaseProposition)) {
\r
131 Resource indexRoot = graph.syncRequest(new PossibleIndexRoot(configurationRoot));
\r
132 if (indexRoot != null) {
\r
134 synchronized (this) {
\r
136 String search = "Name:" + lowercaseName + "*";
\r
137 @SuppressWarnings("unchecked")
\r
138 List<Resource> components = (List<Resource>) index.apply(graph, indexRoot, search, Integer.MAX_VALUE);
\r
140 Set<Resource> rs = new THashSet<Resource>();
\r
141 for (Resource componentResult : components) {
\r
142 if (DEBUG_INDEX_SEARCH)
\r
143 System.out.println(getClass().getSimpleName() + ": found " + componentResult);
\r
144 String n = graph.getPossibleRelatedValue(componentResult, L0.HasName, Bindings.STRING);
\r
145 if (n != null && n.toLowerCase().equals(lowercaseName))
\r
146 rs.add(componentResult);
\r
149 Cache c = getCache(graph, indexRoot);
\r
151 if(rs.size() == 0) {
\r
152 if(!c.getRequested().contains(name)) {
\r
153 c.addRequested(name);
\r
156 } else if(rs.size() == 1) {
\r
157 if(component.equals(rs.iterator().next())) {
\r
170 StructuralResource2 STR = StructuralResource2.getInstance(graph);
\r
171 Resource container = graph.getSingleObject(component, L0.PartOf);
\r
172 Resource componentType = graph.getSingleType(component, STR.Component);
\r
173 return validateInstanceName(graph, configurationRoot, container, componentType, proposition, acceptProposition);
\r
177 static class ComponentsRequest extends UnaryRead<Tuple4, Set<String>>{
\r
179 public ComponentsRequest(Tuple4 parameter) {
\r
184 public Set<String> perform(ReadGraph graph) throws DatabaseException {
\r
186 Resource indexRoot = (Resource)parameter.get(0);
\r
187 Function index = (Function)parameter.get(1);
\r
188 String search = (String)parameter.get(2);
\r
189 Comparator<Object> comparator = (Comparator<Object>)parameter.get(3);
\r
191 List<Resource> components = (List<Resource>) index.apply(graph, indexRoot, search, Integer.MAX_VALUE);
\r
192 Set<String> reserved = new TreeSet<String>(comparator);
\r
194 Layer0 L0 = Layer0.getInstance(graph);
\r
195 for (Resource componentResult : components) {
\r
196 if (DEBUG_INDEX_SEARCH)
\r
197 System.out.println(getClass().getSimpleName() + ": found " + componentResult);
\r
198 String name = graph.getPossibleRelatedValue(componentResult, L0.HasName, Bindings.STRING);
\r
200 reserved.add(name);
\r
203 System.err.println("found " + reserved.size() + " components");
\r
212 public String validateInstanceName(ReadGraph graph, Resource configurationRoot, Resource container,
\r
213 Resource componentType, String proposition, boolean acceptProposition) throws NamingException, DatabaseException {
\r
214 Resource indexRoot = graph.syncRequest(new PossibleIndexRoot(configurationRoot));
\r
215 if (indexRoot == null) {
\r
216 System.err.println("Could not find index root from configuration root '" + NameUtils.getSafeName(graph, configurationRoot, true) + "'");
\r
217 if(fallbackStrategy == null)
\r
218 fallbackStrategy =
\r
219 new CaseInsensitiveComponentNamingStrategy2(graph.getService(GraphChangeListenerSupport.class), generatedNameFormat);
\r
220 return fallbackStrategy.validateInstanceName(graph, configurationRoot, container, componentType, proposition, acceptProposition);
\r
223 if (propositionPreFilter != null)
\r
224 proposition = propositionPreFilter.apply(proposition);
\r
226 synchronized (this) {
\r
228 String search = "Name:" + proposition + "*";
\r
230 Set<String> reserved = graph.syncRequest(new ComponentsRequest(new Tuple4(indexRoot, index, search, getComparator())), TransientCacheAsyncListener.instance());
\r
232 Cache cache = getCache(graph, indexRoot);
\r
234 findStartsWithMatches(cache.getRequested(), proposition, reserved);
\r
236 String result = findFreshName(reserved, proposition, acceptProposition);
\r
237 cache.addRequested(result);
\r
239 if (DEBUG_INDEX_SEARCH)
\r
240 System.out.println(getClass().getSimpleName() + ": validated instance name " + result);
\r
247 public List<String> validateInstanceNames(
\r
249 Resource configurationRoot,
\r
250 List<String> propositions,
\r
251 boolean acceptProposition,
\r
252 Set<String> externallyReserved) throws NamingException, DatabaseException
\r
254 Resource indexRoot = graph.syncRequest(new PossibleIndexRoot(configurationRoot));
\r
255 if (indexRoot == null)
\r
256 throw new NamingException("Could not find index root from configuration root '" + NameUtils.getSafeName(graph, configurationRoot, true) + "'");
\r
258 Layer0 L0 = Layer0.getInstance(graph);
\r
260 synchronized (this) {
\r
261 List<String> result = new ArrayList<String>(propositions.size());
\r
262 Set<String> reserved = new TreeSet<String>(getComparator());
\r
264 for (String proposition : propositions) {
\r
265 if (propositionPreFilter != null)
\r
266 proposition = propositionPreFilter.apply(proposition);
\r
268 String search = "Name:" + IndexQueries.escape( proposition ) + "*";
\r
269 @SuppressWarnings("unchecked")
\r
270 List<Resource> components = (List<Resource>) index.apply(graph, indexRoot, search, Integer.MAX_VALUE);
\r
272 if (DEBUG_INDEX_SEARCH)
\r
273 System.out.println(getClass().getSimpleName() + ": found " + components.size()
\r
274 + " index results for index root " + indexRoot + " & configurationRoot " + configurationRoot
\r
275 + " & proposition '" + proposition + "':");
\r
278 for (Resource componentResult : components) {
\r
279 if (DEBUG_INDEX_SEARCH)
\r
280 System.out.println(getClass().getSimpleName() + ": found " + componentResult);
\r
281 String name = graph.getPossibleRelatedValue(componentResult, L0.HasName, Bindings.STRING);
\r
283 reserved.add(name);
\r
286 if (externallyReserved != null)
\r
287 reserved.addAll(externallyReserved);
\r
288 reserved.addAll(result);
\r
289 String name = findFreshName(reserved, proposition, acceptProposition);
\r
293 if (DEBUG_INDEX_SEARCH)
\r
294 System.out.println(getClass().getSimpleName() + ": validated instance name " + proposition + " -> " + name);
\r
297 if (DEBUG_INDEX_SEARCH)
\r
298 System.out.println(getClass().getSimpleName() + ": validated instance names " + propositions + " -> " + result);
\r
304 private Cache getCache(ReadGraph graph, Resource root) throws DatabaseException {
\r
305 Cache cache = Indexing.getCache(root, Cache.class);
\r
306 if(cache != null) return cache;
\r
307 synchronized (this) {
\r
308 cache = Indexing.getCache(root, Cache.class);
\r
309 if(cache != null) return cache;
\r
310 return Indexing.createCache(root, new Cache(caseInsensitive));
\r
314 static class Cache {
\r
316 private final Set<String> requested;
\r
318 Cache(boolean caseInsensitive) {
\r
319 this.requested = new TreeSet<String>(getComparator(caseInsensitive));
\r
322 public Set<String> getRequested() {
\r
326 public void addRequested(String name) {
\r
327 requested.add(name);
\r
331 public String toString() {
\r
332 return getClass().getSimpleName() + ": " + requested;
\r