1 package org.simantics.modeling.services;
3 import java.nio.CharBuffer;
4 import java.util.Comparator;
5 import java.util.Formatter;
7 import java.util.Locale;
9 import java.util.TreeSet;
11 import org.simantics.db.ReadGraph;
12 import org.simantics.db.Resource;
13 import org.simantics.db.exception.DatabaseException;
16 * An abstract base class that contains some helpers for implementing your own
17 * {@link ComponentNamingStrategy}. A default implementation is also provided
18 * for {@link #findFreshInstanceName(ReadGraph, Resource, Resource, Resource)}
19 * based on the Layer0 ontology <code>HasGeneratedNamePrefix</code> property.
21 * @author Tuukka Lehtonen
23 public abstract class ComponentNamingStrategyBase implements ComponentNamingStrategy {
25 protected static final boolean DEBUG_ALL = false;
26 protected static final boolean DEBUG_NAME_MATCHING = false | DEBUG_ALL;
28 protected final String generatedNameFormat;
29 protected final boolean caseInsensitive;
32 * Base constructor for a naming strategy with the specified format as the
33 * generated name format. The format will receive two arguments:
35 * <li>proposed name as {@link String}</li>
36 * <li>an {@link Integer} that attempts to make the name unique within a
39 * The simplest format specification utilizing both values is
40 * <code>"%s %d"</code>, producing "FOO 1". Another example of a useful name
41 * format is <code>"%s%04d"</code>, producing "FOO0001". Reordering the
42 * arguments is also possible, e.g. <code>"%2$03d %1$s"</code>, producing
46 * See {@link Formatter} for the format specification and how to customize
50 * This constructor will create a case-insensitive naming strategy.
52 * @param generatedNameFormat the format to use for generated names
54 public ComponentNamingStrategyBase(String generatedNameFormat) {
55 this(generatedNameFormat, true);
59 * Base constructor for a naming strategy with the specified format as the
60 * generated name format. The format will receive two arguments:
62 * <li>proposed name as {@link String}</li>
63 * <li>an {@link Integer} that attempts to make the name unique within a
66 * The simplest format specification utilizing both values is
67 * <code>"%s %d"</code>, producing "FOO 1". Another example of a useful name
68 * format is <code>"%s%04d"</code>, producing "FOO0001". Reordering the
69 * arguments is also possible, e.g. <code>"%2$03d %1$s"</code>, producing
73 * See {@link Formatter} for the format specification and how to customize
76 * @param generatedNameFormat the format to use for generated names
77 * @param caseInsensitive controls whether the strategy shall be case-insensitive or not
79 public ComponentNamingStrategyBase(String generatedNameFormat, boolean caseInsensitive) {
80 this.generatedNameFormat = generatedNameFormat;
81 this.caseInsensitive = caseInsensitive;
85 public String findFreshInstanceName(ReadGraph graph, Resource configurationRoot, Resource container,
86 Resource componentType) throws NamingException, DatabaseException {
87 String proposition = ComponentNamingUtil.generateProposition(graph, container, componentType);
88 return validateInstanceName(graph, configurationRoot, container, componentType, proposition);
92 public String validateInstanceName(ReadGraph graph, Resource configurationRoot, Resource container,
93 Resource componentType, String proposition) throws NamingException, DatabaseException {
94 return validateInstanceName(graph, configurationRoot, container, componentType, proposition, false);
98 public List<String> validateInstanceNames(
100 Resource configurationRoot,
101 List<String> propositions,
102 boolean acceptProposition,
103 Set<String> externallyReserved)
104 throws NamingException, DatabaseException
106 throw new UnsupportedOperationException();
109 protected Comparator<Object> getComparator() {
110 return getComparator(caseInsensitive);
113 protected static Comparator<Object> getComparator(boolean caseInsensitive) {
114 return caseInsensitive ? CASE_INSENSITIVE_STRING_CHARBUFFER_COMPARATOR : CASE_SENSITIVE_STRING_CHARBUFFER_COMPARATOR;
117 protected static final Comparator<Object> CASE_SENSITIVE_STRING_CHARBUFFER_COMPARATOR = new Comparator<Object>() {
119 public int compare(Object o1, Object o2) {
122 if (o1 instanceof String)
124 if (o2 instanceof String)
126 if (s1 != null && s2 != null)
127 return s1.compareTo((String) o2);
129 if (s1 == null && s2 == null)
132 if (s1 == null && (o1 instanceof CharBuffer))
133 return -compare(s2, (CharBuffer) o1);
134 if (s2 == null && (o2 instanceof CharBuffer))
135 return compare(s1, (CharBuffer) o2);
139 int compare(String s, CharBuffer buf) {
140 int len1 = s.length();
141 int len2 = buf.position();
142 int n = Math.min(len1, len2);
145 char c1 = s.charAt(k);
146 char c2 = buf.get(k);
156 protected static final Comparator<Object> CASE_INSENSITIVE_STRING_CHARBUFFER_COMPARATOR = new Comparator<Object>() {
158 public int compare(Object o1, Object o2) {
161 if (o1 instanceof String)
163 if (o2 instanceof String)
165 if (s1 != null && s2 != null)
166 return s1.compareToIgnoreCase((String) o2);
168 if (s1 == null && s2 == null)
171 if (s1 == null && (o1 instanceof CharBuffer))
172 return -compare(s2, (CharBuffer) o1);
173 if (s2 == null && (o2 instanceof CharBuffer))
174 return compare(s1, (CharBuffer) o2);
178 int compare(String s, CharBuffer buf) {
179 int len1 = s.length();
180 int len2 = buf.position();
181 int n = Math.min(len1, len2);
184 char c1 = s.charAt(k);
185 char c2 = buf.get(k);
187 c1 = Character.toUpperCase(c1);
188 c2 = Character.toUpperCase(c2);
190 c1 = Character.toLowerCase(c1);
191 c2 = Character.toLowerCase(c2);
203 protected Set<String> findStartsWithMatches(Set<String> matchUniverse, String proposition) {
204 Set<String> result = new TreeSet<String>(getComparator());
205 if (matchUniverse.isEmpty())
208 for (String name : matchUniverse)
209 if (name.startsWith(proposition))
214 protected Set<String> findStartsWithMatches(Set<String> matchUniverse, String proposition, Set<String> result) {
215 if (matchUniverse.isEmpty())
218 for (String name : matchUniverse)
219 if (name.startsWith(proposition))
224 protected String findFreshName(Set<String> allReservedNames, Set<String> allRequestedNames, String proposition, boolean acceptProposition) {
225 if (allReservedNames == null)
226 throw new NullPointerException("null reserved name set");
227 if (proposition == null)
228 throw new NullPointerException("null proposition");
230 Set<String> startsWithMatches = findStartsWithMatches(allReservedNames, proposition);
231 if (allRequestedNames != null)
232 findStartsWithMatches(allRequestedNames, proposition, startsWithMatches);
234 return findFreshName(startsWithMatches, proposition, acceptProposition);
237 protected String findFreshName(Set<String> reserved, String proposition, boolean acceptProposition) {
238 return findFreshName(reserved, proposition, acceptProposition, generatedNameFormat);
241 protected String findFreshName(Set<String> reserved, String proposition, boolean acceptProposition, String nameFormat) {
242 if (proposition == null)
243 throw new NullPointerException("null proposition");
245 if (DEBUG_NAME_MATCHING)
246 System.out.println("Finding fresh proposed name '" + proposition + "' among matching reservations: " + reserved);
248 if (acceptProposition && !reserved.contains(proposition))
251 // Trying to optimize away unnecessary allocation of new String instances
252 // when looking for fresh names for objects.
253 CharBuffer cb = CharBuffer.allocate(proposition.length() + 10);
256 @SuppressWarnings("resource")
257 Formatter formatter = new Formatter(cb, Locale.US);
259 //int i = reserved.size() + 1;
263 formatter.format(nameFormat, proposition, i);
264 if (!reserved.contains(cb)) {
265 // Construct a String from the CharBuffer
266 cb.limit(cb.position());
268 String result = cb.toString();
269 if (DEBUG_NAME_MATCHING)
270 System.out.println("\tfound fresh name: " + result);