Index query fixes after commit 5e340942
[simantics/platform.git] / bundles / org.simantics.modeling / src / org / simantics / modeling / services / CaseInsensitiveComponentFunctionNamingStrategy.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management
3  * in Industry THTH ry.
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
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.modeling.services;
13
14 import java.util.ArrayList;
15 import java.util.Comparator;
16 import java.util.Formatter;
17 import java.util.List;
18 import java.util.Set;
19 import java.util.TreeSet;
20
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;
40
41 import gnu.trove.set.hash.THashSet;
42
43 /**
44  * A component naming strategy implementation for structural models based on an
45  * SCL function that lists used names in a model.
46  * 
47  * <p>
48  * The type of the function is expected to be:
49  * <code>ReadGraph => Resource -> String -> Integer -> List&lt;Resource&gt;</code>
50  * 
51  * @author Tuukka Lehtonen
52  * 
53  * @see ComponentNamingStrategy
54  */
55 public class CaseInsensitiveComponentFunctionNamingStrategy extends ComponentNamingStrategyBase {
56
57     private static final Logger LOGGER = LoggerFactory.getLogger(CaseInsensitiveComponentFunctionNamingStrategy.class);
58     protected static final boolean                                  DEBUG_INDEX_SEARCH = false | DEBUG_ALL;
59
60     @SuppressWarnings("rawtypes")
61     private final Function                                          index;
62
63     /**
64      * A filter that is applied to all user-provided name propositions before
65      * processing them.
66      */
67     private Function1<String, String>                               propositionPreFilter;
68
69     /**
70      * Construct an index-based naming strategy with "%s %d" as the generated
71      * name format. See {@link Formatter} for the format specification.
72      * 
73      * @param index the index function for looking up used names. The function
74      *        must be of type
75      *        <code>ReadGraph => Resource -> String -> Integer -> List&lt;Map&lt;String,Object&gt;&gt;</code>
76      */
77     @SuppressWarnings("rawtypes")
78     public CaseInsensitiveComponentFunctionNamingStrategy(Function index) {
79         this("%s %d", index);
80     }
81
82     /**
83      * Construct an index-based naming strategy with the specified format as the
84      * generated name format. See {@link Formatter} for the format
85      * specification.
86      * 
87      * @param generatedNameFormat the format to use for generated names, see
88      *        {@link Formatter}
89      * @param index the index function for looking up used names. The function
90      *        must be of type
91      *        <code>ReadGraph => Resource -> String -> Integer -> List&lt;Map&lt;String,Object&gt;&gt;</code>
92      */
93     @SuppressWarnings("rawtypes")
94     public CaseInsensitiveComponentFunctionNamingStrategy(String generatedNameFormat, Function index) {
95         super(generatedNameFormat);
96         this.index = index;
97     }
98
99     /**
100      * Construct an index-based naming strategy with the specified format as the
101      * generated name format. See {@link Formatter} for the format
102      * specification.
103      * 
104      * @param generatedNameFormat the format to use for generated names, see
105      *        {@link Formatter}
106      * @param index the index function for looking up used names. The function
107      *        must be of type
108      *        <code>ReadGraph => Resource -> String -> Integer -> List&lt;Map&lt;String,Object&gt;&gt;</code>
109      * @param an optional function to 
110      */
111     @SuppressWarnings("rawtypes")
112     public CaseInsensitiveComponentFunctionNamingStrategy(String generatedNameFormat, Function index, Function1<String, String> propositionPreFilter) {
113         super(generatedNameFormat);
114         this.index = index;
115         this.propositionPreFilter = propositionPreFilter;
116     }
117
118     CaseInsensitiveComponentNamingStrategy2 fallbackStrategy = null;
119
120     @Override
121     public String validateInstanceName(ReadGraph graph,
122                 Resource configurationRoot, Resource component, String proposition, boolean acceptProposition)
123                 throws NamingException, DatabaseException {
124
125         String lowercaseProposition = proposition.toLowerCase();
126         
127         Layer0 L0 = Layer0.getInstance(graph);
128         String name = graph.getPossibleRelatedValue(component, L0.HasName, Bindings.STRING);
129         if (name != null) {
130         
131                 String lowercaseName = name.toLowerCase();
132                 if(lowercaseName.startsWith(lowercaseProposition)) {
133                         
134                 Resource indexRoot = graph.syncRequest(new PossibleIndexRoot(configurationRoot));
135                 if (indexRoot != null) {
136                         
137                     synchronized (this) {
138                         
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);
142
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);
150                         }
151                         
152                         Cache c = getCache(graph, indexRoot);
153                         
154                         if(rs.size() == 0) { 
155                                 if(!c.getRequested().contains(name)) {
156                                         c.addRequested(name);
157                                         return name;
158                                 }
159                         } else if(rs.size() == 1) {
160                                 if(component.equals(rs.iterator().next())) {
161                                 return name;
162                                 }
163                         }
164
165                     }
166                         
167                 }
168                         
169                 }
170                 
171         }
172
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);
177         
178     }
179     
180     static class ComponentsRequest extends UnaryRead<Tuple4, Set<String>>{
181
182                 public ComponentsRequest(Tuple4 parameter) {
183                         super(parameter);
184                 }
185
186                 @Override
187                 public Set<String> perform(ReadGraph graph) throws DatabaseException {
188                         
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);
193                         
194             List<Resource> components = (List<Resource>) index.apply(graph, indexRoot, search, Integer.MAX_VALUE);
195             Set<String> reserved = new TreeSet<String>(comparator);
196
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);
202                 if (name != null)
203                     reserved.add(name);
204             }
205             
206             LOGGER.warn("found " + reserved.size() + " components");
207             
208             return reserved;
209
210                 }
211         
212     }
213     
214     @Override
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)
221                 fallbackStrategy = 
222                     new CaseInsensitiveComponentNamingStrategy2(graph.getService(GraphChangeListenerSupport.class), generatedNameFormat);
223             return fallbackStrategy.validateInstanceName(graph, configurationRoot, container, componentType, proposition, acceptProposition);
224         }
225
226         if (propositionPreFilter != null)
227             proposition = propositionPreFilter.apply(proposition);
228
229         synchronized (this) {
230                 
231             String search = Dependencies.FIELD_NAME_SEARCH + ":" + IndexQueries.escape(proposition.toLowerCase(), true) + "*"; //$NON-NLS-1$ //$NON-NLS-2$
232             
233             Set<String> reserved = graph.syncRequest(new ComponentsRequest(new Tuple4(indexRoot, index, search, getComparator())), TransientCacheAsyncListener.instance());
234
235             Cache cache = getCache(graph, indexRoot);
236
237             findStartsWithMatches(cache.getRequested(), proposition, reserved);
238
239             String result = findFreshName(reserved, proposition, acceptProposition);
240             cache.addRequested(result);
241
242             if (DEBUG_INDEX_SEARCH)
243                 LOGGER.info(getClass().getSimpleName() + ": validated instance name " + result);
244
245             return result;
246         }
247     }
248
249     @Override
250     public List<String> validateInstanceNames(
251             ReadGraph graph,
252             Resource configurationRoot,
253             List<String> propositions,
254             boolean acceptProposition,
255             Set<String> externallyReserved) throws NamingException, DatabaseException
256     {
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) + "'");
260
261         Layer0 L0 = Layer0.getInstance(graph);
262
263         synchronized (this) {
264             List<String> result = new ArrayList<String>(propositions.size());
265             Set<String> reserved = new TreeSet<String>(getComparator());
266
267             for (String proposition : propositions) {
268                 if (propositionPreFilter != null)
269                     proposition = propositionPreFilter.apply(proposition);
270
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);
274
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 + "':");
279
280                 reserved.clear();
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);
285                     if (name != null)
286                         reserved.add(name);
287                 }
288
289                 if (externallyReserved != null)
290                     reserved.addAll(externallyReserved);
291                 reserved.addAll(result);
292                 String name = findFreshName(reserved, proposition, acceptProposition);
293
294                 result.add(name);
295
296                 if (DEBUG_INDEX_SEARCH)
297                     LOGGER.info(getClass().getSimpleName() + ": validated instance name " + proposition + " -> " + name);
298             }
299
300             if (DEBUG_INDEX_SEARCH)
301                 LOGGER.info(getClass().getSimpleName() + ": validated instance names " + propositions + " -> " + result);
302
303             return result;
304         }
305     }
306
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)); 
314         }
315     }
316
317     static class Cache {
318         
319         private final Set<String> requested;
320
321         Cache(boolean caseInsensitive) {
322             this.requested = new TreeSet<String>(getComparator(caseInsensitive));
323         }
324
325         public Set<String> getRequested() {
326             return requested;
327         }
328
329         public void addRequested(String name) {
330             requested.add(name);
331         }
332
333         @Override
334         public String toString() {
335             return getClass().getSimpleName() + ": " + requested;
336         }
337
338     }
339
340 }