1 /*******************************************************************************
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.modeling.services;
14 import java.util.ArrayList;
15 import java.util.Comparator;
16 import java.util.Formatter;
17 import java.util.List;
19 import java.util.TreeSet;
21 import org.simantics.databoard.Bindings;
22 import org.simantics.db.ReadGraph;
23 import org.simantics.db.Resource;
24 import org.simantics.db.common.Indexing;
25 import org.simantics.db.common.procedure.adapter.TransientCacheAsyncListener;
26 import org.simantics.db.common.request.PossibleIndexRoot;
27 import org.simantics.db.common.request.UnaryRead;
28 import org.simantics.db.common.utils.NameUtils;
29 import org.simantics.db.exception.DatabaseException;
30 import org.simantics.db.layer0.genericrelation.Dependencies;
31 import org.simantics.db.layer0.genericrelation.IndexQueries;
32 import org.simantics.db.service.GraphChangeListenerSupport;
33 import org.simantics.layer0.Layer0;
34 import org.simantics.scl.runtime.function.Function;
35 import org.simantics.scl.runtime.function.Function1;
36 import org.simantics.scl.runtime.tuple.Tuple4;
37 import org.simantics.structural.stubs.StructuralResource2;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
41 import gnu.trove.set.hash.THashSet;
44 * A component naming strategy implementation for structural models based on an
45 * SCL function that lists used names in a model.
48 * The type of the function is expected to be:
49 * <code>ReadGraph => Resource -> String -> Integer -> List<Resource></code>
51 * @author Tuukka Lehtonen
53 * @see ComponentNamingStrategy
55 public class CaseInsensitiveComponentFunctionNamingStrategy extends ComponentNamingStrategyBase {
57 private static final Logger LOGGER = LoggerFactory.getLogger(CaseInsensitiveComponentFunctionNamingStrategy.class);
58 protected static final boolean DEBUG_INDEX_SEARCH = false | DEBUG_ALL;
60 @SuppressWarnings("rawtypes")
61 private final Function index;
64 * A filter that is applied to all user-provided name propositions before
67 private Function1<String, String> propositionPreFilter;
70 * Construct an index-based naming strategy with "%s %d" as the generated
71 * name format. See {@link Formatter} for the format specification.
73 * @param index the index function for looking up used names. The function
75 * <code>ReadGraph => Resource -> String -> Integer -> List<Map<String,Object>></code>
77 @SuppressWarnings("rawtypes")
78 public CaseInsensitiveComponentFunctionNamingStrategy(Function index) {
83 * Construct an index-based naming strategy with the specified format as the
84 * generated name format. See {@link Formatter} for the format
87 * @param generatedNameFormat the format to use for generated names, see
89 * @param index the index function for looking up used names. The function
91 * <code>ReadGraph => Resource -> String -> Integer -> List<Map<String,Object>></code>
93 @SuppressWarnings("rawtypes")
94 public CaseInsensitiveComponentFunctionNamingStrategy(String generatedNameFormat, Function index) {
95 super(generatedNameFormat);
100 * Construct an index-based naming strategy with the specified format as the
101 * generated name format. See {@link Formatter} for the format
104 * @param generatedNameFormat the format to use for generated names, see
106 * @param index the index function for looking up used names. The function
108 * <code>ReadGraph => Resource -> String -> Integer -> List<Map<String,Object>></code>
109 * @param an optional function to
111 @SuppressWarnings("rawtypes")
112 public CaseInsensitiveComponentFunctionNamingStrategy(String generatedNameFormat, Function index, Function1<String, String> propositionPreFilter) {
113 super(generatedNameFormat);
115 this.propositionPreFilter = propositionPreFilter;
118 CaseInsensitiveComponentNamingStrategy2 fallbackStrategy = null;
121 public String validateInstanceName(ReadGraph graph,
122 Resource configurationRoot, Resource component, String proposition, boolean acceptProposition)
123 throws NamingException, DatabaseException {
125 String lowercaseProposition = proposition.toLowerCase();
127 Layer0 L0 = Layer0.getInstance(graph);
128 String name = graph.getPossibleRelatedValue(component, L0.HasName, Bindings.STRING);
131 String lowercaseName = name.toLowerCase();
132 if(lowercaseName.startsWith(lowercaseProposition)) {
134 Resource indexRoot = graph.syncRequest(new PossibleIndexRoot(configurationRoot));
135 if (indexRoot != null) {
137 synchronized (this) {
139 String search = IndexQueries.escapeTerm(Dependencies.FIELD_NAME_SEARCH, lowercaseName, true) + "*"; //$NON-NLS-1$
140 @SuppressWarnings("unchecked")
141 List<Resource> components = (List<Resource>) index.apply(graph, indexRoot, search, Integer.MAX_VALUE);
143 Set<Resource> rs = new THashSet<Resource>();
144 for (Resource componentResult : components) {
145 if (DEBUG_INDEX_SEARCH)
146 LOGGER.info(getClass().getSimpleName() + ": found " + componentResult);
147 String n = graph.getPossibleRelatedValue(componentResult, L0.HasName, Bindings.STRING);
148 if (n != null && n.toLowerCase().equals(lowercaseName))
149 rs.add(componentResult);
152 Cache c = getCache(graph, indexRoot);
155 if(!c.getRequested().contains(name)) {
156 c.addRequested(name);
159 } else if(rs.size() == 1) {
160 if(component.equals(rs.iterator().next())) {
173 StructuralResource2 STR = StructuralResource2.getInstance(graph);
174 Resource container = graph.getSingleObject(component, L0.PartOf);
175 Resource componentType = graph.getSingleType(component, STR.Component);
176 return validateInstanceName(graph, configurationRoot, container, componentType, proposition, acceptProposition);
180 static class ComponentsRequest extends UnaryRead<Tuple4, Set<String>>{
182 public ComponentsRequest(Tuple4 parameter) {
187 public Set<String> perform(ReadGraph graph) throws DatabaseException {
189 Resource indexRoot = (Resource)parameter.get(0);
190 Function index = (Function)parameter.get(1);
191 String search = (String)parameter.get(2);
192 Comparator<Object> comparator = (Comparator<Object>)parameter.get(3);
194 List<Resource> components = (List<Resource>) index.apply(graph, indexRoot, search, Integer.MAX_VALUE);
195 Set<String> reserved = new TreeSet<String>(comparator);
197 Layer0 L0 = Layer0.getInstance(graph);
198 for (Resource componentResult : components) {
199 if (DEBUG_INDEX_SEARCH)
200 LOGGER.info(getClass().getSimpleName() + ": found " + componentResult);
201 String name = graph.getPossibleRelatedValue(componentResult, L0.HasName, Bindings.STRING);
206 LOGGER.warn("found " + reserved.size() + " components");
215 public String validateInstanceName(ReadGraph graph, Resource configurationRoot, Resource container,
216 Resource componentType, String proposition, boolean acceptProposition) throws NamingException, DatabaseException {
217 Resource indexRoot = graph.syncRequest(new PossibleIndexRoot(configurationRoot));
218 if (indexRoot == null) {
219 LOGGER.warn("Could not find index root from configuration root '" + NameUtils.getSafeName(graph, configurationRoot, true) + "'");
220 if(fallbackStrategy == null)
222 new CaseInsensitiveComponentNamingStrategy2(graph.getService(GraphChangeListenerSupport.class), generatedNameFormat);
223 return fallbackStrategy.validateInstanceName(graph, configurationRoot, container, componentType, proposition, acceptProposition);
226 if (propositionPreFilter != null)
227 proposition = propositionPreFilter.apply(proposition);
229 synchronized (this) {
231 String search = Dependencies.FIELD_NAME_SEARCH + ":" + IndexQueries.escape(proposition.toLowerCase(), true) + "*"; //$NON-NLS-1$ //$NON-NLS-2$
233 Set<String> reserved = graph.syncRequest(new ComponentsRequest(new Tuple4(indexRoot, index, search, getComparator())), TransientCacheAsyncListener.instance());
235 Cache cache = getCache(graph, indexRoot);
237 findStartsWithMatches(cache.getRequested(), proposition, reserved);
239 String result = findFreshName(reserved, proposition, acceptProposition);
240 cache.addRequested(result);
242 if (DEBUG_INDEX_SEARCH)
243 LOGGER.info(getClass().getSimpleName() + ": validated instance name " + result);
250 public List<String> validateInstanceNames(
252 Resource configurationRoot,
253 List<String> propositions,
254 boolean acceptProposition,
255 Set<String> externallyReserved) throws NamingException, DatabaseException
257 Resource indexRoot = graph.syncRequest(new PossibleIndexRoot(configurationRoot));
258 if (indexRoot == null)
259 throw new NamingException("Could not find index root from configuration root '" + NameUtils.getSafeName(graph, configurationRoot, true) + "'");
261 Layer0 L0 = Layer0.getInstance(graph);
263 synchronized (this) {
264 List<String> result = new ArrayList<String>(propositions.size());
265 Set<String> reserved = new TreeSet<String>(getComparator());
267 for (String proposition : propositions) {
268 if (propositionPreFilter != null)
269 proposition = propositionPreFilter.apply(proposition);
271 String search = IndexQueries.quoteTerm(Dependencies.FIELD_NAME_SEARCH, proposition.toLowerCase());
272 @SuppressWarnings("unchecked")
273 List<Resource> components = (List<Resource>) index.apply(graph, indexRoot, search, Integer.MAX_VALUE);
275 if (DEBUG_INDEX_SEARCH)
276 LOGGER.info(getClass().getSimpleName() + ": found " + components.size()
277 + " index results for index root " + indexRoot + " & configurationRoot " + configurationRoot
278 + " & proposition '" + proposition + "':");
281 for (Resource componentResult : components) {
282 if (DEBUG_INDEX_SEARCH)
283 LOGGER.info(getClass().getSimpleName() + ": found " + componentResult);
284 String name = graph.getPossibleRelatedValue(componentResult, L0.HasName, Bindings.STRING);
289 if (externallyReserved != null)
290 reserved.addAll(externallyReserved);
291 reserved.addAll(result);
292 String name = findFreshName(reserved, proposition, acceptProposition);
296 if (DEBUG_INDEX_SEARCH)
297 LOGGER.info(getClass().getSimpleName() + ": validated instance name " + proposition + " -> " + name);
300 if (DEBUG_INDEX_SEARCH)
301 LOGGER.info(getClass().getSimpleName() + ": validated instance names " + propositions + " -> " + result);
307 private Cache getCache(ReadGraph graph, Resource root) throws DatabaseException {
308 Cache cache = Indexing.getCache(root, Cache.class);
309 if(cache != null) return cache;
310 synchronized (this) {
311 cache = Indexing.getCache(root, Cache.class);
312 if(cache != null) return cache;
313 return Indexing.createCache(root, new Cache(caseInsensitive));
319 private final Set<String> requested;
321 Cache(boolean caseInsensitive) {
322 this.requested = new TreeSet<String>(getComparator(caseInsensitive));
325 public Set<String> getRequested() {
329 public void addRequested(String name) {
334 public String toString() {
335 return getClass().getSimpleName() + ": " + requested;