ad48d1f2f809ab89e48a545da83ae88f26e094ca
[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.IndexQueries;
31 import org.simantics.db.service.GraphChangeListenerSupport;
32 import org.simantics.layer0.Layer0;
33 import org.simantics.scl.runtime.function.Function;
34 import org.simantics.scl.runtime.function.Function1;
35 import org.simantics.scl.runtime.tuple.Tuple4;
36 import org.simantics.structural.stubs.StructuralResource2;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
39
40 import gnu.trove.set.hash.THashSet;
41
42 /**
43  * A component naming strategy implementation for structural models based on an
44  * SCL function that lists used names in a model.
45  * 
46  * <p>
47  * The type of the function is expected to be:
48  * <code>ReadGraph => Resource -> String -> Integer -> List&lt;Map&lt;String,Object&gt;&gt;</code>
49  * 
50  * @author Tuukka Lehtonen
51  * 
52  * @see ComponentNamingStrategy
53  */
54 public class CaseInsensitiveComponentFunctionNamingStrategy extends ComponentNamingStrategyBase {
55
56     private static final Logger LOGGER = LoggerFactory.getLogger(CaseInsensitiveComponentFunctionNamingStrategy.class);
57     protected static final boolean                                  DEBUG_INDEX_SEARCH = false | DEBUG_ALL;
58
59     @SuppressWarnings("rawtypes")
60     private final Function                                          index;
61
62     /**
63      * A filter that is applied to all user-provided name propositions before
64      * processing them.
65      */
66     private Function1<String, String>                               propositionPreFilter;
67
68     /**
69      * Construct an index-based naming strategy with "%s %d" as the generated
70      * name format. See {@link Formatter} for the format specification.
71      * 
72      * @param index the index function for looking up used names. The function
73      *        must be of type
74      *        <code>ReadGraph => Resource -> String -> Integer -> List&lt;Map&lt;String,Object&gt;&gt;</code>
75      */
76     @SuppressWarnings("rawtypes")
77     public CaseInsensitiveComponentFunctionNamingStrategy(Function index) {
78         this("%s %d", index);
79     }
80
81     /**
82      * Construct an index-based naming strategy with the specified format as the
83      * generated name format. See {@link Formatter} for the format
84      * specification.
85      * 
86      * @param generatedNameFormat the format to use for generated names, see
87      *        {@link Formatter}
88      * @param index the index function for looking up used names. The function
89      *        must be of type
90      *        <code>ReadGraph => Resource -> String -> Integer -> List&lt;Map&lt;String,Object&gt;&gt;</code>
91      */
92     @SuppressWarnings("rawtypes")
93     public CaseInsensitiveComponentFunctionNamingStrategy(String generatedNameFormat, Function index) {
94         super(generatedNameFormat);
95         this.index = index;
96     }
97
98     /**
99      * Construct an index-based naming strategy with the specified format as the
100      * generated name format. See {@link Formatter} for the format
101      * specification.
102      * 
103      * @param generatedNameFormat the format to use for generated names, see
104      *        {@link Formatter}
105      * @param index the index function for looking up used names. The function
106      *        must be of type
107      *        <code>ReadGraph => Resource -> String -> Integer -> List&lt;Map&lt;String,Object&gt;&gt;</code>
108      * @param an optional function to 
109      */
110     @SuppressWarnings("rawtypes")
111     public CaseInsensitiveComponentFunctionNamingStrategy(String generatedNameFormat, Function index, Function1<String, String> propositionPreFilter) {
112         super(generatedNameFormat);
113         this.index = index;
114         this.propositionPreFilter = propositionPreFilter;
115     }
116
117     CaseInsensitiveComponentNamingStrategy2 fallbackStrategy = null;
118
119     @Override
120     public String validateInstanceName(ReadGraph graph,
121                 Resource configurationRoot, Resource component, String proposition, boolean acceptProposition)
122                 throws NamingException, DatabaseException {
123
124         String lowercaseProposition = proposition.toLowerCase();
125         
126         Layer0 L0 = Layer0.getInstance(graph);
127         String name = graph.getPossibleRelatedValue(component, L0.HasName, Bindings.STRING);
128         if (name != null) {
129         
130                 String lowercaseName = name.toLowerCase();
131                 if(lowercaseName.startsWith(lowercaseProposition)) {
132                         
133                 Resource indexRoot = graph.syncRequest(new PossibleIndexRoot(configurationRoot));
134                 if (indexRoot != null) {
135                         
136                     synchronized (this) {
137                         
138                         String search = "Name:" + lowercaseName + "*";
139                         @SuppressWarnings("unchecked")
140                         List<Resource> components = (List<Resource>) index.apply(graph, indexRoot, search, Integer.MAX_VALUE);
141
142                         Set<Resource> rs = new THashSet<Resource>();
143                         for (Resource componentResult : components) {
144                             if (DEBUG_INDEX_SEARCH)
145                                 LOGGER.info(getClass().getSimpleName() + ": found " + componentResult);
146                             String n = graph.getPossibleRelatedValue(componentResult, L0.HasName, Bindings.STRING);
147                             if (n != null && n.toLowerCase().equals(lowercaseName))
148                                 rs.add(componentResult);
149                         }
150                         
151                         Cache c = getCache(graph, indexRoot);
152                         
153                         if(rs.size() == 0) { 
154                                 if(!c.getRequested().contains(name)) {
155                                         c.addRequested(name);
156                                         return name;
157                                 }
158                         } else if(rs.size() == 1) {
159                                 if(component.equals(rs.iterator().next())) {
160                                 return name;
161                                 }
162                         }
163
164                     }
165                         
166                 }
167                         
168                 }
169                 
170         }
171
172         StructuralResource2 STR = StructuralResource2.getInstance(graph);
173         Resource container = graph.getSingleObject(component, L0.PartOf);
174         Resource componentType = graph.getSingleType(component, STR.Component);
175         return validateInstanceName(graph, configurationRoot, container, componentType, proposition, acceptProposition);
176         
177     }
178     
179     static class ComponentsRequest extends UnaryRead<Tuple4, Set<String>>{
180
181                 public ComponentsRequest(Tuple4 parameter) {
182                         super(parameter);
183                 }
184
185                 @Override
186                 public Set<String> perform(ReadGraph graph) throws DatabaseException {
187                         
188                         Resource indexRoot = (Resource)parameter.get(0);
189                         Function index = (Function)parameter.get(1);
190                         String search = (String)parameter.get(2);
191                         Comparator<Object> comparator = (Comparator<Object>)parameter.get(3);
192                         
193             List<Resource> components = (List<Resource>) index.apply(graph, indexRoot, search, Integer.MAX_VALUE);
194             Set<String> reserved = new TreeSet<String>(comparator);
195
196             Layer0 L0 = Layer0.getInstance(graph);
197             for (Resource componentResult : components) {
198                 if (DEBUG_INDEX_SEARCH)
199                     LOGGER.info(getClass().getSimpleName() + ": found " + componentResult);
200                 String name = graph.getPossibleRelatedValue(componentResult, L0.HasName, Bindings.STRING);
201                 if (name != null)
202                     reserved.add(name);
203             }
204             
205             LOGGER.warn("found " + reserved.size() + " components");
206             
207             return reserved;
208
209                 }
210         
211     }
212     
213     @Override
214     public String validateInstanceName(ReadGraph graph, Resource configurationRoot, Resource container,
215             Resource componentType, String proposition, boolean acceptProposition) throws NamingException, DatabaseException {
216         Resource indexRoot = graph.syncRequest(new PossibleIndexRoot(configurationRoot));
217         if (indexRoot == null) {
218             LOGGER.warn("Could not find index root from configuration root '" + NameUtils.getSafeName(graph, configurationRoot, true) + "'");
219             if(fallbackStrategy == null)
220                 fallbackStrategy = 
221                     new CaseInsensitiveComponentNamingStrategy2(graph.getService(GraphChangeListenerSupport.class), generatedNameFormat);
222             return fallbackStrategy.validateInstanceName(graph, configurationRoot, container, componentType, proposition, acceptProposition);
223         }
224
225         if (propositionPreFilter != null)
226             proposition = propositionPreFilter.apply(proposition);
227
228         synchronized (this) {
229                 
230             String search = "Name:" + proposition + "*";
231             
232             Set<String> reserved = graph.syncRequest(new ComponentsRequest(new Tuple4(indexRoot, index, search, getComparator())), TransientCacheAsyncListener.instance());
233
234             Cache cache = getCache(graph, indexRoot);
235
236             findStartsWithMatches(cache.getRequested(), proposition, reserved);
237
238             String result = findFreshName(reserved, proposition, acceptProposition);
239             cache.addRequested(result);
240
241             if (DEBUG_INDEX_SEARCH)
242                 LOGGER.info(getClass().getSimpleName() + ": validated instance name " + result);
243
244             return result;
245         }
246     }
247
248     @Override
249     public List<String> validateInstanceNames(
250             ReadGraph graph,
251             Resource configurationRoot,
252             List<String> propositions,
253             boolean acceptProposition,
254             Set<String> externallyReserved) throws NamingException, DatabaseException
255     {
256         Resource indexRoot = graph.syncRequest(new PossibleIndexRoot(configurationRoot));
257         if (indexRoot == null)
258             throw new NamingException("Could not find index root from configuration root '" + NameUtils.getSafeName(graph, configurationRoot, true) + "'");
259
260         Layer0 L0 = Layer0.getInstance(graph);
261
262         synchronized (this) {
263             List<String> result = new ArrayList<String>(propositions.size());
264             Set<String> reserved = new TreeSet<String>(getComparator());
265
266             for (String proposition : propositions) {
267                 if (propositionPreFilter != null)
268                     proposition = propositionPreFilter.apply(proposition);
269
270                 String search = "Name:" + IndexQueries.quoteTerm( proposition );
271                 @SuppressWarnings("unchecked")
272                 List<Resource> components = (List<Resource>) index.apply(graph, indexRoot, search, Integer.MAX_VALUE);
273
274                 if (DEBUG_INDEX_SEARCH)
275                     LOGGER.info(getClass().getSimpleName() + ": found " + components.size()
276                             + " index results for index root " + indexRoot + " & configurationRoot " + configurationRoot
277                             + " & proposition '" + proposition + "':");
278
279                 reserved.clear();
280                 for (Resource componentResult : components) {
281                     if (DEBUG_INDEX_SEARCH)
282                         LOGGER.info(getClass().getSimpleName() + ": found " + componentResult);
283                     String name = graph.getPossibleRelatedValue(componentResult, L0.HasName, Bindings.STRING);
284                     if (name != null)
285                         reserved.add(name);
286                 }
287
288                 if (externallyReserved != null)
289                     reserved.addAll(externallyReserved);
290                 reserved.addAll(result);
291                 String name = findFreshName(reserved, proposition, acceptProposition);
292
293                 result.add(name);
294
295                 if (DEBUG_INDEX_SEARCH)
296                     LOGGER.info(getClass().getSimpleName() + ": validated instance name " + proposition + " -> " + name);
297             }
298
299             if (DEBUG_INDEX_SEARCH)
300                 LOGGER.info(getClass().getSimpleName() + ": validated instance names " + propositions + " -> " + result);
301
302             return result;
303         }
304     }
305
306     private Cache getCache(ReadGraph graph, Resource root) throws DatabaseException {
307         Cache cache = Indexing.getCache(root, Cache.class);
308         if(cache != null) return cache;
309         synchronized (this) {
310             cache = Indexing.getCache(root, Cache.class);
311             if(cache != null) return cache;
312             return Indexing.createCache(root, new Cache(caseInsensitive)); 
313         }
314     }
315
316     static class Cache {
317         
318         private final Set<String> requested;
319
320         Cache(boolean caseInsensitive) {
321             this.requested = new TreeSet<String>(getComparator(caseInsensitive));
322         }
323
324         public Set<String> getRequested() {
325             return requested;
326         }
327
328         public void addRequested(String name) {
329             requested.add(name);
330         }
331
332         @Override
333         public String toString() {
334             return getClass().getSimpleName() + ": " + requested;
335         }
336
337     }
338
339 }