]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.modeling/src/org/simantics/modeling/services/ComponentNamingStrategyBase.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.modeling / src / org / simantics / modeling / services / ComponentNamingStrategyBase.java
1 package org.simantics.modeling.services;\r
2 \r
3 import java.nio.CharBuffer;\r
4 import java.util.Comparator;\r
5 import java.util.Formatter;\r
6 import java.util.List;\r
7 import java.util.Locale;\r
8 import java.util.Set;\r
9 import java.util.TreeSet;\r
10 \r
11 import org.simantics.db.ReadGraph;\r
12 import org.simantics.db.Resource;\r
13 import org.simantics.db.exception.DatabaseException;\r
14 \r
15 /**\r
16  * An abstract base class that contains some helpers for implementing your own\r
17  * {@link ComponentNamingStrategy}. A default implementation is also provided\r
18  * for {@link #findFreshInstanceName(ReadGraph, Resource, Resource, Resource)}\r
19  * based on the Layer0 ontology <code>HasGeneratedNamePrefix</code> property.\r
20  * \r
21  * @author Tuukka Lehtonen\r
22  */\r
23 public abstract class ComponentNamingStrategyBase implements ComponentNamingStrategy {\r
24 \r
25     protected static final boolean DEBUG_ALL           = false;\r
26     protected static final boolean DEBUG_NAME_MATCHING = false | DEBUG_ALL;\r
27 \r
28     protected final String         generatedNameFormat;\r
29     protected final boolean        caseInsensitive;\r
30 \r
31     /**\r
32      * Base constructor for a naming strategy with the specified format as the\r
33      * generated name format. The format will receive two arguments:\r
34      * <ol>\r
35      * <li>proposed name as {@link String}</li>\r
36      * <li>an {@link Integer} that attempts to make the name unique within a\r
37      * context</li>\r
38      * </ol>\r
39      * The simplest format specification utilizing both values is\r
40      * <code>"%s %d"</code>, producing "FOO 1". Another example of a useful name\r
41      * format is <code>"%s%04d"</code>, producing "FOO0001". Reordering the\r
42      * arguments is also possible, e.g. <code>"%2$03d %1$s"</code>, producing\r
43      * "001 FOO".\r
44      * \r
45      * <p>\r
46      * See {@link Formatter} for the format specification and how to customize\r
47      * it.\r
48      * \r
49      * <p>\r
50      * This constructor will create a case-insensitive naming strategy.\r
51      * \r
52      * @param generatedNameFormat the format to use for generated names\r
53      */\r
54     public ComponentNamingStrategyBase(String generatedNameFormat) {\r
55         this(generatedNameFormat, true);\r
56     }\r
57 \r
58     /**\r
59      * Base constructor for a naming strategy with the specified format as the\r
60      * generated name format. The format will receive two arguments:\r
61      * <ol>\r
62      * <li>proposed name as {@link String}</li>\r
63      * <li>an {@link Integer} that attempts to make the name unique within a\r
64      * context</li>\r
65      * </ol>\r
66      * The simplest format specification utilizing both values is\r
67      * <code>"%s %d"</code>, producing "FOO 1". Another example of a useful name\r
68      * format is <code>"%s%04d"</code>, producing "FOO0001". Reordering the\r
69      * arguments is also possible, e.g. <code>"%2$03d %1$s"</code>, producing\r
70      * "001 FOO".\r
71      * \r
72      * <p>\r
73      * See {@link Formatter} for the format specification and how to customize\r
74      * it.\r
75      * \r
76      * @param generatedNameFormat the format to use for generated names\r
77      * @param caseInsensitive controls whether the strategy shall be case-insensitive or not\r
78      */\r
79     public ComponentNamingStrategyBase(String generatedNameFormat, boolean caseInsensitive) {\r
80         this.generatedNameFormat = generatedNameFormat;\r
81         this.caseInsensitive = caseInsensitive;\r
82     }\r
83 \r
84     @Override\r
85     public String findFreshInstanceName(ReadGraph graph, Resource configurationRoot, Resource container,\r
86             Resource componentType) throws NamingException, DatabaseException {\r
87         String proposition = ComponentNamingUtil.generateProposition(graph, container, componentType);\r
88         return validateInstanceName(graph, configurationRoot, container, componentType, proposition);\r
89     }\r
90 \r
91     @Override\r
92     public String validateInstanceName(ReadGraph graph, Resource configurationRoot, Resource container,\r
93             Resource componentType, String proposition) throws NamingException, DatabaseException {\r
94         return validateInstanceName(graph, configurationRoot, container, componentType, proposition, false);\r
95     }\r
96 \r
97     @Override\r
98     public List<String> validateInstanceNames(\r
99             ReadGraph graph,\r
100             Resource configurationRoot,\r
101             List<String> propositions,\r
102             boolean acceptProposition,\r
103             Set<String> externallyReserved)\r
104                     throws NamingException, DatabaseException\r
105     {\r
106         throw new UnsupportedOperationException();\r
107     }\r
108 \r
109     protected Comparator<Object> getComparator() {\r
110         return getComparator(caseInsensitive);\r
111     }\r
112 \r
113     protected static Comparator<Object> getComparator(boolean caseInsensitive) {\r
114         return caseInsensitive ? CASE_INSENSITIVE_STRING_CHARBUFFER_COMPARATOR : CASE_SENSITIVE_STRING_CHARBUFFER_COMPARATOR;\r
115     }\r
116 \r
117     protected static final Comparator<Object> CASE_SENSITIVE_STRING_CHARBUFFER_COMPARATOR = new Comparator<Object>() {\r
118         @Override\r
119         public int compare(Object o1, Object o2) {\r
120             String s1 = null;\r
121             String s2 = null;\r
122             if (o1 instanceof String)\r
123                 s1 = (String) o1;\r
124             if (o2 instanceof String)\r
125                 s2 = (String) o2;\r
126             if (s1 != null && s2 != null)\r
127                 return s1.compareTo((String) o2);\r
128 \r
129             if (s1 == null && s2 == null)\r
130                 return 0;\r
131 \r
132             if (s1 == null && (o1 instanceof CharBuffer))\r
133                 return -compare(s2, (CharBuffer) o1);\r
134             if (s2 == null && (o2 instanceof CharBuffer))\r
135                 return compare(s1, (CharBuffer) o2);\r
136 \r
137             return 0;\r
138         }\r
139         int compare(String s, CharBuffer buf) {\r
140             int len1 = s.length();\r
141             int len2 = buf.position();\r
142             int n = Math.min(len1, len2);\r
143             int k = 0;\r
144             while (k < n) {\r
145                 char c1 = s.charAt(k);\r
146                 char c2 = buf.get(k);\r
147                 if (c1 != c2) {\r
148                     return c1 - c2;\r
149                 }\r
150                 k++;\r
151             }\r
152             return len1 - len2;\r
153         }\r
154     };\r
155 \r
156     protected static final Comparator<Object> CASE_INSENSITIVE_STRING_CHARBUFFER_COMPARATOR = new Comparator<Object>() {\r
157         @Override\r
158         public int compare(Object o1, Object o2) {\r
159             String s1 = null;\r
160             String s2 = null;\r
161             if (o1 instanceof String)\r
162                 s1 = (String) o1;\r
163             if (o2 instanceof String)\r
164                 s2 = (String) o2;\r
165             if (s1 != null && s2 != null)\r
166                 return s1.compareToIgnoreCase((String) o2);\r
167 \r
168             if (s1 == null && s2 == null)\r
169                 return 0;\r
170 \r
171             if (s1 == null && (o1 instanceof CharBuffer))\r
172                 return -compare(s2, (CharBuffer) o1);\r
173             if (s2 == null && (o2 instanceof CharBuffer))\r
174                 return compare(s1, (CharBuffer) o2);\r
175 \r
176             return 0;\r
177         }\r
178         int compare(String s, CharBuffer buf) {\r
179             int len1 = s.length();\r
180             int len2 = buf.position();\r
181             int n = Math.min(len1, len2);\r
182             int k = 0;\r
183             while (k < n) {\r
184                 char c1 = s.charAt(k);\r
185                 char c2 = buf.get(k);\r
186                 if (c1 != c2) {\r
187                     c1 = Character.toUpperCase(c1);\r
188                     c2 = Character.toUpperCase(c2);\r
189                     if (c1 != c2) {\r
190                         c1 = Character.toLowerCase(c1);\r
191                         c2 = Character.toLowerCase(c2);\r
192                         if (c1 != c2) {\r
193                             return c1 - c2;\r
194                         }\r
195                     }\r
196                 }\r
197                 k++;\r
198             }\r
199             return len1 - len2;\r
200         }\r
201     };\r
202 \r
203     protected Set<String> findStartsWithMatches(Set<String> matchUniverse, String proposition) {\r
204         Set<String> result = new TreeSet<String>(getComparator());\r
205         if (matchUniverse.isEmpty())\r
206             return result;\r
207 \r
208         for (String name : matchUniverse)\r
209             if (name.startsWith(proposition))\r
210                 result.add(name);\r
211         return result;\r
212     }\r
213 \r
214     protected Set<String> findStartsWithMatches(Set<String> matchUniverse, String proposition, Set<String> result) {\r
215         if (matchUniverse.isEmpty())\r
216             return result;\r
217 \r
218         for (String name : matchUniverse)\r
219             if (name.startsWith(proposition))\r
220                 result.add(name);\r
221         return result;\r
222     }\r
223 \r
224     protected String findFreshName(Set<String> allReservedNames, Set<String> allRequestedNames, String proposition, boolean acceptProposition) {\r
225         if (allReservedNames == null)\r
226             throw new NullPointerException("null reserved name set");\r
227         if (proposition == null)\r
228             throw new NullPointerException("null proposition");\r
229 \r
230         Set<String> startsWithMatches = findStartsWithMatches(allReservedNames, proposition);\r
231         if (allRequestedNames != null)\r
232             findStartsWithMatches(allRequestedNames, proposition, startsWithMatches);\r
233 \r
234         return findFreshName(startsWithMatches, proposition, acceptProposition);\r
235     }\r
236 \r
237     protected String findFreshName(Set<String> reserved, String proposition, boolean acceptProposition) {\r
238         return findFreshName(reserved, proposition, acceptProposition, generatedNameFormat);\r
239     }\r
240 \r
241     protected String findFreshName(Set<String> reserved, String proposition, boolean acceptProposition, String nameFormat) {\r
242         if (proposition == null)\r
243             throw new NullPointerException("null proposition");\r
244 \r
245         if (DEBUG_NAME_MATCHING)\r
246             System.out.println("Finding fresh proposed name '" + proposition + "' among matching reservations: " + reserved);\r
247 \r
248         if (acceptProposition && !reserved.contains(proposition))\r
249             return proposition;\r
250 \r
251         // Trying to optimize away unnecessary allocation of new String instances\r
252         // when looking for fresh names for objects.\r
253         CharBuffer cb = CharBuffer.allocate(proposition.length() + 10);\r
254         cb.mark();\r
255 \r
256         @SuppressWarnings("resource")\r
257         Formatter formatter = new Formatter(cb, Locale.US);\r
258 \r
259         //int i = reserved.size() + 1;\r
260         int i = 1;\r
261         for (;; ++i) {\r
262             cb.reset();\r
263             formatter.format(nameFormat, proposition, i);\r
264             if (!reserved.contains(cb)) {\r
265                 // Construct a String from the CharBuffer\r
266                 cb.limit(cb.position());\r
267                 cb.rewind();\r
268                 String result = cb.toString();\r
269                 if (DEBUG_NAME_MATCHING)\r
270                     System.out.println("\tfound fresh name: " + result);\r
271                 return result;\r
272             }\r
273         }\r
274     }\r
275 \r
276 }