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